mirror of https://github.com/Minestom/Minestom.git
Compare commits
18 Commits
cc636614df
...
2de92f5152
Author | SHA1 | Date |
---|---|---|
GoldenStack | 2de92f5152 | |
GoldenStack | b728031ca5 | |
GoldenStack | ed225efb11 | |
GoldenStack | 95efe11ba4 | |
GoldenStack | 93421e1740 | |
themode | 67e6686acc | |
themode | 0e91a565b4 | |
themode | da3523f559 | |
GoldenStack | d4b44c7137 | |
GoldenStack | 3a8bab554a | |
GoldenStack | e9d957814e | |
themode | 1dc9a20541 | |
themode | 3e3182660c | |
themode | 1573b34970 | |
themode | 74eb1ac794 | |
themode | 18406cb504 | |
GoldenStack | 8c79660681 | |
GoldenStack | 1a497115b0 |
|
@ -26,7 +26,7 @@ import net.minestom.server.instance.InstanceContainer;
|
|||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.LightingChunk;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
public class PlayerInit {
|
||||
|
||||
private static final Inventory inventory;
|
||||
private static final ContainerInventory inventory;
|
||||
|
||||
private static final EventNode<Event> DEMO_NODE = EventNode.all("demo")
|
||||
.addListener(EntityAttackEvent.class, event -> {
|
||||
|
@ -187,7 +187,7 @@ public class PlayerInit {
|
|||
// System.out.println("light end");
|
||||
// });
|
||||
|
||||
inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
||||
inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
||||
inventory.setItemStack(3, ItemStack.of(Material.DIAMOND, 34));
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import net.kyori.adventure.text.Component;
|
|||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.TransactionOption;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.utils.entity.EntityFinder;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -25,7 +25,7 @@ public class GiveCommand extends Command {
|
|||
addSyntax((sender, context) -> {
|
||||
final EntityFinder entityFinder = context.get("target");
|
||||
int count = context.get("count");
|
||||
count = Math.min(count, PlayerInventory.INVENTORY_SIZE * 64);
|
||||
count = Math.min(count, PlayerInventoryUtils.INVENTORY_SIZE * 64);
|
||||
ItemStack itemStack = context.get("item");
|
||||
|
||||
List<ItemStack> itemStacks;
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package net.minestom.server.entity;
|
||||
|
||||
import net.minestom.server.item.attribute.AttributeSlot;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
||||
|
||||
public enum EquipmentSlot {
|
||||
MAIN_HAND(false, -1),
|
||||
OFF_HAND(false, -1),
|
||||
BOOTS(true, BOOTS_SLOT),
|
||||
LEGGINGS(true, LEGGINGS_SLOT),
|
||||
CHESTPLATE(true, CHESTPLATE_SLOT),
|
||||
HELMET(true, HELMET_SLOT);
|
||||
BOOTS(true, PlayerInventoryUtils.BOOTS_SLOT),
|
||||
LEGGINGS(true, PlayerInventoryUtils.LEGGINGS_SLOT),
|
||||
CHESTPLATE(true, PlayerInventoryUtils.CHESTPLATE_SLOT),
|
||||
HELMET(true, PlayerInventoryUtils.HELMET_SLOT);
|
||||
|
||||
private static final List<EquipmentSlot> ARMORS = List.of(BOOTS, LEGGINGS, CHESTPLATE, HELMET);
|
||||
|
||||
|
|
|
@ -45,9 +45,10 @@ import net.minestom.server.event.player.*;
|
|||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.EntityTracker;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.WrittenBookMeta;
|
||||
|
@ -178,10 +179,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
private int level;
|
||||
private int portalCooldown = 0;
|
||||
|
||||
protected Click.Preprocessor clickPreprocessor = new Click.Preprocessor();
|
||||
protected PlayerInventory inventory;
|
||||
private Inventory openInventory;
|
||||
// Used internally to allow the closing of inventory within the inventory listener
|
||||
private boolean didCloseInventory;
|
||||
private boolean skipClosePacket;
|
||||
|
||||
private byte heldSlot;
|
||||
|
||||
|
@ -239,7 +241,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
setRespawnPoint(Pos.ZERO);
|
||||
|
||||
this.settings = new PlayerSettings();
|
||||
this.inventory = new PlayerInventory(this);
|
||||
this.inventory = new PlayerInventory();
|
||||
|
||||
setCanPickupItem(true); // By default
|
||||
|
||||
|
@ -370,6 +372,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
refreshHealth(); // Heal and send health packet
|
||||
refreshAbilities(); // Send abilities packet
|
||||
|
||||
inventory.addViewer(this);
|
||||
|
||||
return setInstance(spawnInstance);
|
||||
}
|
||||
|
||||
|
@ -1008,11 +1012,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
.pages(book.pages()))
|
||||
.build();
|
||||
// Set book in offhand
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook));
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, writtenBook));
|
||||
// Open the book
|
||||
sendPacket(new OpenBookPacket(Hand.OFF));
|
||||
// Restore the item in offhand
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, getItemInOffHand()));
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, getItemInOffHand()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1717,6 +1721,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
this.belowNameTag = belowNameTag;
|
||||
}
|
||||
|
||||
public @NotNull Click.Preprocessor clickPreprocessor() {
|
||||
return clickPreprocessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player open inventory.
|
||||
*
|
||||
|
@ -1726,6 +1734,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
return openInventory;
|
||||
}
|
||||
|
||||
private void tryCloseInventory(boolean skipClosePacket) {
|
||||
var closedInventory = getOpenInventory();
|
||||
if (closedInventory == null) return;
|
||||
|
||||
this.skipClosePacket = skipClosePacket;
|
||||
|
||||
if (closedInventory.removeViewer(this)) {
|
||||
if (closedInventory == getOpenInventory()) {
|
||||
this.openInventory = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified Inventory, close the previous inventory if existing.
|
||||
*
|
||||
|
@ -1736,21 +1757,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
||||
|
||||
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
|
||||
Inventory openInventory = getOpenInventory();
|
||||
if (openInventory != null) {
|
||||
openInventory.removeViewer(this);
|
||||
}
|
||||
tryCloseInventory(true);
|
||||
|
||||
Inventory newInventory = inventoryOpenEvent.getInventory();
|
||||
if (newInventory == null) {
|
||||
// just close the inventory
|
||||
return;
|
||||
if (newInventory.addViewer(this)) {
|
||||
this.openInventory = newInventory;
|
||||
}
|
||||
|
||||
sendPacket(new OpenWindowPacket(newInventory.getWindowId(),
|
||||
newInventory.getInventoryType().getWindowType(), newInventory.getTitle()));
|
||||
newInventory.addViewer(this);
|
||||
this.openInventory = newInventory;
|
||||
});
|
||||
return !inventoryOpenEvent.isCancelled();
|
||||
}
|
||||
|
@ -1765,61 +1777,28 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
|
||||
@ApiStatus.Internal
|
||||
public void closeInventory(boolean fromClient) {
|
||||
Inventory openInventory = getOpenInventory();
|
||||
|
||||
// Drop cursor item when closing inventory
|
||||
ItemStack cursorItem;
|
||||
if (openInventory == null) {
|
||||
cursorItem = getInventory().getCursorItem();
|
||||
getInventory().setCursorItem(ItemStack.AIR);
|
||||
} else {
|
||||
cursorItem = openInventory.getCursorItem(this);
|
||||
openInventory.setCursorItem(this, ItemStack.AIR);
|
||||
}
|
||||
if (!cursorItem.isAir()) {
|
||||
// Add item to inventory if he hasn't been able to drop it
|
||||
if (!dropItem(cursorItem)) {
|
||||
getInventory().addItemStack(cursorItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (openInventory == getOpenInventory()) {
|
||||
CloseWindowPacket closeWindowPacket;
|
||||
if (openInventory == null) {
|
||||
closeWindowPacket = new CloseWindowPacket((byte) 0);
|
||||
} else {
|
||||
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
|
||||
openInventory.removeViewer(this); // Clear cache
|
||||
this.openInventory = null;
|
||||
}
|
||||
if (!fromClient) sendPacket(closeWindowPacket);
|
||||
inventory.update();
|
||||
this.didCloseInventory = true;
|
||||
}
|
||||
tryCloseInventory(fromClient);
|
||||
inventory.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally to prevent an inventory click to be processed
|
||||
* when the inventory listeners closed the inventory.
|
||||
* <p>
|
||||
* Should only be used within an inventory listener (event or condition).
|
||||
*
|
||||
* @return true if the inventory has been closed, false otherwise
|
||||
* Used internally to determine when sending the close inventory packet should be skipped.
|
||||
*/
|
||||
public boolean didCloseInventory() {
|
||||
return didCloseInventory;
|
||||
public boolean skipClosePacket() {
|
||||
return skipClosePacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally to reset the didCloseInventory field.
|
||||
* Used internally to reset the skipClosePacket field, which determines when sending the close inventory packet
|
||||
* should be skipped.
|
||||
* <p>
|
||||
* Shouldn't be used externally without proper understanding of its consequence.
|
||||
*
|
||||
* @param didCloseInventory the new didCloseInventory field
|
||||
* @param skipClosePacket the new skipClosePacket field
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public void UNSAFE_changeDidCloseInventory(boolean didCloseInventory) {
|
||||
this.didCloseInventory = didCloseInventory;
|
||||
public void UNSAFE_changeSkipClosePacket(boolean skipClosePacket) {
|
||||
this.skipClosePacket = skipClosePacket;
|
||||
}
|
||||
|
||||
public int getNextTeleportId() {
|
||||
|
@ -2304,62 +2283,62 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInMainHand() {
|
||||
return inventory.getItemInMainHand();
|
||||
return inventory.getEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
||||
inventory.setItemInMainHand(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInOffHand() {
|
||||
return inventory.getItemInOffHand();
|
||||
return inventory.getEquipment(EquipmentSlot.OFF_HAND, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
||||
inventory.setItemInOffHand(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.OFF_HAND, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getHelmet() {
|
||||
return inventory.getHelmet();
|
||||
return inventory.getEquipment(EquipmentSlot.HELMET, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(@NotNull ItemStack itemStack) {
|
||||
inventory.setHelmet(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.HELMET, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getChestplate() {
|
||||
return inventory.getChestplate();
|
||||
return inventory.getEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(@NotNull ItemStack itemStack) {
|
||||
inventory.setChestplate(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getLeggings() {
|
||||
return inventory.getLeggings();
|
||||
return inventory.getEquipment(EquipmentSlot.LEGGINGS, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(@NotNull ItemStack itemStack) {
|
||||
inventory.setLeggings(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.LEGGINGS, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getBoots() {
|
||||
return inventory.getBoots();
|
||||
return inventory.getEquipment(EquipmentSlot.BOOTS, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(@NotNull ItemStack itemStack) {
|
||||
inventory.setBoots(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.BOOTS, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when a player clicks an inventory button.
|
||||
* See <a href="https://wiki.vg/Protocol#Click_Container_Button">wiki.vg</a> for slot number details.
|
||||
*/
|
||||
public class InventoryButtonClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final byte button;
|
||||
|
||||
public InventoryButtonClickEvent(@NotNull Inventory inventory, @NotNull Player player, byte button) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.button = button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked the button in the inventory.
|
||||
*
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory button number that the player clicked. This is different from inventory slots.
|
||||
* @return the button clicked by the player
|
||||
*/
|
||||
public byte getButton() {
|
||||
return button;
|
||||
}
|
||||
}
|
|
@ -1,89 +1,95 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Called after {@link InventoryPreClickEvent}, this event cannot be cancelled and items related to the click
|
||||
* are already moved.
|
||||
* Called after {@link InventoryPreClickEvent} and before {@link InventoryPostClickEvent}.
|
||||
*/
|
||||
public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final PlayerInventory playerInventory;
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final int slot;
|
||||
private final ClickType clickType;
|
||||
private final ItemStack clickedItem;
|
||||
private final ItemStack cursorItem;
|
||||
private final Click.Info info;
|
||||
private List<Click.Change> changes;
|
||||
|
||||
public InventoryClickEvent(@Nullable Inventory inventory, @NotNull Player player,
|
||||
int slot, @NotNull ClickType clickType,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory,
|
||||
@NotNull Player player, @NotNull Click.Info info, @NotNull List<Click.Change> changes) {
|
||||
this.playerInventory = playerInventory;
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.slot = slot;
|
||||
this.clickType = clickType;
|
||||
this.clickedItem = clicked;
|
||||
this.cursorItem = cursor;
|
||||
this.info = info;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked in the inventory.
|
||||
* Gets the player who is trying to click on the inventory.
|
||||
*
|
||||
* @return the player who clicked in the inventory
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked slot number.
|
||||
* Gets the info about the click that occurred. This is enough to fully describe the click.
|
||||
*
|
||||
* @return the clicked slot number
|
||||
* @return the click info
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click type.
|
||||
* Gets the changes that will occur as a result of this click.
|
||||
*
|
||||
* @return the click type
|
||||
* @return the changes
|
||||
*/
|
||||
@NotNull
|
||||
public ClickType getClickType() {
|
||||
return clickType;
|
||||
public @NotNull List<Click.Change> getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked item.
|
||||
* Updates the changes that will occur as a result of this click.
|
||||
*
|
||||
* @return the clicked item
|
||||
* @param changes the new results
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getClickedItem() {
|
||||
return clickedItem;
|
||||
public void setChanges(@NotNull List<Click.Change> changes) {
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item in the player cursor.
|
||||
* Gets the player inventory that was involved with the click.
|
||||
*
|
||||
* @return the cursor item
|
||||
* @return the player inventory
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
public @NotNull PlayerInventory getPlayerInventory() {
|
||||
return playerInventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private Inventory newInventory;
|
||||
private @Nullable Inventory newInventory;
|
||||
|
||||
public InventoryCloseEvent(@Nullable Inventory inventory, @NotNull Player player) {
|
||||
public InventoryCloseEvent(@NotNull Inventory inventory, @NotNull Player player) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
}
|
||||
|
@ -36,8 +36,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
*
|
||||
* @return the new inventory to open, null if there isn't any
|
||||
*/
|
||||
@Nullable
|
||||
public Inventory getNewInventory() {
|
||||
public @Nullable Inventory getNewInventory() {
|
||||
return newInventory;
|
||||
}
|
||||
|
||||
|
@ -51,7 +50,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,14 @@ package net.minestom.server.event.inventory;
|
|||
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.RecursiveEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked.
|
||||
* Called when a slot was changed in an inventory.
|
||||
* This event cannot be cancelled and items related to the change are already moved.
|
||||
*
|
||||
* @see PlayerInventoryItemChangeEvent
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent {
|
||||
|
||||
private final Inventory inventory;
|
||||
|
@ -22,7 +17,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent
|
|||
private final ItemStack previousItem;
|
||||
private final ItemStack newItem;
|
||||
|
||||
public InventoryItemChangeEvent(@Nullable Inventory inventory, int slot,
|
||||
public InventoryItemChangeEvent(@NotNull Inventory inventory, int slot,
|
||||
@NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
|
||||
this.inventory = inventory;
|
||||
this.slot = slot;
|
||||
|
@ -58,7 +53,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import net.minestom.server.event.trait.InventoryEvent;
|
|||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when a player open an {@link Inventory}.
|
||||
|
@ -15,12 +14,12 @@ import org.jetbrains.annotations.Nullable;
|
|||
*/
|
||||
public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private Inventory inventory;
|
||||
private final Player player;
|
||||
private Inventory inventory;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryOpenEvent(@Nullable Inventory inventory, @NotNull Player player) {
|
||||
public InventoryOpenEvent(@NotNull Inventory inventory, @NotNull Player player) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
}
|
||||
|
@ -36,13 +35,12 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory to open, this could have been change by the {@link #setInventory(Inventory)}.
|
||||
* Gets the inventory to open.
|
||||
*
|
||||
* @return the inventory to open, null to just close the current inventory if any
|
||||
* @return the inventory to open
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
|
@ -53,7 +51,7 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
|
|||
*
|
||||
* @param inventory the inventory to open
|
||||
*/
|
||||
public void setInventory(@Nullable Inventory inventory) {
|
||||
public void setInventory(@NotNull Inventory inventory) {
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Called after {@link InventoryClickEvent}, this event cannot be cancelled and items related to the click
|
||||
* are already moved.
|
||||
*/
|
||||
public class InventoryPostClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
|
||||
private final Player player;
|
||||
private final Inventory inventory;
|
||||
private final Click.Info info;
|
||||
private final List<Click.Change> changes;
|
||||
|
||||
public InventoryPostClickEvent(@NotNull Player player, @NotNull Inventory inventory, @NotNull Click.Info info, @NotNull List<Click.Change> changes) {
|
||||
this.player = player;
|
||||
this.inventory = inventory;
|
||||
this.info = info;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked in the inventory.
|
||||
*
|
||||
* @return the player who clicked in the inventory
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the info about the click that was already processed.
|
||||
*
|
||||
* @return the click info
|
||||
*/
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the changes that occurred as a result of this click.
|
||||
*
|
||||
* @return the changes
|
||||
*/
|
||||
public @NotNull List<Click.Change> getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
|
@ -5,35 +5,28 @@ import net.minestom.server.event.trait.CancellableEvent;
|
|||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called before {@link InventoryClickEvent}, used to potentially cancel the click.
|
||||
*/
|
||||
public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final PlayerInventory playerInventory;
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final int slot;
|
||||
private final ClickType clickType;
|
||||
private ItemStack clickedItem;
|
||||
private ItemStack cursorItem;
|
||||
private Click.Info info;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryPreClickEvent(@Nullable Inventory inventory,
|
||||
@NotNull Player player,
|
||||
int slot, @NotNull ClickType clickType,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
public InventoryPreClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory,
|
||||
@NotNull Player player, @NotNull Click.Info info) {
|
||||
this.playerInventory = playerInventory;
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.slot = slot;
|
||||
this.clickType = clickType;
|
||||
this.clickedItem = clicked;
|
||||
this.cursorItem = cursor;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,66 +34,41 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
|
|||
*
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked slot number.
|
||||
* Gets the info about the click that occurred. This is enough to fully describe the click.
|
||||
*
|
||||
* @return the clicked slot number
|
||||
* @return the click info
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click type.
|
||||
* Updates the information about the click that occurred. This completely overrides the previous click, but it may
|
||||
* require the inventory to be updated.
|
||||
*
|
||||
* @return the click type
|
||||
* @param info the new click info
|
||||
*/
|
||||
@NotNull
|
||||
public ClickType getClickType() {
|
||||
return clickType;
|
||||
public void setClickInfo(@NotNull Click.Info info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item who have been clicked.
|
||||
* Gets the player inventory that was involved with the click.
|
||||
*
|
||||
* @return the clicked item
|
||||
* @return the player inventory
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getClickedItem() {
|
||||
return clickedItem;
|
||||
public @NotNull PlayerInventory getPlayerInventory() {
|
||||
return playerInventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the clicked item.
|
||||
*
|
||||
* @param clickedItem the clicked item
|
||||
*/
|
||||
public void setClickedItem(@NotNull ItemStack clickedItem) {
|
||||
this.clickedItem = clickedItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item who was in the player cursor.
|
||||
*
|
||||
* @return the cursor item
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the cursor item.
|
||||
*
|
||||
* @param cursorItem the cursor item
|
||||
*/
|
||||
public void setCursorItem(@NotNull ItemStack cursorItem) {
|
||||
this.cursorItem = cursorItem;
|
||||
@Override
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,9 +80,4 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
|
|||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked on a {@link PlayerInventory}.
|
||||
* This event cannot be cancelled and items related to the change are already moved.
|
||||
* <p>
|
||||
* When this event is being called, {@link InventoryItemChangeEvent} listeners will also be triggered, so you can
|
||||
* listen only for an ancestor event and check whether it is an instance of that class.
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
public class PlayerInventoryItemChangeEvent extends InventoryItemChangeEvent implements PlayerInstanceEvent {
|
||||
|
||||
private final Player player;
|
||||
|
||||
public PlayerInventoryItemChangeEvent(@NotNull Player player, int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
|
||||
super(null, slot, previousItem, newItem);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when a player is trying to swap his main and off hand item.
|
||||
*/
|
||||
public class PlayerSwapItemEvent implements PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final Player player;
|
||||
private ItemStack mainHandItem;
|
||||
private ItemStack offHandItem;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public PlayerSwapItemEvent(@NotNull Player player, @NotNull ItemStack mainHandItem, @NotNull ItemStack offHandItem) {
|
||||
this.player = player;
|
||||
this.mainHandItem = mainHandItem;
|
||||
this.offHandItem = offHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item which will be in player main hand after the event.
|
||||
*
|
||||
* @return the item in main hand
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getMainHandItem() {
|
||||
return mainHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the item which will be in the player main hand.
|
||||
*
|
||||
* @param mainHandItem the main hand item
|
||||
*/
|
||||
public void setMainHandItem(@NotNull ItemStack mainHandItem) {
|
||||
this.mainHandItem = mainHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item which will be in player off hand after the event.
|
||||
*
|
||||
* @return the item in off hand
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getOffHandItem() {
|
||||
return offHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the item which will be in the player off hand.
|
||||
*
|
||||
* @param offHandItem the off hand item
|
||||
*/
|
||||
public void setOffHandItem(@NotNull ItemStack offHandItem) {
|
||||
this.offHandItem = offHandItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package net.minestom.server.event.trait;
|
|||
|
||||
import net.minestom.server.event.Event;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents any event inside an {@link Inventory}.
|
||||
|
@ -12,7 +12,7 @@ public interface InventoryEvent extends Event {
|
|||
/**
|
||||
* Gets the inventory.
|
||||
*
|
||||
* @return the inventory, null if this is a player's inventory
|
||||
* @return the inventory (may be a player inventory)
|
||||
*/
|
||||
@Nullable Inventory getInventory();
|
||||
@NotNull Inventory getInventory();
|
||||
}
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.inventory.InventoryItemChangeEvent;
|
||||
import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent;
|
||||
import net.minestom.server.inventory.click.InventoryClickProcessor;
|
||||
import net.minestom.server.inventory.condition.InventoryCondition;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.tag.TagHandler;
|
||||
import net.minestom.server.tag.Taggable;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Represents an inventory where items can be modified/retrieved.
|
||||
*/
|
||||
public sealed abstract class AbstractInventory implements InventoryClickHandler, Taggable
|
||||
permits Inventory, PlayerInventory {
|
||||
|
||||
private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class);
|
||||
|
||||
private final int size;
|
||||
protected final ItemStack[] itemStacks;
|
||||
|
||||
// list of conditions/callbacks assigned to this inventory
|
||||
protected final List<InventoryCondition> inventoryConditions = new CopyOnWriteArrayList<>();
|
||||
// the click processor which process all the clicks in the inventory
|
||||
protected final InventoryClickProcessor clickProcessor = new InventoryClickProcessor();
|
||||
|
||||
private final TagHandler tagHandler = TagHandler.newHandler();
|
||||
|
||||
protected AbstractInventory(int size) {
|
||||
this.size = size;
|
||||
this.itemStacks = new ItemStack[getSize()];
|
||||
Arrays.fill(itemStacks, ItemStack.AIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s).
|
||||
*
|
||||
* @param slot the slot to set the item
|
||||
* @param itemStack the item to set
|
||||
*/
|
||||
public synchronized void setItemStack(int slot, @NotNull ItemStack itemStack) {
|
||||
Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()),
|
||||
"Inventory does not have the slot " + slot);
|
||||
safeItemInsert(slot, itemStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts safely an item into the inventory.
|
||||
* <p>
|
||||
* This will update the slot for all viewers and warn the inventory that
|
||||
* the window items packet is not up-to-date.
|
||||
*
|
||||
* @param slot the internal slot id
|
||||
* @param itemStack the item to insert (use air instead of null)
|
||||
* @throws IllegalArgumentException if the slot {@code slot} does not exist
|
||||
*/
|
||||
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
|
||||
ItemStack previous;
|
||||
synchronized (this) {
|
||||
Check.argCondition(
|
||||
!MathUtils.isBetween(slot, 0, getSize()),
|
||||
"The slot {0} does not exist in this inventory",
|
||||
slot
|
||||
);
|
||||
previous = itemStacks[slot];
|
||||
if (itemStack.equals(previous)) return; // Avoid sending updates if the item has not changed
|
||||
UNSAFE_itemInsert(slot, itemStack, sendPacket);
|
||||
}
|
||||
if (this instanceof PlayerInventory inv) {
|
||||
EventDispatcher.call(new PlayerInventoryItemChangeEvent(inv.player, slot, previous, itemStack));
|
||||
} else if (this instanceof Inventory inv) {
|
||||
EventDispatcher.call(new InventoryItemChangeEvent(inv, slot, previous, itemStack));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack) {
|
||||
safeItemInsert(slot, itemStack, true);
|
||||
}
|
||||
|
||||
protected abstract void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket);
|
||||
|
||||
public synchronized <T> @NotNull T processItemStack(@NotNull ItemStack itemStack,
|
||||
@NotNull TransactionType type,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
return option.fill(type, this, itemStack);
|
||||
}
|
||||
|
||||
public synchronized <T> @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionType type,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
List<T> result = new ArrayList<>(itemStacks.size());
|
||||
itemStacks.forEach(itemStack -> {
|
||||
T transactionResult = processItemStack(itemStack, type, option);
|
||||
result.add(transactionResult);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link ItemStack} to the inventory and sends relevant update to the viewer(s).
|
||||
*
|
||||
* @param itemStack the item to add
|
||||
* @param option the transaction option
|
||||
* @return true if the item has been successfully added, false otherwise
|
||||
*/
|
||||
public <T> @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
return processItemStack(itemStack, TransactionType.ADD, option);
|
||||
}
|
||||
|
||||
public boolean addItemStack(@NotNull ItemStack itemStack) {
|
||||
return addItemStack(itemStack, TransactionOption.ALL_OR_NOTHING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s).
|
||||
*
|
||||
* @param itemStacks items to add
|
||||
* @param option the transaction option
|
||||
* @return the operation results
|
||||
*/
|
||||
public <T> @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
return processItemStacks(itemStacks, TransactionType.ADD, option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an {@link ItemStack} from the inventory and sends relevant update to the viewer(s).
|
||||
*
|
||||
* @param itemStack the item to take
|
||||
* @return true if the item has been successfully fully taken, false otherwise
|
||||
*/
|
||||
public <T> @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
return processItemStack(itemStack, TransactionType.TAKE, option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s).
|
||||
*
|
||||
* @param itemStacks items to take
|
||||
* @return the operation results
|
||||
*/
|
||||
public <T> @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
return processItemStacks(itemStacks, TransactionType.TAKE, option);
|
||||
}
|
||||
|
||||
public synchronized void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) {
|
||||
var currentItem = getItemStack(slot);
|
||||
setItemStack(slot, operator.apply(currentItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the inventory and send relevant update to the viewer(s).
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
// Clear the item array
|
||||
for (int i = 0; i < size; i++) {
|
||||
safeItemInsert(i, ItemStack.AIR, false);
|
||||
}
|
||||
// Send the cleared inventory to viewers
|
||||
update();
|
||||
}
|
||||
|
||||
public abstract void update();
|
||||
|
||||
/**
|
||||
* Gets the {@link ItemStack} at the specified slot.
|
||||
*
|
||||
* @param slot the slot to check
|
||||
* @return the item in the slot {@code slot}
|
||||
*/
|
||||
public @NotNull ItemStack getItemStack(int slot) {
|
||||
return (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the {@link ItemStack} in the inventory.
|
||||
* <p>
|
||||
* Be aware that the returned array does not need to be the original one,
|
||||
* meaning that modifying it directly may not work.
|
||||
*
|
||||
* @return an array containing all the inventory's items
|
||||
*/
|
||||
public @NotNull ItemStack[] getItemStacks() {
|
||||
return itemStacks.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the inventory.
|
||||
*
|
||||
* @return the inventory's size
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the "inner inventory" (which includes only "usable" slots).
|
||||
*
|
||||
* @return inner inventory's size
|
||||
*/
|
||||
public int getInnerSize() {
|
||||
return getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the {@link InventoryCondition} of this inventory.
|
||||
*
|
||||
* @return a modifiable {@link List} containing all the inventory conditions
|
||||
*/
|
||||
public @NotNull List<@NotNull InventoryCondition> getInventoryConditions() {
|
||||
return inventoryConditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new {@link InventoryCondition} to this inventory.
|
||||
*
|
||||
* @param inventoryCondition the inventory condition to add
|
||||
*/
|
||||
public void addInventoryCondition(@NotNull InventoryCondition inventoryCondition) {
|
||||
this.inventoryConditions.add(inventoryCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places all the items of {@code itemStacks} into the internal array.
|
||||
*
|
||||
* @param itemStacks the array to copy the content from
|
||||
* @throws IllegalArgumentException if the size of the array is not equal to {@link #getSize()}
|
||||
* @throws NullPointerException if {@code itemStacks} contains one null element or more
|
||||
*/
|
||||
public void copyContents(@NotNull ItemStack[] itemStacks) {
|
||||
Check.argCondition(itemStacks.length != getSize(),
|
||||
"The size of the array has to be of the same size as the inventory: " + getSize());
|
||||
|
||||
for (int i = 0; i < itemStacks.length; i++) {
|
||||
final ItemStack itemStack = itemStacks[i];
|
||||
Check.notNull(itemStack, "The item array cannot contain any null element!");
|
||||
setItemStack(i, itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TagHandler tagHandler() {
|
||||
return tagHandler;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
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;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Represents an inventory which can be viewed by a collection of {@link Player}.
|
||||
* <p>
|
||||
* You can create one with {@link ContainerInventory#ContainerInventory(InventoryType, String)} or by making your own subclass.
|
||||
* It can then be opened using {@link Player#openInventory(Inventory)}.
|
||||
*/
|
||||
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
|
||||
* @return the click result, or null if the click did not occur
|
||||
*/
|
||||
public static @Nullable List<Click.Change> 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);
|
||||
EventDispatcher.call(preClickEvent);
|
||||
if (!preClickEvent.isCancelled()) {
|
||||
final Click.Info newInfo = preClickEvent.getClickInfo();
|
||||
Click.Getter getter = new Click.Getter(inventory::getItemStack, playerInventory::getItemStack, playerInventory.getCursorItem(), inventory.getSize());
|
||||
final List<Click.Change> changes = processor.apply(newInfo, getter);
|
||||
|
||||
InventoryClickEvent clickEvent = new InventoryClickEvent(playerInventory, inventory, player, newInfo, changes);
|
||||
EventDispatcher.call(clickEvent);
|
||||
|
||||
if (!clickEvent.isCancelled()) {
|
||||
final List<Click.Change> newChanges = clickEvent.getChanges();
|
||||
|
||||
apply(newChanges, player, inventory);
|
||||
|
||||
EventDispatcher.call(new InventoryPostClickEvent(player, inventory, newInfo, newChanges));
|
||||
|
||||
if (!info.equals(newInfo) || !changes.equals(newChanges)) {
|
||||
inventory.update(player);
|
||||
if (inventory != playerInventory) {
|
||||
playerInventory.update(player);
|
||||
}
|
||||
}
|
||||
|
||||
return newChanges;
|
||||
}
|
||||
}
|
||||
|
||||
inventory.update(player);
|
||||
if (inventory != playerInventory) {
|
||||
playerInventory.update(player);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void apply(@NotNull List<Click.Change> changes, @NotNull Player player, @NotNull Inventory inventory) {
|
||||
PlayerInventory playerInventory = player.getInventory();
|
||||
|
||||
for (var change : changes) {
|
||||
switch (change) {
|
||||
case Click.Change.Container(int slot, ItemStack item) -> {
|
||||
if (slot < inventory.getSize()) {
|
||||
inventory.setItemStack(slot, item);
|
||||
} else {
|
||||
int converted = PlayerInventoryUtils.protocolToMinestom(slot, inventory.getSize());
|
||||
playerInventory.setItemStack(converted, item);
|
||||
}
|
||||
}
|
||||
case Click.Change.Player(int slot, ItemStack item) -> playerInventory.setItemStack(slot, item);
|
||||
case Click.Change.Cursor(ItemStack item) -> playerInventory.setCursorItem(item);
|
||||
case Click.Change.DropFromPlayer(ItemStack item) -> player.dropItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||
|
||||
private final byte id;
|
||||
private final InventoryType inventoryType;
|
||||
private Component title;
|
||||
|
||||
public ContainerInventory(@NotNull InventoryType inventoryType, @NotNull Component title) {
|
||||
super(inventoryType.getSize());
|
||||
this.id = generateId();
|
||||
this.inventoryType = inventoryType;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public ContainerInventory(@NotNull InventoryType inventoryType, @NotNull String title) {
|
||||
this(inventoryType, Component.text(title));
|
||||
}
|
||||
|
||||
private static byte generateId() {
|
||||
return (byte) ID_COUNTER.updateAndGet(i -> i + 1 >= 128 ? 1 : i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory type of this inventory.
|
||||
*
|
||||
* @return the inventory type
|
||||
*/
|
||||
public @NotNull InventoryType getInventoryType() {
|
||||
return inventoryType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory title of this inventory.
|
||||
*
|
||||
* @return the inventory title
|
||||
*/
|
||||
public @NotNull Component getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the inventory title of this inventory.
|
||||
*
|
||||
* @param title the new inventory title
|
||||
*/
|
||||
public void setTitle(@NotNull Component title) {
|
||||
this.title = title;
|
||||
|
||||
// Reopen and update this inventory with the new title
|
||||
sendPacketToViewers(new OpenWindowPacket(getWindowId(), getInventoryType().getWindowType(), title));
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<Click.Change> 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
if (!this.viewers.add(player)) return false;
|
||||
|
||||
player.sendPacket(new OpenWindowPacket(getWindowId(), inventoryType.getWindowType(), getTitle()));
|
||||
update(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a window property to all viewers.
|
||||
*
|
||||
* @param property the property to send
|
||||
* @param value the value of the property
|
||||
* @see <a href="https://wiki.vg/Protocol#Set_Container_Property">https://wiki.vg/Protocol#Set_Container_Property</a>
|
||||
*/
|
||||
protected void sendProperty(@NotNull InventoryProperty property, short value) {
|
||||
sendPacketToViewers(new WindowPropertyPacket(getWindowId(), property.getProperty(), value));
|
||||
}
|
||||
}
|
|
@ -163,10 +163,19 @@ public interface EquipmentHandler {
|
|||
* @param slot the slot of the equipment
|
||||
*/
|
||||
default void syncEquipment(@NotNull EquipmentSlot slot) {
|
||||
syncEquipment(slot, getEquipment(slot));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a specific equipment to viewers.
|
||||
*
|
||||
* @param slot the slot of the equipment
|
||||
* @param itemStack the item to be sent for the slot
|
||||
*/
|
||||
default void syncEquipment(@NotNull EquipmentSlot slot, @NotNull ItemStack itemStack) {
|
||||
Check.stateCondition(!(this instanceof Entity), "Only accessible for Entity");
|
||||
|
||||
Entity entity = (Entity) this;
|
||||
final ItemStack itemStack = getEquipment(slot);
|
||||
entity.sendPacketToViewers(new EntityEquipmentPacket(entity.getEntityId(), Map.of(slot, itemStack)));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,394 +1,145 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.Viewable;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.inventory.click.InventoryClickResult;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.OpenWindowPacket;
|
||||
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
||||
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
|
||||
import net.minestom.server.network.packet.server.play.WindowPropertyPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import net.minestom.server.tag.Taggable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Represents an inventory which can be viewed by a collection of {@link Player}.
|
||||
* <p>
|
||||
* You can create one with {@link Inventory#Inventory(InventoryType, String)} or by making your own subclass.
|
||||
* It can then be opened using {@link Player#openInventory(Inventory)}.
|
||||
* Represents a generic inventory that can be interacted with.
|
||||
*/
|
||||
public non-sealed class Inventory extends AbstractInventory implements Viewable {
|
||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||
|
||||
// the id of this inventory
|
||||
private final byte id;
|
||||
// the type of this inventory
|
||||
private final InventoryType inventoryType;
|
||||
// the title of this inventory
|
||||
private Component title;
|
||||
|
||||
private final int offset;
|
||||
|
||||
// the players currently viewing this inventory
|
||||
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
private final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
|
||||
// (player -> cursor item) map, used by the click listeners
|
||||
private final ConcurrentHashMap<Player, ItemStack> cursorPlayersItem = new ConcurrentHashMap<>();
|
||||
|
||||
public Inventory(@NotNull InventoryType inventoryType, @NotNull Component title) {
|
||||
super(inventoryType.getSize());
|
||||
this.id = generateId();
|
||||
this.inventoryType = inventoryType;
|
||||
this.title = title;
|
||||
|
||||
this.offset = getSize();
|
||||
}
|
||||
|
||||
public Inventory(@NotNull InventoryType inventoryType, @NotNull String title) {
|
||||
this(inventoryType, Component.text(title));
|
||||
}
|
||||
|
||||
private static byte generateId() {
|
||||
return (byte) ID_COUNTER.updateAndGet(i -> i + 1 >= 128 ? 1 : i + 1);
|
||||
}
|
||||
public sealed interface Inventory extends Taggable, Viewable permits InventoryImpl {
|
||||
|
||||
/**
|
||||
* Gets the inventory type.
|
||||
*
|
||||
* @return the inventory type
|
||||
* Gets the size of this inventory. This should be a constant number.
|
||||
* @return the size
|
||||
*/
|
||||
public @NotNull InventoryType getInventoryType() {
|
||||
return inventoryType;
|
||||
}
|
||||
int getSize();
|
||||
|
||||
/**
|
||||
* Gets the inventory title.
|
||||
* Gets the {@link ItemStack} at the specified slot.
|
||||
*
|
||||
* @return the inventory title
|
||||
* @param slot the slot to check
|
||||
* @return the item in the slot {@code slot}
|
||||
*/
|
||||
public @NotNull Component getTitle() {
|
||||
return title;
|
||||
}
|
||||
@NotNull ItemStack getItemStack(int slot);
|
||||
|
||||
/**
|
||||
* Changes the inventory title.
|
||||
* Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s).
|
||||
*
|
||||
* @param title the new inventory title
|
||||
* @param slot the slot to set the item
|
||||
* @param itemStack the item to set
|
||||
*/
|
||||
public void setTitle(@NotNull Component title) {
|
||||
this.title = title;
|
||||
// Re-open the inventory
|
||||
sendPacketToViewers(new OpenWindowPacket(getWindowId(), getInventoryType().getWindowType(), title));
|
||||
// Send inventory items
|
||||
update();
|
||||
}
|
||||
void setItemStack(int slot, @NotNull ItemStack itemStack);
|
||||
|
||||
/**
|
||||
* Gets this window id.
|
||||
* Gets the window ID of this window, as a byte.
|
||||
*
|
||||
* @return the window ID
|
||||
*/
|
||||
byte getWindowId();
|
||||
|
||||
/**
|
||||
* Handles the provided click from the given player, returning the results after it is applied. If the results are
|
||||
* null, this indicates that the click was cancelled or was otherwise not processed.
|
||||
*
|
||||
* @param player the player that clicked
|
||||
* @param info the information about the player's click
|
||||
* @return the results of the click, or null if the click was cancelled or otherwise was not handled
|
||||
*/
|
||||
@Nullable List<Click.Change> handleClick(@NotNull Player player, @NotNull Click.Info info);
|
||||
|
||||
/**
|
||||
* Gets all the {@link ItemStack} in the inventory.
|
||||
* <p>
|
||||
* This is the id that the client will send to identify the affected inventory, mostly used by packets.
|
||||
* Be aware that the returned array does not need to be the original one,
|
||||
* meaning that modifying it directly may not work.
|
||||
*
|
||||
* @return the window id
|
||||
* @return an array containing all the inventory's items
|
||||
*/
|
||||
public byte getWindowId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
this.cursorPlayersItem.clear();
|
||||
super.clear();
|
||||
}
|
||||
@NotNull ItemStack[] getItemStacks();
|
||||
|
||||
/**
|
||||
* Refreshes the inventory for all viewers.
|
||||
* Places all the items of {@code itemStacks} into the internal array.
|
||||
*
|
||||
* @param itemStacks the array to copy the content from
|
||||
* @throws IllegalArgumentException if the size of the array is not equal to {@link #getSize()}
|
||||
* @throws NullPointerException if {@code itemStacks} contains one null element or more
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
this.viewers.forEach(p -> p.sendPacket(createNewWindowItemsPacket(p)));
|
||||
}
|
||||
void copyContents(@NotNull ItemStack[] itemStacks);
|
||||
|
||||
/**
|
||||
* Refreshes the inventory for a specific viewer.
|
||||
* <p>
|
||||
* The player needs to be a viewer, otherwise nothing is sent.
|
||||
*
|
||||
* @param player the player to update the inventory
|
||||
* Clears the inventory and send relevant update to the viewer(s).
|
||||
*/
|
||||
public void update(@NotNull Player player) {
|
||||
if (!isViewer(player)) return;
|
||||
player.sendPacket(createNewWindowItemsPacket(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Player> getViewers() {
|
||||
return unmodifiableViewers;
|
||||
}
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* This will not open the inventory for {@code player}, use {@link Player#openInventory(Inventory)}.
|
||||
*
|
||||
* @param player the viewer to add
|
||||
* @return true if the player has successfully been added
|
||||
* Updates the inventory for all viewers.
|
||||
*/
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
final boolean result = this.viewers.add(player);
|
||||
update(player);
|
||||
return result;
|
||||
}
|
||||
void update();
|
||||
|
||||
/**
|
||||
* This will not close the inventory for {@code player}, use {@link Player#closeInventory()}.
|
||||
* Updates the inventory for a specific viewer.
|
||||
*
|
||||
* @param player the viewer to remove
|
||||
* @return true if the player has successfully been removed
|
||||
* @param player the player to update the inventory for
|
||||
*/
|
||||
@Override
|
||||
public boolean removeViewer(@NotNull Player player) {
|
||||
final boolean result = this.viewers.remove(player);
|
||||
setCursorItem(player, ItemStack.AIR);
|
||||
this.clickProcessor.clearCache(player);
|
||||
return result;
|
||||
}
|
||||
void update(@NotNull Player player);
|
||||
|
||||
/**
|
||||
* Gets the cursor item of a viewer.
|
||||
* Replaces the item in the slot according to the operator.
|
||||
*
|
||||
* @param player the player to get the cursor item from
|
||||
* @return the player cursor item, air item if the player is not a viewer
|
||||
* @param slot the slot to replace
|
||||
* @param operator the operator to apply to the slot
|
||||
*/
|
||||
public @NotNull ItemStack getCursorItem(@NotNull Player player) {
|
||||
return cursorPlayersItem.getOrDefault(player, ItemStack.AIR);
|
||||
}
|
||||
void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator);
|
||||
|
||||
<T> @NotNull T processItemStack(@NotNull ItemStack itemStack,
|
||||
@NotNull TransactionType type,
|
||||
@NotNull TransactionOption<T> option);
|
||||
|
||||
<T> @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionType type, @NotNull TransactionOption<T> option);
|
||||
|
||||
/**
|
||||
* Changes the cursor item of a viewer,
|
||||
* does nothing if <code>player</code> is not a viewer.
|
||||
* Adds an {@link ItemStack} to the inventory and sends relevant update to the viewer(s).
|
||||
*
|
||||
* @param player the player to change the cursor item
|
||||
* @param cursorItem the new player cursor item
|
||||
* @param itemStack the item to add
|
||||
* @param option the transaction option
|
||||
* @return true if the item has been successfully added, false otherwise
|
||||
*/
|
||||
public void setCursorItem(@NotNull Player player, @NotNull ItemStack cursorItem) {
|
||||
final ItemStack currentCursorItem = cursorPlayersItem.getOrDefault(player, ItemStack.AIR);
|
||||
if (!currentCursorItem.equals(cursorItem)) {
|
||||
player.sendPacket(SetSlotPacket.createCursorPacket(cursorItem));
|
||||
}
|
||||
if (!cursorItem.isAir()) {
|
||||
this.cursorPlayersItem.put(player, cursorItem);
|
||||
} else {
|
||||
this.cursorPlayersItem.remove(player);
|
||||
}
|
||||
}
|
||||
<T> @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option);
|
||||
|
||||
@Override
|
||||
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
|
||||
itemStacks[slot] = itemStack;
|
||||
if (sendPacket) sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack));
|
||||
}
|
||||
|
||||
private @NotNull WindowItemsPacket createNewWindowItemsPacket(Player player) {
|
||||
return new WindowItemsPacket(getWindowId(), 0, List.of(getItemStacks()), cursorPlayersItem.getOrDefault(player, ItemStack.AIR));
|
||||
}
|
||||
boolean addItemStack(@NotNull ItemStack itemStack);
|
||||
|
||||
/**
|
||||
* Sends a window property to all viewers.
|
||||
* Adds {@link ItemStack}s to the inventory and sends relevant updates to the viewer(s).
|
||||
*
|
||||
* @param property the property to send
|
||||
* @param value the value of the property
|
||||
* @see <a href="https://wiki.vg/Protocol#Window_Property">https://wiki.vg/Protocol#Window_Property</a>
|
||||
* @param itemStacks items to add
|
||||
* @param option the transaction option
|
||||
* @return the operation results
|
||||
*/
|
||||
protected void sendProperty(@NotNull InventoryProperty property, short value) {
|
||||
sendPacketToViewers(new WindowPropertyPacket(getWindowId(), property.getProperty(), value));
|
||||
}
|
||||
<T> @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, @NotNull TransactionOption<T> option);
|
||||
|
||||
@Override
|
||||
public boolean leftClick(@NotNull Player player, int slot) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final ItemStack cursor = getCursorItem(player);
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.leftClick(player,
|
||||
isInWindow ? this : playerInventory, clickSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
if (isInWindow) {
|
||||
setItemStack(slot, clickResult.getClicked());
|
||||
} else {
|
||||
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
|
||||
}
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
callClickEvent(player, isInWindow ? this : null, slot, ClickType.LEFT_CLICK, clicked, cursor);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Takes an {@link ItemStack} from the inventory and sends relevant update to the viewer(s).
|
||||
*
|
||||
* @param itemStack the item to take
|
||||
* @return true if the item has been successfully fully taken, false otherwise
|
||||
*/
|
||||
<T> @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option);
|
||||
|
||||
@Override
|
||||
public boolean rightClick(@NotNull Player player, int slot) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final ItemStack cursor = getCursorItem(player);
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.rightClick(player,
|
||||
isInWindow ? this : playerInventory, clickSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
if (isInWindow) {
|
||||
setItemStack(slot, clickResult.getClicked());
|
||||
} else {
|
||||
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
|
||||
}
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
callClickEvent(player, isInWindow ? this : null, slot, ClickType.RIGHT_CLICK, clicked, cursor);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Takes {@link ItemStack}s from the inventory and sends relevant updates to the viewer(s).
|
||||
*
|
||||
* @param itemStacks items to take
|
||||
* @return the operation results
|
||||
*/
|
||||
<T> @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks, @NotNull TransactionOption<T> option);
|
||||
|
||||
@Override
|
||||
public boolean shiftClick(@NotNull Player player, int slot) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
|
||||
final ItemStack cursor = getCursorItem(player); // Isn't used in the algorithm
|
||||
final InventoryClickResult clickResult = clickProcessor.shiftClick(
|
||||
isInWindow ? this : playerInventory,
|
||||
isInWindow ? playerInventory : this,
|
||||
0, isInWindow ? playerInventory.getInnerSize() : getInnerSize(), 1,
|
||||
player, clickSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
if (isInWindow) {
|
||||
setItemStack(slot, clickResult.getClicked());
|
||||
} else {
|
||||
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
|
||||
}
|
||||
updateAll(player); // FIXME: currently not properly client-predicted
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeHeld(@NotNull Player player, int slot, int key) {
|
||||
final int convertedKey = key == 40 ? PlayerInventoryUtils.OFFHAND_SLOT : key;
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot);
|
||||
final ItemStack heldItem = playerInventory.getItemStack(convertedKey);
|
||||
final InventoryClickResult clickResult = clickProcessor.changeHeld(player,
|
||||
isInWindow ? this : playerInventory, clickSlot, convertedKey, clicked, heldItem);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
if (isInWindow) {
|
||||
setItemStack(slot, clickResult.getClicked());
|
||||
} else {
|
||||
playerInventory.setItemStack(clickSlot, clickResult.getClicked());
|
||||
}
|
||||
playerInventory.setItemStack(convertedKey, clickResult.getCursor());
|
||||
callClickEvent(player, isInWindow ? this : null, slot, ClickType.CHANGE_HELD, clicked, getCursorItem(player));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean middleClick(@NotNull Player player, int slot) {
|
||||
// TODO
|
||||
update(player);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drop(@NotNull Player player, boolean all, int slot, int button) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final boolean outsideDrop = slot == -999;
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = outsideDrop ?
|
||||
ItemStack.AIR : (isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot));
|
||||
final ItemStack cursor = getCursorItem(player);
|
||||
final InventoryClickResult clickResult = clickProcessor.drop(player,
|
||||
isInWindow ? this : playerInventory, all, clickSlot, button, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
final ItemStack resultClicked = clickResult.getClicked();
|
||||
if (!outsideDrop && resultClicked != null) {
|
||||
if (isInWindow) {
|
||||
setItemStack(slot, resultClicked);
|
||||
} else {
|
||||
playerInventory.setItemStack(clickSlot, resultClicked);
|
||||
}
|
||||
}
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dragging(@NotNull Player player, int slot, int button) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = slot != -999 ?
|
||||
(isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)) :
|
||||
ItemStack.AIR;
|
||||
final ItemStack cursor = getCursorItem(player);
|
||||
final InventoryClickResult clickResult = clickProcessor.dragging(player,
|
||||
slot != -999 ? (isInWindow ? this : playerInventory) : null,
|
||||
clickSlot, button,
|
||||
clicked, cursor);
|
||||
if (clickResult == null || clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
updateAll(player); // FIXME: currently not properly client-predicted
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doubleClick(@NotNull Player player, int slot) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
final boolean isInWindow = isClickInWindow(slot);
|
||||
final int clickSlot = isInWindow ? slot : PlayerInventoryUtils.convertSlot(slot, offset);
|
||||
final ItemStack clicked = slot != -999 ?
|
||||
(isInWindow ? getItemStack(slot) : playerInventory.getItemStack(clickSlot)) :
|
||||
ItemStack.AIR;
|
||||
final ItemStack cursor = getCursorItem(player);
|
||||
final InventoryClickResult clickResult = clickProcessor.doubleClick(isInWindow ? this : playerInventory,
|
||||
this, player, clickSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
}
|
||||
this.cursorPlayersItem.put(player, clickResult.getCursor());
|
||||
updateAll(player); // FIXME: currently not properly client-predicted
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isClickInWindow(int slot) {
|
||||
return slot < getSize();
|
||||
}
|
||||
|
||||
private void updateAll(Player player) {
|
||||
player.getInventory().update();
|
||||
update(player);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.inventory.InventoryClickEvent;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents an inventory which can receive click input.
|
||||
* All methods returning boolean returns true if the action is successful, false otherwise.
|
||||
* <p>
|
||||
* See https://wiki.vg/Protocol#Click_Window for more information.
|
||||
*/
|
||||
public sealed interface InventoryClickHandler permits AbstractInventory {
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} left click in the inventory. Can also be to drop the cursor item
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param slot the slot number
|
||||
* @return true if the click hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean leftClick(@NotNull Player player, int slot);
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} right click in the inventory. Can also be to drop the cursor item
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param slot the slot number
|
||||
* @return true if the click hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean rightClick(@NotNull Player player, int slot);
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} shift click in the inventory
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param slot the slot number
|
||||
* @return true if the click hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean shiftClick(@NotNull Player player, int slot); // shift + left/right click have the same behavior
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} held click in the inventory
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param slot the slot number
|
||||
* @param key the held slot (0-8) pressed
|
||||
* @return true if the click hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean changeHeld(@NotNull Player player, int slot, int key);
|
||||
|
||||
boolean middleClick(@NotNull Player player, int slot);
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} press the drop button
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param all
|
||||
* @param slot the slot number
|
||||
* @param button -999 if clicking outside, normal if he is not
|
||||
* @return true if the drop hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean drop(@NotNull Player player, boolean all, int slot, int button);
|
||||
|
||||
boolean dragging(@NotNull Player player, int slot, int button);
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} double click in the inventory
|
||||
*
|
||||
* @param player the player who clicked
|
||||
* @param slot the slot number
|
||||
* @return true if the click hasn't been cancelled, false otherwise
|
||||
*/
|
||||
boolean doubleClick(@NotNull Player player, int slot);
|
||||
|
||||
default void callClickEvent(@NotNull Player player, Inventory inventory, int slot,
|
||||
@NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
EventDispatcher.call(new InventoryClickEvent(inventory, player, slot, clickType, clicked, cursor));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
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.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.CloseWindowPacket;
|
||||
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
||||
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
|
||||
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 java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
sealed abstract class InventoryImpl implements Inventory permits ContainerInventory, PlayerInventory {
|
||||
|
||||
private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class);
|
||||
|
||||
private final int size;
|
||||
protected final ItemStack[] itemStacks;
|
||||
|
||||
private final TagHandler tagHandler = TagHandler.newHandler();
|
||||
|
||||
protected final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
// the players currently viewing this inventory
|
||||
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
protected final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
|
||||
|
||||
protected InventoryImpl(int size) {
|
||||
this.size = size;
|
||||
this.itemStacks = new ItemStack[size];
|
||||
Arrays.fill(itemStacks, ItemStack.AIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TagHandler tagHandler() {
|
||||
return tagHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Player> getViewers() {
|
||||
return unmodifiableViewers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
if (!this.viewers.add(player)) return false;
|
||||
|
||||
update(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeViewer(@NotNull Player player) {
|
||||
if (!this.viewers.remove(player)) return false;
|
||||
|
||||
ItemStack cursorItem = player.getInventory().getCursorItem();
|
||||
player.getInventory().setCursorItem(ItemStack.AIR);
|
||||
|
||||
if (!cursorItem.isAir()) {
|
||||
// Drop the item if it can not be added back to the inventory
|
||||
if (!player.getInventory().addItemStack(cursorItem)) {
|
||||
player.dropItem(cursorItem);
|
||||
}
|
||||
}
|
||||
|
||||
player.clickPreprocessor().clearCache();
|
||||
if (player.skipClosePacket()) {
|
||||
player.UNSAFE_changeSkipClosePacket(false);
|
||||
} else {
|
||||
player.sendPacket(new CloseWindowPacket(getWindowId()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the provided slot for this inventory's viewers.
|
||||
*
|
||||
* @param slot the slot to update
|
||||
* @param itemStack the item treated as in the slot
|
||||
*/
|
||||
protected void updateSlot(int slot, @NotNull ItemStack itemStack) {
|
||||
sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
this.viewers.forEach(this::update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull Player player) {
|
||||
player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(itemStacks), player.getInventory().getCursorItem()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemStack(int slot) {
|
||||
return (ItemStack) ITEM_UPDATER.getVolatile(itemStacks, slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack[] getItemStacks() {
|
||||
return itemStacks.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyContents(@NotNull ItemStack[] itemStacks) {
|
||||
Check.argCondition(itemStacks.length != getSize(),
|
||||
"The size of the array has to be of the same size as the inventory: " + getSize());
|
||||
|
||||
for (int i = 0; i < itemStacks.length; i++) {
|
||||
final ItemStack itemStack = itemStacks[i];
|
||||
Check.notNull(itemStack, "The item array cannot contain any null element!");
|
||||
setItemStack(i, itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemStack(int slot, @NotNull ItemStack itemStack) {
|
||||
Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()),
|
||||
"Inventory does not have the slot " + slot);
|
||||
safeItemInsert(slot, itemStack);
|
||||
}
|
||||
|
||||
protected final void safeItemInsert(int slot, @NotNull ItemStack itemStack) {
|
||||
safeItemInsert(slot, itemStack, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts safely an item into the inventory.
|
||||
* <p>
|
||||
* This will update the slot for all viewers and warn the inventory that
|
||||
* the window items packet is not up-to-date.
|
||||
*
|
||||
* @param slot the internal slot id
|
||||
* @param itemStack the item to insert (use air instead of null)
|
||||
* @throws IllegalArgumentException if the slot {@code slot} does not exist
|
||||
*/
|
||||
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
|
||||
|
||||
UNSAFE_itemInsert(slot, itemStack);
|
||||
if (sendPacket) updateSlot(slot, itemStack);
|
||||
|
||||
EventDispatcher.call(new InventoryItemChangeEvent(this, slot, previous, itemStack));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) {
|
||||
itemStacks[slot] = itemStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceItemStack(int slot, @NotNull UnaryOperator<@NotNull ItemStack> operator) {
|
||||
lock.lock();
|
||||
try {
|
||||
var currentItem = getItemStack(slot);
|
||||
setItemStack(slot, operator.apply(currentItem));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
// Send the cleared inventory to viewers
|
||||
update();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull T processItemStack(@NotNull ItemStack itemStack,
|
||||
@NotNull TransactionType type,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
lock.lock();
|
||||
try {
|
||||
return option.fill(type, this, itemStack);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull List<@NotNull T> processItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionType type,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
List<T> result = new ArrayList<>(itemStacks.size());
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
for (ItemStack item : itemStacks) {
|
||||
result.add(processItemStack(item, type, option));
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
List<Integer> slots = IntStream.range(0, getSize()).boxed().toList();
|
||||
return processItemStack(itemStack, TransactionType.add(slots, slots), option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addItemStack(@NotNull ItemStack itemStack) {
|
||||
return addItemStack(itemStack, TransactionOption.ALL_OR_NOTHING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull List<@NotNull T> addItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
List<T> result = new ArrayList<>(itemStacks.size());
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
for (ItemStack item : itemStacks) {
|
||||
result.add(addItemStack(item, option));
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
return processItemStack(itemStack, TransactionType.take(IntStream.range(0, getSize()).boxed().toList()), option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull List<@NotNull T> takeItemStacks(@NotNull List<@NotNull ItemStack> itemStacks,
|
||||
@NotNull TransactionOption<T> option) {
|
||||
List<T> result = new ArrayList<>(itemStacks.size());
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
for (ItemStack item : itemStacks) {
|
||||
result.add(takeItemStack(item, option));
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
/**
|
||||
* Represents a type of {@link Inventory}
|
||||
* Represents a type of {@link ContainerInventory}
|
||||
*/
|
||||
public enum InventoryType {
|
||||
|
||||
|
|
|
@ -4,327 +4,179 @@ import net.minestom.server.entity.EquipmentSlot;
|
|||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.item.EntityEquipEvent;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.inventory.click.InventoryClickResult;
|
||||
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.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.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
||||
|
||||
/**
|
||||
* Represents the inventory of a {@link Player}, retrieved with {@link Player#getInventory()}.
|
||||
*/
|
||||
public non-sealed class PlayerInventory extends AbstractInventory implements EquipmentHandler {
|
||||
public static final int INVENTORY_SIZE = 46;
|
||||
public static final int INNER_INVENTORY_SIZE = 36;
|
||||
public non-sealed class PlayerInventory extends InventoryImpl {
|
||||
private static int getSlotIndex(@NotNull EquipmentSlot slot, int heldSlot) {
|
||||
return switch (slot) {
|
||||
case HELMET, CHESTPLATE, LEGGINGS, BOOTS -> slot.armorSlot();
|
||||
case OFF_HAND -> OFF_HAND_SLOT;
|
||||
case MAIN_HAND -> heldSlot;
|
||||
};
|
||||
}
|
||||
|
||||
private static @Nullable EquipmentSlot fromSlotIndex(int slot, int heldSlot) {
|
||||
return switch (slot) {
|
||||
case HELMET_SLOT -> EquipmentSlot.HELMET;
|
||||
case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE;
|
||||
case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS;
|
||||
case BOOTS_SLOT -> EquipmentSlot.BOOTS;
|
||||
case OFF_HAND_SLOT -> EquipmentSlot.OFF_HAND;
|
||||
default -> slot == heldSlot ? EquipmentSlot.MAIN_HAND : null;
|
||||
};
|
||||
}
|
||||
|
||||
private static final List<Integer> FILL_ADD_SLOTS = IntStream.concat(
|
||||
IntStream.of(OFF_HAND_SLOT),
|
||||
IntStream.range(0, 36)
|
||||
).boxed().toList();
|
||||
|
||||
private static final List<Integer> AIR_ADD_SLOTS = IntStream.range(0, 36).boxed().toList();
|
||||
|
||||
private static final List<Integer> TAKE_SLOTS = Stream.of(
|
||||
IntStream.range(0, 36),
|
||||
IntStream.of(OFF_HAND_SLOT),
|
||||
IntStream.range(36, 45)
|
||||
).flatMapToInt(i -> i).boxed().toList();
|
||||
|
||||
protected final Player player;
|
||||
private ItemStack cursorItem = ItemStack.AIR;
|
||||
|
||||
public PlayerInventory(@NotNull Player player) {
|
||||
public PlayerInventory() {
|
||||
super(INVENTORY_SIZE);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
cursorItem = ItemStack.AIR;
|
||||
super.clear();
|
||||
// Update equipments
|
||||
this.player.sendPacketToViewersAndSelf(player.getEquipmentsPacket());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInnerSize() {
|
||||
return INNER_INVENTORY_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInMainHand() {
|
||||
return getItemStack(player.getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(player.getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInOffHand() {
|
||||
return getItemStack(OFFHAND_SLOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(OFFHAND_SLOT, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getHelmet() {
|
||||
return getItemStack(HELMET_SLOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(HELMET_SLOT, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getChestplate() {
|
||||
return getItemStack(CHESTPLATE_SLOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(CHESTPLATE_SLOT, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getLeggings() {
|
||||
return getItemStack(LEGGINGS_SLOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(LEGGINGS_SLOT, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getBoots() {
|
||||
return getItemStack(BOOTS_SLOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(@NotNull ItemStack itemStack) {
|
||||
safeItemInsert(BOOTS_SLOT, itemStack);
|
||||
public byte getWindowId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the player inventory by sending a {@link WindowItemsPacket} containing all.
|
||||
* the inventory items
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
this.player.sendPacket(createWindowItemsPacket());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item in player cursor.
|
||||
* Gets the cursor item of this inventory
|
||||
*
|
||||
* @return the cursor item
|
||||
* @return the cursor item that is shared between all viewers
|
||||
*/
|
||||
public @NotNull ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the player cursor item.
|
||||
* Sets the cursor item for all viewers of this inventory.
|
||||
*
|
||||
* @param cursorItem the new cursor item
|
||||
* @param cursorItem the new item (will not update if same as current)
|
||||
*/
|
||||
public void setCursorItem(@NotNull ItemStack cursorItem) {
|
||||
setCursorItem(cursorItem, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cursor item for all viewers of this inventory.
|
||||
*
|
||||
* @param cursorItem the new item (will not update if same as current)
|
||||
* @param sendPacket whether to send a packet
|
||||
*/
|
||||
public void setCursorItem(@NotNull ItemStack cursorItem, boolean sendPacket) {
|
||||
if (this.cursorItem.equals(cursorItem)) return;
|
||||
this.cursorItem = cursorItem;
|
||||
final SetSlotPacket setSlotPacket = SetSlotPacket.createCursorPacket(cursorItem);
|
||||
this.player.sendPacket(setSlotPacket);
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
this.cursorItem = cursorItem;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if (sendPacket) sendPacketToViewers(SetSlotPacket.createCursorPacket(cursorItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack, boolean sendPacket) {
|
||||
final EquipmentSlot equipmentSlot = switch (slot) {
|
||||
case HELMET_SLOT -> EquipmentSlot.HELMET;
|
||||
case CHESTPLATE_SLOT -> EquipmentSlot.CHESTPLATE;
|
||||
case LEGGINGS_SLOT -> EquipmentSlot.LEGGINGS;
|
||||
case BOOTS_SLOT -> EquipmentSlot.BOOTS;
|
||||
case OFFHAND_SLOT -> EquipmentSlot.OFF_HAND;
|
||||
default -> slot == player.getHeldSlot() ? EquipmentSlot.MAIN_HAND : null;
|
||||
};
|
||||
if (equipmentSlot != null) {
|
||||
public void updateSlot(int slot, @NotNull ItemStack itemStack) {
|
||||
SetSlotPacket defaultPacket = new SetSlotPacket(getWindowId(), 0, (short) PlayerInventoryUtils.minestomToProtocol(slot), itemStack);
|
||||
for (Player player : getViewers()) {
|
||||
Inventory open = player.getOpenInventory();
|
||||
if (open != null && slot >= 0 && slot < INNER_SIZE) {
|
||||
player.sendPacket(new SetSlotPacket(open.getWindowId(), 0, (short) PlayerInventoryUtils.minestomToProtocol(slot, open.getSize()), itemStack));
|
||||
} else if (open == null || slot == OFF_HAND_SLOT) {
|
||||
player.sendPacket(defaultPacket);
|
||||
}
|
||||
|
||||
var equipmentSlot = fromSlotIndex(slot, player.getHeldSlot());
|
||||
if (equipmentSlot == null) continue;
|
||||
|
||||
player.syncEquipment(equipmentSlot, itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull Player player) {
|
||||
ItemStack[] local = getItemStacks();
|
||||
ItemStack[] mapped = new ItemStack[getSize()];
|
||||
for (int slot = 0; slot < getSize(); slot++) {
|
||||
mapped[PlayerInventoryUtils.minestomToProtocol(slot)] = local[slot];
|
||||
}
|
||||
player.sendPacket(new WindowItemsPacket(getWindowId(), 0, List.of(mapped), getCursorItem()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) {
|
||||
for (var player : getViewers()) {
|
||||
final EquipmentSlot equipmentSlot = fromSlotIndex(slot, player.getHeldSlot());
|
||||
if (equipmentSlot == null) continue;
|
||||
|
||||
EntityEquipEvent entityEquipEvent = new EntityEquipEvent(player, itemStack, equipmentSlot);
|
||||
EventDispatcher.call(entityEquipEvent);
|
||||
itemStack = entityEquipEvent.getEquippedItem();
|
||||
}
|
||||
this.itemStacks[slot] = itemStack;
|
||||
|
||||
if (sendPacket) {
|
||||
// Sync equipment
|
||||
if (equipmentSlot != null) this.player.syncEquipment(equipmentSlot);
|
||||
// Refresh slot
|
||||
sendSlotRefresh((short) convertToPacketSlot(slot), itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes an inventory slot.
|
||||
*
|
||||
* @param slot the packet slot,
|
||||
* see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)}
|
||||
* @param itemStack the item stack in the slot
|
||||
*/
|
||||
protected void sendSlotRefresh(short slot, ItemStack itemStack) {
|
||||
var openInventory = player.getOpenInventory();
|
||||
if (openInventory != null && slot >= OFFSET && slot < OFFSET + INNER_INVENTORY_SIZE) {
|
||||
this.player.sendPacket(new SetSlotPacket(openInventory.getWindowId(), 0, (short) (slot + openInventory.getSize() - OFFSET), itemStack));
|
||||
} else if (openInventory == null || slot == OFFHAND_SLOT) {
|
||||
this.player.sendPacket(new SetSlotPacket((byte) 0, 0, slot, itemStack));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link WindowItemsPacket} with all the items in the inventory.
|
||||
*
|
||||
* @return a {@link WindowItemsPacket} with inventory items
|
||||
*/
|
||||
private WindowItemsPacket createWindowItemsPacket() {
|
||||
ItemStack[] convertedSlots = new ItemStack[INVENTORY_SIZE];
|
||||
for (int i = 0; i < itemStacks.length; i++) {
|
||||
final int slot = convertToPacketSlot(i);
|
||||
convertedSlots[slot] = itemStacks[i];
|
||||
}
|
||||
return new WindowItemsPacket((byte) 0, 0, List.of(convertedSlots), cursorItem);
|
||||
super.UNSAFE_itemInsert(slot, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean leftClick(@NotNull Player player, int slot) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = getItemStack(convertedSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.leftClick(player, this, convertedSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
public void clear() {
|
||||
lock.lock();
|
||||
try {
|
||||
super.clear();
|
||||
for (Player player : getViewers()) {
|
||||
player.sendPacketToViewersAndSelf(player.getEquipmentsPacket());
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
setItemStack(convertedSlot, clickResult.getClicked());
|
||||
setCursorItem(clickResult.getCursor());
|
||||
callClickEvent(player, null, convertedSlot, ClickType.LEFT_CLICK, clicked, cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rightClick(@NotNull Player player, int slot) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = getItemStack(convertedSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.rightClick(player, this, convertedSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
setItemStack(convertedSlot, clickResult.getClicked());
|
||||
setCursorItem(clickResult.getCursor());
|
||||
callClickEvent(player, null, convertedSlot, ClickType.RIGHT_CLICK, clicked, cursor);
|
||||
return true;
|
||||
public @Nullable List<Click.Change> handleClick(@NotNull Player player, @NotNull Click.Info info) {
|
||||
return ContainerInventory.handleClick(this, player, info, ClickProcessors.PLAYER_PROCESSOR);
|
||||
}
|
||||
|
||||
public @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot, int heldSlot) {
|
||||
return getItemStack(getSlotIndex(slot, heldSlot));
|
||||
}
|
||||
|
||||
public void setEquipment(@NotNull EquipmentSlot slot, int heldSlot, @NotNull ItemStack newValue) {
|
||||
setItemStack(getSlotIndex(slot, heldSlot), newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean middleClick(@NotNull Player player, int slot) {
|
||||
// TODO
|
||||
update();
|
||||
return false;
|
||||
public <T> @NotNull T addItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
return processItemStack(itemStack, TransactionType.add(FILL_ADD_SLOTS, AIR_ADD_SLOTS), option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drop(@NotNull Player player, boolean all, int slot, int button) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final boolean outsideDrop = slot == -999;
|
||||
final ItemStack clicked = outsideDrop ? ItemStack.AIR : getItemStack(convertedSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.drop(player, this,
|
||||
all, convertedSlot, button, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
final ItemStack resultClicked = clickResult.getClicked();
|
||||
if (resultClicked != null && !outsideDrop) {
|
||||
setItemStack(convertedSlot, resultClicked);
|
||||
}
|
||||
setCursorItem(clickResult.getCursor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shiftClick(@NotNull Player player, int slot) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = getItemStack(convertedSlot);
|
||||
final boolean hotBarClick = convertSlot(slot, OFFSET) < 9;
|
||||
final int start = hotBarClick ? 9 : 0;
|
||||
final int end = hotBarClick ? getSize() - 9 : 8;
|
||||
final InventoryClickResult clickResult = clickProcessor.shiftClick(
|
||||
this, this,
|
||||
start, end, 1,
|
||||
player, convertedSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
setItemStack(convertedSlot, clickResult.getClicked());
|
||||
setCursorItem(clickResult.getCursor());
|
||||
update(); // FIXME: currently not properly client-predicted
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeHeld(@NotNull Player player, int slot, int key) {
|
||||
final int convertedKey = key == 40 ? OFFHAND_SLOT : key;
|
||||
final ItemStack cursorItem = getCursorItem();
|
||||
if (!cursorItem.isAir()) return false;
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack heldItem = getItemStack(convertedKey);
|
||||
final ItemStack clicked = getItemStack(convertedSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.changeHeld(player, this, convertedSlot, convertedKey, clicked, heldItem);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
setItemStack(convertedSlot, clickResult.getClicked());
|
||||
setItemStack(convertedKey, clickResult.getCursor());
|
||||
callClickEvent(player, null, convertedSlot, ClickType.CHANGE_HELD, clicked, cursorItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dragging(@NotNull Player player, int slot, int button) {
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = slot != -999 ? getItemStackFromPacketSlot(slot) : ItemStack.AIR;
|
||||
final InventoryClickResult clickResult = clickProcessor.dragging(player, this,
|
||||
convertPlayerInventorySlot(slot, OFFSET), button, clicked, cursor);
|
||||
if (clickResult == null || clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
setCursorItem(clickResult.getCursor());
|
||||
update(); // FIXME: currently not properly client-predicted
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doubleClick(@NotNull Player player, int slot) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = getItemStack(convertedSlot);
|
||||
final InventoryClickResult clickResult = clickProcessor.doubleClick(this, this, player, convertedSlot, clicked, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
setCursorItem(clickResult.getCursor());
|
||||
update(); // FIXME: currently not properly client-predicted
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setItemStackFromPacketSlot(int slot, @NotNull ItemStack itemStack) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
setItemStack(convertedSlot, itemStack);
|
||||
}
|
||||
|
||||
private ItemStack getItemStackFromPacketSlot(int slot) {
|
||||
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
|
||||
return itemStacks[convertedSlot];
|
||||
public <T> @NotNull T takeItemStack(@NotNull ItemStack itemStack, @NotNull TransactionOption<T> option) {
|
||||
return processItemStack(itemStack, TransactionType.take(TAKE_SLOTS), option);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.StackingRule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* A transaction operator is a simpler operation that takes two items and returns two items.
|
||||
* <br>
|
||||
* This allows a significant amount of logic reuse, since many operations are just the {@link #flip(TransactionOperator) flipped}
|
||||
* version of others.
|
||||
*/
|
||||
public interface TransactionOperator extends UnaryOperator<TransactionOperator.Entry> {
|
||||
|
||||
/**
|
||||
* Creates a new operator that filters the given one using the provided predicate
|
||||
*/
|
||||
static @NotNull TransactionOperator filter(@NotNull TransactionOperator operator, @NotNull BiPredicate<ItemStack, ItemStack> predicate) {
|
||||
return (entry) -> {
|
||||
final ItemStack left = entry.left();
|
||||
final ItemStack right = entry.right();
|
||||
return predicate.test(left, right) ? operator.apply(entry) : null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new operator that flips the left and right before providing it to the given operator.
|
||||
*/
|
||||
static @NotNull TransactionOperator flip(@NotNull TransactionOperator operator) {
|
||||
return (entry) -> {
|
||||
final Entry pair = operator.apply(entry.reverse());
|
||||
return pair != null ? new Entry(pair.right(), pair.left()) : null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides operators that try to stack up to the provided number of items to the left.
|
||||
*/
|
||||
static @NotNull TransactionOperator stackLeftN(int count) {
|
||||
return (entry) -> {
|
||||
final ItemStack left = entry.left();
|
||||
final ItemStack right = entry.right();
|
||||
final StackingRule rule = StackingRule.get();
|
||||
|
||||
// Quick exit if the right is air (nothing can be transferred anyway)
|
||||
// If the left is air then we know it can be transferred, but it can also be transferred if they're stackable
|
||||
// and left isn't full, even if left isn't air.
|
||||
if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int leftAmount = left.isAir() ? 0 : rule.getAmount(left);
|
||||
int rightAmount = rule.getAmount(right);
|
||||
|
||||
int addedAmount = Math.min(Math.min(rightAmount, count), rule.getMaxSize(left) - leftAmount);
|
||||
|
||||
if (addedAmount == 0) return null;
|
||||
|
||||
return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stacks as many items to the left as possible, including if the left is an air item.<br>
|
||||
* This will not swap the items if they are of different types.
|
||||
*/
|
||||
TransactionOperator STACK_LEFT = (entry) -> {
|
||||
final ItemStack left = entry.left();
|
||||
final ItemStack right = entry.right();
|
||||
final StackingRule rule = StackingRule.get();
|
||||
|
||||
// Quick exit if the right is air (nothing can be transferred anyway)
|
||||
// If the left is air then we know it can be transferred, but it can also be transferred if they're stackable
|
||||
// and left isn't full, even if left isn't air.
|
||||
if (right.isAir() || (!left.isAir() && !(rule.canBeStacked(left, right) && rule.getAmount(left) < rule.getMaxSize(left)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int leftAmount = left.isAir() ? 0 : rule.getAmount(left);
|
||||
int rightAmount = rule.getAmount(right);
|
||||
|
||||
int addedAmount = Math.min(rightAmount, rule.getMaxSize(left) - leftAmount);
|
||||
|
||||
return new Entry(rule.apply(left.isAir() ? right : left, leftAmount + addedAmount), rule.apply(right, rightAmount - addedAmount));
|
||||
};
|
||||
|
||||
/**
|
||||
* Stacks as many items to the right as possible. This is the flipped version of {@link #STACK_LEFT}.
|
||||
*/
|
||||
TransactionOperator STACK_RIGHT = flip(STACK_LEFT);
|
||||
|
||||
/**
|
||||
* Takes as many items as possible from both stacks, if the given items are stackable.
|
||||
* This is a symmetric operation.
|
||||
*/
|
||||
TransactionOperator TAKE = (entry) -> {
|
||||
final ItemStack left = entry.left();
|
||||
final ItemStack right = entry.right();
|
||||
final StackingRule rule = StackingRule.get();
|
||||
if (right.isAir() || !rule.canBeStacked(left, right)) {
|
||||
return null;
|
||||
}
|
||||
final int leftAmount = rule.getAmount(left);
|
||||
final int rightAmount = rule.getAmount(right);
|
||||
final int subtracted = Math.min(leftAmount, rightAmount);
|
||||
return new Entry(rule.apply(left, leftAmount - subtracted), rule.apply(right, rightAmount - subtracted));
|
||||
};
|
||||
|
||||
default Entry apply(@NotNull ItemStack left, @NotNull ItemStack right) {
|
||||
return apply(new Entry(left, right));
|
||||
}
|
||||
|
||||
record Entry(@NotNull ItemStack left, @NotNull ItemStack right) {
|
||||
public Entry reverse() {
|
||||
return new Entry(right, left);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,24 +9,22 @@ import java.util.Map;
|
|||
public interface TransactionOption<T> {
|
||||
|
||||
/**
|
||||
* Place as much as the item as possible.
|
||||
* <p>
|
||||
* The remaining, can be air.
|
||||
* Performs as much of the operation as is possible.
|
||||
* Returns the remaining item in the operation (can be air).
|
||||
*/
|
||||
TransactionOption<ItemStack> ALL = (inventory, result, itemChangesMap) -> {
|
||||
itemChangesMap.forEach(inventory::safeItemInsert);
|
||||
itemChangesMap.forEach(inventory::setItemStack);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Only place the item if can be fully added.
|
||||
* <p>
|
||||
* Returns true if the item has been added, false if nothing changed.
|
||||
* Performs the operation atomically (only if the operation resulted in air), returning whether the operation
|
||||
* was performed.
|
||||
*/
|
||||
TransactionOption<Boolean> ALL_OR_NOTHING = (inventory, result, itemChangesMap) -> {
|
||||
if (result.isAir()) {
|
||||
// Item can be fully placed inside the inventory, do so
|
||||
itemChangesMap.forEach(inventory::safeItemInsert);
|
||||
itemChangesMap.forEach(inventory::setItemStack);
|
||||
return true;
|
||||
} else {
|
||||
// Inventory cannot accept the item fully
|
||||
|
@ -35,20 +33,15 @@ public interface TransactionOption<T> {
|
|||
};
|
||||
|
||||
/**
|
||||
* Loop through the inventory items without changing anything.
|
||||
* <p>
|
||||
* Returns true if the item can be fully added, false otherwise.
|
||||
* Discards the result of the operation, returning whether the operation could have finished.
|
||||
*/
|
||||
TransactionOption<Boolean> DRY_RUN = (inventory, result, itemChangesMap) -> result.isAir();
|
||||
|
||||
@NotNull T fill(@NotNull AbstractInventory inventory,
|
||||
@NotNull ItemStack result,
|
||||
@NotNull Map<@NotNull Integer, @NotNull ItemStack> itemChangesMap);
|
||||
@NotNull
|
||||
T fill(@NotNull Inventory inventory, @NotNull ItemStack result, @NotNull Map<Integer, ItemStack> itemChangesMap);
|
||||
|
||||
default @NotNull T fill(@NotNull TransactionType type,
|
||||
@NotNull AbstractInventory inventory,
|
||||
@NotNull ItemStack itemStack) {
|
||||
var pair = type.process(inventory, itemStack);
|
||||
return fill(inventory, pair.left(), pair.right());
|
||||
default @NotNull T fill(@NotNull TransactionType type, @NotNull Inventory inventory, @NotNull ItemStack itemStack) {
|
||||
final TransactionType.Entry result = type.apply(itemStack, inventory::getItemStack);
|
||||
return fill(inventory, result.remaining(), result.changes());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +1,101 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.StackingRule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Represents a type of transaction that you can apply to an {@link AbstractInventory}.
|
||||
* Represents a type of transaction that you can apply to an {@link Inventory}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TransactionType {
|
||||
public interface TransactionType extends BiFunction<@NotNull ItemStack, @NotNull IntFunction<ItemStack>, TransactionType.@NotNull Entry> {
|
||||
|
||||
/**
|
||||
* Applies a transaction operator to a given list of slots, turning it into a TransactionType.
|
||||
*/
|
||||
static @NotNull TransactionType general(@NotNull TransactionOperator operator, @NotNull List<Integer> slots) {
|
||||
return (item, getter) -> {
|
||||
Int2ObjectMap<ItemStack> map = new Int2ObjectArrayMap<>();
|
||||
for (int slot : slots) {
|
||||
final ItemStack slotItem = getter.apply(slot);
|
||||
final TransactionOperator.Entry result = operator.apply(slotItem, item);
|
||||
if (result == null) continue;
|
||||
|
||||
map.put(slot, result.left());
|
||||
item = result.right();
|
||||
}
|
||||
return new Entry(item, map);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins two transaction types consecutively.
|
||||
* This will use the same getter in both cases, so ensure that any potential overlap between the transaction types
|
||||
* will not result in unexpected behaviour (e.g. item duping).
|
||||
*/
|
||||
static @NotNull TransactionType join(@NotNull TransactionType first, @NotNull TransactionType second) {
|
||||
return (item, getter) -> {
|
||||
// Calculate results
|
||||
final Entry f = first.apply(item, getter);
|
||||
final Entry s = second.apply(f.remaining(), getter);
|
||||
// Join results
|
||||
Map<Integer, ItemStack> map = new Int2ObjectArrayMap<>();
|
||||
map.putAll(f.changes());
|
||||
map.putAll(s.changes());
|
||||
return new Entry(s.remaining(), map);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the inventory.
|
||||
* Can either take an air slot or be stacked.
|
||||
*
|
||||
* @param fill the list of slots that will be added to if they already have some of the item in it
|
||||
* @param air the list of slots that will be added to if they have air (may be different from {@code fill}).
|
||||
*/
|
||||
TransactionType ADD = (inventory, itemStack, slotPredicate, start, end, step) -> {
|
||||
Int2ObjectMap<ItemStack> itemChangesMap = new Int2ObjectOpenHashMap<>();
|
||||
final StackingRule stackingRule = StackingRule.get();
|
||||
// Check filled slot (not air)
|
||||
for (int i = start; i < end; i += step) {
|
||||
ItemStack inventoryItem = inventory.getItemStack(i);
|
||||
if (inventoryItem.isAir()) {
|
||||
continue;
|
||||
}
|
||||
if (stackingRule.canBeStacked(itemStack, inventoryItem)) {
|
||||
final int itemAmount = stackingRule.getAmount(inventoryItem);
|
||||
final int maxSize = stackingRule.getMaxSize(inventoryItem);
|
||||
if (itemAmount >= maxSize) continue;
|
||||
if (!slotPredicate.test(i, inventoryItem)) {
|
||||
// Cancelled transaction
|
||||
continue;
|
||||
}
|
||||
|
||||
final int itemStackAmount = stackingRule.getAmount(itemStack);
|
||||
final int totalAmount = itemStackAmount + itemAmount;
|
||||
if (!stackingRule.canApply(itemStack, totalAmount)) {
|
||||
// Slot cannot accept the whole item, reduce amount to 'itemStack'
|
||||
itemChangesMap.put(i, stackingRule.apply(inventoryItem, maxSize));
|
||||
itemStack = stackingRule.apply(itemStack, totalAmount - maxSize);
|
||||
} else {
|
||||
// Slot can accept the whole item
|
||||
itemChangesMap.put(i, stackingRule.apply(inventoryItem, totalAmount));
|
||||
itemStack = stackingRule.apply(itemStack, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check air slot to fill
|
||||
for (int i = start; i < end; i += step) {
|
||||
ItemStack inventoryItem = inventory.getItemStack(i);
|
||||
if (!inventoryItem.isAir()) continue;
|
||||
if (!slotPredicate.test(i, inventoryItem)) {
|
||||
// Cancelled transaction
|
||||
continue;
|
||||
}
|
||||
// Fill the slot
|
||||
itemChangesMap.put(i, itemStack);
|
||||
itemStack = stackingRule.apply(itemStack, 0);
|
||||
break;
|
||||
}
|
||||
return Pair.of(itemStack, itemChangesMap);
|
||||
};
|
||||
static @NotNull TransactionType add(@NotNull List<Integer> fill, @NotNull List<Integer> air) {
|
||||
final TransactionType first = general(entry -> {
|
||||
final ItemStack slotItem = entry.left();
|
||||
final ItemStack extra = entry.right();
|
||||
return !slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null;
|
||||
}, fill);
|
||||
final TransactionType second = general(entry -> {
|
||||
final ItemStack slotItem = entry.left();
|
||||
final ItemStack extra = entry.right();
|
||||
return slotItem.isAir() ? TransactionOperator.STACK_LEFT.apply(slotItem, extra) : null;
|
||||
}, air);
|
||||
return TransactionType.join(first, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an item from the inventory.
|
||||
* Can either transform items to air or reduce their amount.
|
||||
*
|
||||
* @param takeSlots the ordered list of slots that will be taken from (if possible)
|
||||
*/
|
||||
TransactionType TAKE = (inventory, itemStack, slotPredicate, start, end, step) -> {
|
||||
Int2ObjectMap<ItemStack> itemChangesMap = new Int2ObjectOpenHashMap<>();
|
||||
final StackingRule stackingRule = StackingRule.get();
|
||||
for (int i = start; i < end; i += step) {
|
||||
final ItemStack inventoryItem = inventory.getItemStack(i);
|
||||
if (inventoryItem.isAir()) continue;
|
||||
if (stackingRule.canBeStacked(itemStack, inventoryItem)) {
|
||||
if (!slotPredicate.test(i, inventoryItem)) {
|
||||
// Cancelled transaction
|
||||
continue;
|
||||
}
|
||||
static @NotNull TransactionType take(@NotNull List<Integer> takeSlots) {
|
||||
return general(TransactionOperator.TAKE, takeSlots);
|
||||
}
|
||||
|
||||
final int itemAmount = stackingRule.getAmount(inventoryItem);
|
||||
final int itemStackAmount = stackingRule.getAmount(itemStack);
|
||||
if (itemStackAmount < itemAmount) {
|
||||
itemChangesMap.put(i, stackingRule.apply(inventoryItem, itemAmount - itemStackAmount));
|
||||
itemStack = stackingRule.apply(itemStack, 0);
|
||||
break;
|
||||
}
|
||||
itemChangesMap.put(i, stackingRule.apply(inventoryItem, 0));
|
||||
itemStack = stackingRule.apply(itemStack, itemStackAmount - itemAmount);
|
||||
if (stackingRule.getAmount(itemStack) == 0) {
|
||||
itemStack = stackingRule.apply(itemStack, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Processes the provided item into the given inventory.
|
||||
*
|
||||
* @param itemStack the item to process
|
||||
* @param inventory the inventory function
|
||||
* @return the remaining portion of the processed item, as well as the changes
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
Entry apply(@NotNull ItemStack itemStack, @NotNull IntFunction<ItemStack> inventory);
|
||||
|
||||
record Entry(@NotNull ItemStack remaining, @NotNull Map<@NotNull Integer, @NotNull ItemStack> changes) {
|
||||
public Entry {
|
||||
changes = Map.copyOf(changes);
|
||||
}
|
||||
return Pair.of(itemStack, itemChangesMap);
|
||||
};
|
||||
|
||||
@NotNull Pair<ItemStack, Map<Integer, ItemStack>> process(@NotNull AbstractInventory inventory,
|
||||
@NotNull ItemStack itemStack,
|
||||
@NotNull SlotPredicate slotPredicate,
|
||||
int start, int end, int step);
|
||||
|
||||
default @NotNull Pair<ItemStack, Map<Integer, ItemStack>> process(@NotNull AbstractInventory inventory,
|
||||
@NotNull ItemStack itemStack,
|
||||
@NotNull SlotPredicate slotPredicate) {
|
||||
return process(inventory, itemStack, slotPredicate, 0, inventory.getInnerSize(), 1);
|
||||
}
|
||||
|
||||
default @NotNull Pair<ItemStack, Map<Integer, ItemStack>> process(@NotNull AbstractInventory inventory,
|
||||
@NotNull ItemStack itemStack) {
|
||||
return process(inventory, itemStack, (slot, itemStack1) -> true);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface SlotPredicate {
|
||||
boolean test(int slot, @NotNull ItemStack itemStack);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public final class Click {
|
||||
|
||||
/**
|
||||
* Contains information about a click. These are equal to the packet slot IDs from <a href="https://wiki.vg/Inventory">the Minecraft protocol.</a>.
|
||||
* The inventory used should be known from context.
|
||||
*/
|
||||
public sealed interface Info {
|
||||
record Left(int slot) implements Info {
|
||||
}
|
||||
|
||||
record Right(int slot) implements Info {
|
||||
}
|
||||
|
||||
record Middle(int slot) implements Info {
|
||||
// Creative only
|
||||
}
|
||||
|
||||
record LeftShift(int slot) implements Info {
|
||||
}
|
||||
|
||||
record RightShift(int slot) implements Info {
|
||||
}
|
||||
|
||||
record Double(int slot) implements Info {
|
||||
}
|
||||
|
||||
record LeftDrag(List<Integer> slots) implements Info {
|
||||
public LeftDrag {
|
||||
slots = List.copyOf(slots);
|
||||
}
|
||||
}
|
||||
|
||||
record RightDrag(List<Integer> slots) implements Info {
|
||||
public RightDrag {
|
||||
slots = List.copyOf(slots);
|
||||
}
|
||||
}
|
||||
|
||||
record MiddleDrag(List<Integer> slots) implements Info {
|
||||
// Creative only
|
||||
public MiddleDrag {
|
||||
slots = List.copyOf(slots);
|
||||
}
|
||||
}
|
||||
|
||||
record LeftDropCursor() implements Info {
|
||||
}
|
||||
|
||||
record RightDropCursor() implements Info {
|
||||
}
|
||||
|
||||
record MiddleDropCursor() implements Info {
|
||||
}
|
||||
|
||||
record DropSlot(int slot, boolean all) implements Info {
|
||||
}
|
||||
|
||||
record HotbarSwap(int hotbarSlot, int clickedSlot) implements Info {
|
||||
}
|
||||
|
||||
record OffhandSwap(int slot) implements Info {
|
||||
}
|
||||
|
||||
record CreativeSetItem(int slot, @NotNull ItemStack item) implements Info {
|
||||
}
|
||||
|
||||
record CreativeDropItem(@NotNull ItemStack item) implements Info {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses click packets, turning them into {@link Info} instances for further processing.
|
||||
*/
|
||||
public static final class Preprocessor {
|
||||
private final Set<Integer> leftDrag = new LinkedHashSet<>();
|
||||
private final Set<Integer> rightDrag = new LinkedHashSet<>();
|
||||
private final Set<Integer> middleDrag = new LinkedHashSet<>();
|
||||
|
||||
public void clearCache() {
|
||||
this.leftDrag.clear();
|
||||
this.rightDrag.clear();
|
||||
this.middleDrag.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the provided click packet, turning it into a {@link Info}.
|
||||
* This will do simple verification of the packet before sending it to {@link #process(ClientClickWindowPacket.ClickType, int, byte)}.
|
||||
*
|
||||
* @param packet the raw click packet
|
||||
* @param isCreative whether the player is in creative mode (used for ignoring some actions)
|
||||
* @param containerSize the size of the open container, or null if the player inventory is open
|
||||
* @return the information about the click, or nothing if there was no immediately usable information
|
||||
*/
|
||||
public @Nullable Click.Info processClick(@NotNull ClientClickWindowPacket packet, boolean isCreative, @Nullable Integer containerSize) {
|
||||
final byte button = packet.button();
|
||||
final boolean requireCreative = switch (packet.clickType()) {
|
||||
case CLONE -> packet.slot() != -999; // Permit middle click dropping
|
||||
case QUICK_CRAFT -> button == 8 || button == 9 || button == 10;
|
||||
default -> false;
|
||||
};
|
||||
if (requireCreative && !isCreative) return null;
|
||||
final int slot = packet.slot() == -999 ? -999 :
|
||||
containerSize == null ? PlayerInventoryUtils.protocolToMinestom(packet.slot()) : packet.slot();
|
||||
final int maxSize = containerSize != null ? containerSize + PlayerInventoryUtils.INNER_SIZE : PlayerInventoryUtils.INVENTORY_SIZE;
|
||||
final boolean valid = slot >= 0 && slot < maxSize;
|
||||
|
||||
if (!valid) {
|
||||
return slot == -999 ? processInvalidSlot(packet.clickType(), button) : null;
|
||||
}
|
||||
|
||||
return process(packet.clickType(), slot, button);
|
||||
}
|
||||
|
||||
private @Nullable Click.Info processInvalidSlot(@NotNull ClientClickWindowPacket.ClickType type, byte button) {
|
||||
return switch (type) {
|
||||
case PICKUP -> {
|
||||
if (button == 0) yield new Info.LeftDropCursor();
|
||||
if (button == 1) yield new Info.RightDropCursor();
|
||||
yield null;
|
||||
}
|
||||
case CLONE -> {
|
||||
if (button == 2) yield new Info.MiddleDropCursor(); // Why Mojang, why?
|
||||
yield null;
|
||||
}
|
||||
case QUICK_CRAFT -> {
|
||||
// Trust me, a switch would not make this cleaner
|
||||
|
||||
if (button == 2) {
|
||||
var list = List.copyOf(leftDrag);
|
||||
leftDrag.clear();
|
||||
yield new Info.LeftDrag(list);
|
||||
} else if (button == 6) {
|
||||
var list = List.copyOf(rightDrag);
|
||||
rightDrag.clear();
|
||||
yield new Info.RightDrag(list);
|
||||
} else if (button == 10) {
|
||||
var list = List.copyOf(middleDrag);
|
||||
middleDrag.clear();
|
||||
yield new Info.MiddleDrag(list);
|
||||
}
|
||||
|
||||
if (button == 0) leftDrag.clear();
|
||||
if (button == 4) rightDrag.clear();
|
||||
if (button == 8) middleDrag.clear();
|
||||
|
||||
yield null;
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a packet into click info.
|
||||
*
|
||||
* @param type the type of the click
|
||||
* @param slot the clicked slot
|
||||
* @param button the sent button
|
||||
* @return the information about the click, or nothing if there was no immediately usable information
|
||||
*/
|
||||
private @Nullable Click.Info process(@NotNull ClientClickWindowPacket.ClickType type, int slot, byte button) {
|
||||
return switch (type) {
|
||||
case PICKUP -> switch (button) {
|
||||
case 0 -> new Info.Left(slot);
|
||||
case 1 -> new Info.Right(slot);
|
||||
default -> null;
|
||||
};
|
||||
case QUICK_MOVE -> button == 0 ? new Info.LeftShift(slot) : new Info.RightShift(slot);
|
||||
case SWAP -> {
|
||||
if (button >= 0 && button < 9) {
|
||||
yield new Info.HotbarSwap(button, slot);
|
||||
} else if (button == 40) {
|
||||
yield new Info.OffhandSwap(slot);
|
||||
} else {
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
case CLONE -> new Info.Middle(slot);
|
||||
case THROW -> new Info.DropSlot(slot, button == 1);
|
||||
case QUICK_CRAFT -> {
|
||||
switch (button) {
|
||||
case 1 -> leftDrag.add(slot);
|
||||
case 5 -> rightDrag.add(slot);
|
||||
case 9 -> middleDrag.add(slot);
|
||||
}
|
||||
yield null;
|
||||
}
|
||||
case PICKUP_ALL -> new Info.Double(slot);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public record Getter(@NotNull IntFunction<ItemStack> main, @NotNull IntFunction<ItemStack> player,
|
||||
@NotNull ItemStack cursor, int mainSize) {
|
||||
public @NotNull ItemStack get(int slot) {
|
||||
if (slot < mainSize()) {
|
||||
return main.apply(slot);
|
||||
} else {
|
||||
return player.apply(PlayerInventoryUtils.protocolToMinestom(slot, mainSize()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual change that occurred to an inventory.
|
||||
*/
|
||||
public sealed interface Change {
|
||||
|
||||
/**
|
||||
* A change to the open container. If the click was in the player inventory, this could indicate a change there,
|
||||
* despite the existence of {@link Player}
|
||||
* @param slot the changed slot
|
||||
* @param item the new item in the slot
|
||||
*/
|
||||
record Container(int slot, @NotNull ItemStack item) implements Change {
|
||||
}
|
||||
|
||||
/**
|
||||
* A change that must have occurred in the player's inventory, even if they have another one open.
|
||||
* @param slot the changed player slot
|
||||
* @param item the new item in the slot
|
||||
*/
|
||||
record Player(int slot, @NotNull ItemStack item) implements Change {
|
||||
}
|
||||
|
||||
/**
|
||||
* A new cursor item. The cursor item is stored in the player's {@link net.minestom.server.inventory.PlayerInventory}.
|
||||
* @param item the new cursor item
|
||||
*/
|
||||
record Cursor(@NotNull ItemStack item) implements Change {
|
||||
}
|
||||
|
||||
/**
|
||||
* An item that was dropped from the player during the click.
|
||||
* @param item the item to drop
|
||||
*/
|
||||
record DropFromPlayer(@NotNull ItemStack item) implements Change {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import net.minestom.server.entity.EquipmentSlot;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.inventory.TransactionOperator;
|
||||
import net.minestom.server.inventory.TransactionType;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.DropFromPlayer;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.inventory.click.Click.Change.Player;
|
||||
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.
|
||||
*/
|
||||
public final class ClickProcessors {
|
||||
private static final @NotNull StackingRule RULE = StackingRule.get();
|
||||
|
||||
public static @NotNull List<Click.Change> leftClick(int slot, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
final ItemStack clickedItem = getter.get(slot);
|
||||
|
||||
final TransactionOperator.Entry pair = TransactionOperator.STACK_LEFT.apply(clickedItem, cursor);
|
||||
if (pair != null) { // Stackable items, combine their counts
|
||||
return List.of(new Container(slot, pair.left()), new Cursor(pair.right()));
|
||||
} else if (!RULE.canBeStacked(cursor, clickedItem)) { // If they're unstackable, switch them
|
||||
return List.of(new Container(slot, cursor), new Cursor(clickedItem));
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> rightClick(int slot, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
final ItemStack clickedItem = getter.get(slot);
|
||||
if (cursor.isAir() && clickedItem.isAir()) return List.of(); // Both are air, no changes
|
||||
|
||||
if (cursor.isAir()) { // Take half (rounded up) of the clicked item
|
||||
int newAmount = (int) Math.ceil(RULE.getAmount(clickedItem) / 2d);
|
||||
final TransactionOperator.Entry cursorSlot = TransactionOperator.stackLeftN(newAmount).apply(cursor, clickedItem);
|
||||
if (cursorSlot == null) return List.of();
|
||||
return List.of(new Container(slot, cursorSlot.right()), new Cursor(cursorSlot.left()));
|
||||
} else if (clickedItem.isAir() || RULE.canBeStacked(clickedItem, cursor)) { // Can add, transfer one over
|
||||
final TransactionOperator.Entry slotCursor = TransactionOperator.stackLeftN(1).apply(clickedItem, cursor);
|
||||
if (slotCursor == null) return List.of();
|
||||
return List.of(new Container(slot, slotCursor.left()), new Cursor(slotCursor.right()));
|
||||
} else { // Two existing of items of different types, so switch
|
||||
return List.of(new Cursor(clickedItem), new Container(slot, cursor));
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> middleClick(int slot, @NotNull Click.Getter getter) {
|
||||
final ItemStack item = getter.get(slot);
|
||||
if (!getter.cursor().isAir() || item.isAir()) return List.of();
|
||||
return List.of(new Cursor(RULE.apply(item, RULE.getMaxSize(item))));
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> shiftClick(int slot, @NotNull List<Integer> slots, @NotNull Click.Getter getter) {
|
||||
final ItemStack clicked = getter.get(slot);
|
||||
|
||||
slots = new ArrayList<>(slots);
|
||||
slots.removeIf(i -> i == slot);
|
||||
|
||||
final TransactionType.Entry result = TransactionType.add(slots, slots).apply(clicked, getter::get);
|
||||
List<Click.Change> changes = new ArrayList<>();
|
||||
result.changes().forEach((slotId, item) -> changes.add(new Container(slotId, item)));
|
||||
|
||||
if (!result.remaining().equals(clicked)) {
|
||||
changes.add(new Container(slot, result.remaining()));
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> doubleClick(@NotNull List<Integer> slots, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
if (cursor.isAir()) return List.of();
|
||||
|
||||
final TransactionType unstacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT,
|
||||
(left, right) -> RULE.getAmount(left) < RULE.getMaxSize(left)), slots);
|
||||
final TransactionType stacked = TransactionType.general(TransactionOperator.filter(TransactionOperator.STACK_RIGHT,
|
||||
(left, right) -> RULE.getAmount(left) == RULE.getMaxSize(left)), slots);
|
||||
|
||||
final TransactionType.Entry result = TransactionType.join(unstacked, stacked).apply(cursor, getter::get);
|
||||
List<Click.Change> changes = new ArrayList<>();
|
||||
result.changes().forEach((slotId, item) -> changes.add(new Container(slotId, item)));
|
||||
|
||||
if (!result.remaining().equals(cursor)) {
|
||||
changes.add(new Cursor(result.remaining()));
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> dragClick(int countPerSlot, @NotNull List<Integer> slots, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
if (cursor.isAir()) return List.of();
|
||||
|
||||
final TransactionType.Entry result = TransactionType.general(TransactionOperator.stackLeftN(countPerSlot), slots).apply(cursor, getter::get);
|
||||
List<Click.Change> changes = new ArrayList<>();
|
||||
result.changes().forEach((slotId, item) -> changes.add(new Container(slotId, item)));
|
||||
|
||||
if (!result.remaining().equals(cursor)) {
|
||||
changes.add(new Cursor(result.remaining()));
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> middleDragClick(@NotNull List<Integer> slots, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
return slots.stream()
|
||||
.filter(slot -> getter.get(slot).isAir())
|
||||
.map(slot -> (Click.Change) new Container(slot, cursor))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> dropFromCursor(int amount, @NotNull Click.Getter getter) {
|
||||
final ItemStack cursor = getter.cursor();
|
||||
if (cursor.isAir()) return List.of(); // Do nothing
|
||||
|
||||
final TransactionOperator.Entry pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, cursor);
|
||||
if (pair == null) return List.of();
|
||||
|
||||
return List.of(new Cursor(pair.right()), new DropFromPlayer(pair.left()));
|
||||
}
|
||||
|
||||
public static @NotNull List<Click.Change> dropFromSlot(int slot, int amount, @NotNull Click.Getter getter) {
|
||||
final ItemStack item = getter.get(slot);
|
||||
if (item.isAir()) return List.of(); // Do nothing
|
||||
|
||||
final TransactionOperator.Entry pair = TransactionOperator.stackLeftN(amount).apply(ItemStack.AIR, item);
|
||||
if (pair == null) return List.of();
|
||||
|
||||
return List.of(new Container(slot, pair.right()), new DropFromPlayer(pair.left()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicks, given a shift click provider and a double click provider.<br>
|
||||
* When shift clicks or double clicks need to be handled, the slots provided from the relevant handler will be
|
||||
* checked in their given order.<br>
|
||||
* For example, double-clicking will collect items of the same type as the cursor; the slots provided by the double
|
||||
* click slot provider will be checked sequentially and used if they have the same type as
|
||||
*
|
||||
* @param shiftClickSlots the shift click slot supplier
|
||||
* @param doubleClickSlots the double click slot supplier
|
||||
*/
|
||||
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.LeftDrag(List<Integer> slots) -> {
|
||||
int cursorAmount = RULE.getAmount(getter.cursor());
|
||||
int amount = (int) Math.floor(cursorAmount / (double) slots.size());
|
||||
yield dragClick(amount, slots, getter);
|
||||
}
|
||||
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.LeftDropCursor() -> dropFromCursor(getter.cursor().amount(), getter);
|
||||
case Click.Info.RightDropCursor() -> dropFromCursor(1, getter);
|
||||
case Click.Info.MiddleDropCursor() -> List.of();
|
||||
case Click.Info.HotbarSwap(int hotbarSlot, int clickedSlot) -> {
|
||||
var hotbarItem = getter.player().apply(hotbarSlot);
|
||||
var selectedItem = getter.get(clickedSlot);
|
||||
if (hotbarItem.equals(selectedItem)) yield List.of();
|
||||
|
||||
yield List.of(new Container(clickedSlot, hotbarItem), new Player(hotbarSlot, selectedItem));
|
||||
}
|
||||
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 List.of();
|
||||
|
||||
yield List.of(new Container(slot, offhandItem), new Player(OFF_HAND_SLOT, selectedItem));
|
||||
}
|
||||
case Click.Info.CreativeSetItem(int slot, ItemStack item) -> List.of(new Container(slot, item));
|
||||
case Click.Info.CreativeDropItem(ItemStack item) -> List.of(new DropFromPlayer(item));
|
||||
};
|
||||
}
|
||||
|
||||
public interface InventoryProcessor extends BiFunction<Click.Info, Click.Getter, List<Click.Change>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* shift clicking an item out of an inventory can only put it in the player's inner inventory slots, and will never
|
||||
* put the item anywhere else in the inventory or the player's inventory.<br>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SlotSuggestor {
|
||||
|
||||
/**
|
||||
* Suggests slots to be used for this operation.
|
||||
*
|
||||
* @param builder the result builder
|
||||
* @param item the item clicked
|
||||
* @param slot the slot of the clicked item
|
||||
* @return the list of slots, in order of priority, to be used for this operation
|
||||
*/
|
||||
@NotNull
|
||||
IntStream get(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot);
|
||||
|
||||
default @NotNull List<Integer> getList(@NotNull Click.Getter builder, @NotNull ItemStack item, int slot) {
|
||||
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) :
|
||||
PlayerInventoryUtils.getInnerShiftClickSlots(size);
|
||||
},
|
||||
(builder, item, slot) -> {
|
||||
final int size = builder.mainSize();
|
||||
return IntStream.concat(
|
||||
IntStream.range(0, size),
|
||||
PlayerInventoryUtils.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 PlayerInventoryUtils.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),
|
||||
PlayerInventoryUtils.getInnerDoubleClickSlots(size)
|
||||
);
|
||||
});
|
||||
|
||||
public static final Map<InventoryType, InventoryProcessor> PROCESSORS_MAP = Map.ofEntries(
|
||||
entry(InventoryType.FURNACE, FURNACE_PROCESSOR)
|
||||
);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
public enum ClickType {
|
||||
|
||||
LEFT_CLICK,
|
||||
RIGHT_CLICK,
|
||||
CHANGE_HELD,
|
||||
|
||||
START_SHIFT_CLICK,
|
||||
SHIFT_CLICK,
|
||||
|
||||
START_LEFT_DRAGGING,
|
||||
START_RIGHT_DRAGGING,
|
||||
|
||||
LEFT_DRAGGING,
|
||||
RIGHT_DRAGGING,
|
||||
|
||||
END_LEFT_DRAGGING,
|
||||
END_RIGHT_DRAGGING,
|
||||
|
||||
START_DOUBLE_CLICK,
|
||||
DOUBLE_CLICK,
|
||||
|
||||
DROP
|
||||
|
||||
}
|
|
@ -1,476 +0,0 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import net.minestom.server.entity.EquipmentSlot;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.inventory.InventoryClickEvent;
|
||||
import net.minestom.server.event.inventory.InventoryPreClickEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.TransactionType;
|
||||
import net.minestom.server.inventory.condition.InventoryCondition;
|
||||
import net.minestom.server.inventory.condition.InventoryConditionResult;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.StackingRule;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class InventoryClickProcessor {
|
||||
// Dragging maps
|
||||
private final Map<Player, List<DragData>> leftDraggingMap = new ConcurrentHashMap<>();
|
||||
private final Map<Player, List<DragData>> rightDraggingMap = new ConcurrentHashMap<>();
|
||||
|
||||
public @NotNull InventoryClickResult leftClick(@NotNull Player player, @NotNull AbstractInventory inventory,
|
||||
int slot,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
final var result = startCondition(player, inventory, slot, ClickType.LEFT_CLICK, clicked, cursor);
|
||||
if (result.isCancel()) return result;
|
||||
clicked = result.getClicked();
|
||||
cursor = result.getCursor();
|
||||
final StackingRule rule = StackingRule.get();
|
||||
if (rule.canBeStacked(cursor, clicked)) {
|
||||
// Try to stack items
|
||||
final int totalAmount = rule.getAmount(cursor) + rule.getAmount(clicked);
|
||||
final int maxSize = rule.getMaxSize(cursor);
|
||||
if (!rule.canApply(clicked, totalAmount)) {
|
||||
// Size is too big, stack as much as possible into clicked
|
||||
result.setCursor(rule.apply(cursor, totalAmount - maxSize));
|
||||
result.setClicked(rule.apply(clicked, maxSize));
|
||||
} else {
|
||||
// Merge cursor item clicked
|
||||
result.setCursor(rule.apply(cursor, 0));
|
||||
result.setClicked(rule.apply(clicked, totalAmount));
|
||||
}
|
||||
} else {
|
||||
// Items are not compatible, swap them
|
||||
result.setCursor(clicked);
|
||||
result.setClicked(cursor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public @NotNull InventoryClickResult rightClick(@NotNull Player player, @NotNull AbstractInventory inventory,
|
||||
int slot,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
final var result = startCondition(player, inventory, slot, ClickType.RIGHT_CLICK, clicked, cursor);
|
||||
if (result.isCancel()) return result;
|
||||
clicked = result.getClicked();
|
||||
cursor = result.getCursor();
|
||||
final StackingRule rule = StackingRule.get();
|
||||
if (rule.canBeStacked(clicked, cursor)) {
|
||||
// Items can be stacked
|
||||
final int amount = rule.getAmount(clicked) + 1;
|
||||
if (!rule.canApply(clicked, amount)) {
|
||||
// Size too large, stop here
|
||||
return result;
|
||||
} else {
|
||||
// Add 1 to clicked
|
||||
result.setCursor(rule.apply(cursor, operand -> operand - 1));
|
||||
result.setClicked(rule.apply(clicked, amount));
|
||||
}
|
||||
} else {
|
||||
// Items cannot be stacked
|
||||
if (cursor.isAir()) {
|
||||
// Take half of clicked
|
||||
final int amount = (int) Math.ceil((double) rule.getAmount(clicked) / 2d);
|
||||
result.setCursor(rule.apply(clicked, amount));
|
||||
result.setClicked(rule.apply(clicked, operand -> operand / 2));
|
||||
} else {
|
||||
if (clicked.isAir()) {
|
||||
// Put 1 to clicked
|
||||
result.setCursor(rule.apply(cursor, operand -> operand - 1));
|
||||
result.setClicked(rule.apply(cursor, 1));
|
||||
} else {
|
||||
// Swap items
|
||||
result.setCursor(clicked);
|
||||
result.setClicked(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public @NotNull InventoryClickResult changeHeld(@NotNull Player player, @NotNull AbstractInventory inventory,
|
||||
int slot, int key,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
// Verify the clicked item
|
||||
InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.CHANGE_HELD, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
// Verify the destination (held bar)
|
||||
clickResult = startCondition(player, player.getInventory(), key, ClickType.CHANGE_HELD, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
// Swap items
|
||||
clickResult.setClicked(cursor);
|
||||
clickResult.setCursor(clicked);
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
public @NotNull InventoryClickResult shiftClick(@NotNull AbstractInventory inventory, @NotNull AbstractInventory targetInventory,
|
||||
int start, int end, int step,
|
||||
@NotNull Player player, int slot,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.START_SHIFT_CLICK, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
if (clicked.isAir()) return clickResult.cancelled();
|
||||
|
||||
// Handle armor equip
|
||||
if (inventory instanceof PlayerInventory && targetInventory instanceof PlayerInventory) {
|
||||
final Material material = clicked.material();
|
||||
final EquipmentSlot equipmentSlot = material.registry().equipmentSlot();
|
||||
if (equipmentSlot != null) {
|
||||
// Shift-click equip
|
||||
final ItemStack currentArmor = player.getEquipment(equipmentSlot);
|
||||
if (currentArmor.isAir()) {
|
||||
final int armorSlot = equipmentSlot.armorSlot();
|
||||
InventoryClickResult result = startCondition(player, targetInventory, armorSlot, ClickType.SHIFT_CLICK, clicked, cursor);
|
||||
if (result.isCancel()) return clickResult;
|
||||
result.setClicked(ItemStack.AIR);
|
||||
result.setCursor(cursor);
|
||||
player.setEquipment(equipmentSlot, clicked);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clickResult.setCancel(true);
|
||||
final var pair = TransactionType.ADD.process(targetInventory, clicked, (index, itemStack) -> {
|
||||
if (inventory == targetInventory && index == slot)
|
||||
return false; // Prevent item lose/duplication
|
||||
InventoryClickResult result = startCondition(player, targetInventory, index, ClickType.SHIFT_CLICK, itemStack, cursor);
|
||||
if (result.isCancel()) {
|
||||
return false;
|
||||
}
|
||||
clickResult.setCancel(false);
|
||||
return true;
|
||||
}, start, end, step);
|
||||
|
||||
final ItemStack itemResult = pair.left();
|
||||
final Map<Integer, ItemStack> itemChangesMap = pair.right();
|
||||
itemChangesMap.forEach((Integer s, ItemStack itemStack) -> {
|
||||
targetInventory.setItemStack(s, itemStack);
|
||||
callClickEvent(player, targetInventory, s, ClickType.SHIFT_CLICK, itemStack, cursor);
|
||||
});
|
||||
clickResult.setClicked(itemResult);
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
public @Nullable InventoryClickResult dragging(@NotNull Player player, @Nullable AbstractInventory inventory,
|
||||
int slot, int button,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
InventoryClickResult clickResult = null;
|
||||
final StackingRule stackingRule = StackingRule.get();
|
||||
if (slot != -999) {
|
||||
// Add slot
|
||||
if (button == 1) {
|
||||
// Add left
|
||||
List<DragData> left = leftDraggingMap.get(player);
|
||||
if (left == null) return null;
|
||||
left.add(new DragData(slot, inventory));
|
||||
} else if (button == 5) {
|
||||
// Add right
|
||||
List<DragData> right = rightDraggingMap.get(player);
|
||||
if (right == null) return null;
|
||||
right.add(new DragData(slot, inventory));
|
||||
} else if (button == 9) {
|
||||
// Add middle
|
||||
// TODO
|
||||
}
|
||||
} else {
|
||||
// Drag instruction
|
||||
if (button == 0) {
|
||||
// Start left
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.START_LEFT_DRAGGING, clicked, cursor);
|
||||
if (!clickResult.isCancel()) this.leftDraggingMap.put(player, new ArrayList<>());
|
||||
} else if (button == 2) {
|
||||
// End left
|
||||
final List<DragData> slots = leftDraggingMap.remove(player);
|
||||
if (slots == null) return null;
|
||||
final int slotCount = slots.size();
|
||||
final int cursorAmount = stackingRule.getAmount(cursor);
|
||||
if (slotCount > cursorAmount) return null;
|
||||
for (DragData s : slots) {
|
||||
// Apply each drag element
|
||||
final ItemStack slotItem = s.inventory.getItemStack(s.slot);
|
||||
clickResult = startCondition(player, s.inventory, s.slot, ClickType.LEFT_DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
return clickResult;
|
||||
}
|
||||
}
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.END_LEFT_DRAGGING, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
// Should be size of each defined slot (if not full)
|
||||
final int slotSize = (int) ((float) cursorAmount / (float) slotCount);
|
||||
// Place all waiting drag action
|
||||
int finalCursorAmount = cursorAmount;
|
||||
for (DragData dragData : slots) {
|
||||
final var inv = dragData.inventory;
|
||||
final int s = dragData.slot;
|
||||
ItemStack slotItem = inv.getItemStack(s);
|
||||
final int amount = stackingRule.getAmount(slotItem);
|
||||
if (stackingRule.canBeStacked(cursor, slotItem)) {
|
||||
if (stackingRule.canApply(slotItem, amount + slotSize)) {
|
||||
// Append divided amount to slot
|
||||
slotItem = stackingRule.apply(slotItem, a -> a + slotSize);
|
||||
finalCursorAmount -= slotSize;
|
||||
} else {
|
||||
// Amount too big, fill as much as possible
|
||||
final int maxSize = stackingRule.getMaxSize(cursor);
|
||||
final int removedAmount = maxSize - amount;
|
||||
slotItem = stackingRule.apply(slotItem, maxSize);
|
||||
finalCursorAmount -= removedAmount;
|
||||
}
|
||||
} else if (slotItem.isAir()) {
|
||||
// Slot is empty, add divided amount
|
||||
slotItem = stackingRule.apply(cursor, slotSize);
|
||||
finalCursorAmount -= slotSize;
|
||||
}
|
||||
inv.setItemStack(s, slotItem);
|
||||
callClickEvent(player, inv, s, ClickType.LEFT_DRAGGING, slotItem, cursor);
|
||||
}
|
||||
// Update the cursor
|
||||
clickResult.setCursor(stackingRule.apply(cursor, finalCursorAmount));
|
||||
} else if (button == 4) {
|
||||
// Start right
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.START_RIGHT_DRAGGING, clicked, cursor);
|
||||
if (!clickResult.isCancel()) this.rightDraggingMap.put(player, new ArrayList<>());
|
||||
} else if (button == 6) {
|
||||
// End right
|
||||
final List<DragData> slots = rightDraggingMap.remove(player);
|
||||
if (slots == null) return null;
|
||||
final int size = slots.size();
|
||||
int cursorAmount = stackingRule.getAmount(cursor);
|
||||
if (size > cursorAmount) return null;
|
||||
// Verify if each slot can be modified (or cancel the whole drag)
|
||||
for (DragData s : slots) {
|
||||
final ItemStack slotItem = s.inventory.getItemStack(s.slot);
|
||||
clickResult = startCondition(player, s.inventory, s.slot, ClickType.RIGHT_DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
return clickResult;
|
||||
}
|
||||
}
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.END_RIGHT_DRAGGING, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
// Place all waiting drag action
|
||||
int finalCursorAmount = cursorAmount;
|
||||
for (DragData dragData : slots) {
|
||||
final var inv = dragData.inventory;
|
||||
final int s = dragData.slot;
|
||||
ItemStack slotItem = inv.getItemStack(s);
|
||||
if (stackingRule.canBeStacked(cursor, slotItem)) {
|
||||
// Compatible item in the slot, increment by 1
|
||||
final int amount = stackingRule.getAmount(slotItem) + 1;
|
||||
if (stackingRule.canApply(slotItem, amount)) {
|
||||
slotItem = stackingRule.apply(slotItem, amount);
|
||||
finalCursorAmount -= 1;
|
||||
}
|
||||
} else if (slotItem.isAir()) {
|
||||
// No item at the slot, place one
|
||||
slotItem = stackingRule.apply(cursor, 1);
|
||||
finalCursorAmount -= 1;
|
||||
}
|
||||
inv.setItemStack(s, slotItem);
|
||||
callClickEvent(player, inv, s, ClickType.RIGHT_DRAGGING, slotItem, cursor);
|
||||
}
|
||||
// Update the cursor
|
||||
clickResult.setCursor(stackingRule.apply(cursor, finalCursorAmount));
|
||||
}
|
||||
}
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
public @NotNull InventoryClickResult doubleClick(@NotNull AbstractInventory clickedInventory, @NotNull AbstractInventory inventory, @NotNull Player player, int slot,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
InventoryClickResult clickResult = startCondition(player, clickedInventory, slot, ClickType.START_DOUBLE_CLICK, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
if (cursor.isAir()) return clickResult.cancelled();
|
||||
|
||||
final StackingRule rule = StackingRule.get();
|
||||
final int amount = rule.getAmount(cursor);
|
||||
final int maxSize = rule.getMaxSize(cursor);
|
||||
final int remainingAmount = maxSize - amount;
|
||||
if (remainingAmount == 0) {
|
||||
// Item is already full
|
||||
return clickResult;
|
||||
}
|
||||
final BiFunction<AbstractInventory, ItemStack, ItemStack> func = (inv, rest) -> {
|
||||
var pair = TransactionType.TAKE.process(inv, rest, (index, itemStack) -> {
|
||||
if (index == slot) // Prevent item lose/duplication
|
||||
return false;
|
||||
final InventoryClickResult result = startCondition(player, inv, index, ClickType.DOUBLE_CLICK, itemStack, cursor);
|
||||
return !result.isCancel();
|
||||
});
|
||||
final ItemStack itemResult = pair.left();
|
||||
var itemChangesMap = pair.right();
|
||||
itemChangesMap.forEach((Integer s, ItemStack itemStack) -> {
|
||||
inv.setItemStack(s, itemStack);
|
||||
callClickEvent(player, inv, s, ClickType.DOUBLE_CLICK, itemStack, cursor);
|
||||
});
|
||||
return itemResult;
|
||||
};
|
||||
|
||||
ItemStack remain = rule.apply(cursor, remainingAmount);
|
||||
final var playerInventory = player.getInventory();
|
||||
// Retrieve remain
|
||||
if (Objects.equals(clickedInventory, inventory)) {
|
||||
// Clicked inside inventory
|
||||
remain = func.apply(inventory, remain);
|
||||
if (!remain.isAir()) {
|
||||
remain = func.apply(playerInventory, remain);
|
||||
}
|
||||
} else if (clickedInventory == playerInventory) {
|
||||
// Clicked inside player inventory, but with another inventory open
|
||||
remain = func.apply(playerInventory, remain);
|
||||
if (!remain.isAir()) {
|
||||
remain = func.apply(inventory, remain);
|
||||
}
|
||||
} else {
|
||||
// Clicked inside player inventory
|
||||
remain = func.apply(playerInventory, remain);
|
||||
}
|
||||
|
||||
// Update cursor based on the remaining
|
||||
if (remain.isAir()) {
|
||||
// Item has been filled
|
||||
clickResult.setCursor(rule.apply(cursor, maxSize));
|
||||
} else {
|
||||
final int tookAmount = remainingAmount - rule.getAmount(remain);
|
||||
clickResult.setCursor(rule.apply(cursor, amount + tookAmount));
|
||||
}
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
public @NotNull InventoryClickResult drop(@NotNull Player player, @NotNull AbstractInventory inventory,
|
||||
boolean all, int slot, int button,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
final InventoryClickResult clickResult = startCondition(player, inventory, slot, ClickType.DROP, clicked, cursor);
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
|
||||
final StackingRule rule = StackingRule.get();
|
||||
|
||||
ItemStack resultClicked = clicked;
|
||||
ItemStack resultCursor = cursor;
|
||||
|
||||
if (slot == -999) {
|
||||
// Click outside
|
||||
if (button == 0) {
|
||||
// Left (drop all)
|
||||
final int amount = rule.getAmount(resultCursor);
|
||||
final ItemStack dropItem = rule.apply(resultCursor, amount);
|
||||
final boolean dropResult = player.dropItem(dropItem);
|
||||
clickResult.setCancel(!dropResult);
|
||||
if (dropResult) {
|
||||
resultCursor = rule.apply(resultCursor, 0);
|
||||
}
|
||||
} else if (button == 1) {
|
||||
// Right (drop 1)
|
||||
final ItemStack dropItem = rule.apply(resultCursor, 1);
|
||||
final boolean dropResult = player.dropItem(dropItem);
|
||||
clickResult.setCancel(!dropResult);
|
||||
if (dropResult) {
|
||||
final int amount = rule.getAmount(resultCursor);
|
||||
final int newAmount = amount - 1;
|
||||
resultCursor = rule.apply(resultCursor, newAmount);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!all) {
|
||||
if (button == 0) {
|
||||
// Drop key Q (drop 1)
|
||||
final ItemStack dropItem = rule.apply(resultClicked, 1);
|
||||
final boolean dropResult = player.dropItem(dropItem);
|
||||
clickResult.setCancel(!dropResult);
|
||||
if (dropResult) {
|
||||
final int amount = rule.getAmount(resultClicked);
|
||||
final int newAmount = amount - 1;
|
||||
resultClicked = rule.apply(resultClicked, newAmount);
|
||||
}
|
||||
} else if (button == 1) {
|
||||
// Ctrl + Drop key Q (drop all)
|
||||
final int amount = rule.getAmount(resultClicked);
|
||||
final ItemStack dropItem = rule.apply(resultClicked, amount);
|
||||
final boolean dropResult = player.dropItem(dropItem);
|
||||
clickResult.setCancel(!dropResult);
|
||||
if (dropResult) {
|
||||
resultClicked = rule.apply(resultClicked, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clickResult.setClicked(resultClicked);
|
||||
clickResult.setCursor(resultCursor);
|
||||
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
private @NotNull InventoryClickResult startCondition(@NotNull Player player,
|
||||
@Nullable AbstractInventory inventory,
|
||||
int slot, @NotNull ClickType clickType,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
final InventoryClickResult clickResult = new InventoryClickResult(clicked, cursor);
|
||||
final Inventory eventInventory = inventory instanceof Inventory ? (Inventory) inventory : null;
|
||||
|
||||
// Reset the didCloseInventory field
|
||||
// Wait for inventory conditions + events to possibly close the inventory
|
||||
player.UNSAFE_changeDidCloseInventory(false);
|
||||
// InventoryPreClickEvent
|
||||
{
|
||||
InventoryPreClickEvent inventoryPreClickEvent = new InventoryPreClickEvent(eventInventory, player, slot, clickType,
|
||||
clickResult.getClicked(), clickResult.getCursor());
|
||||
EventDispatcher.call(inventoryPreClickEvent);
|
||||
clickResult.setCursor(inventoryPreClickEvent.getCursorItem());
|
||||
clickResult.setClicked(inventoryPreClickEvent.getClickedItem());
|
||||
if (inventoryPreClickEvent.isCancelled()) {
|
||||
clickResult.setCancel(true);
|
||||
}
|
||||
}
|
||||
// Inventory conditions
|
||||
{
|
||||
if (inventory != null) {
|
||||
final List<InventoryCondition> inventoryConditions = inventory.getInventoryConditions();
|
||||
if (!inventoryConditions.isEmpty()) {
|
||||
for (InventoryCondition inventoryCondition : inventoryConditions) {
|
||||
var result = new InventoryConditionResult(clickResult.getClicked(), clickResult.getCursor());
|
||||
inventoryCondition.accept(player, slot, clickType, result);
|
||||
|
||||
clickResult.setCursor(result.getCursorItem());
|
||||
clickResult.setClicked(result.getClickedItem());
|
||||
if (result.isCancel()) {
|
||||
clickResult.setCancel(true);
|
||||
}
|
||||
}
|
||||
// Cancel the click if the inventory has been closed by Player#closeInventory within an inventory listener
|
||||
if (player.didCloseInventory()) {
|
||||
clickResult.setCancel(true);
|
||||
player.UNSAFE_changeDidCloseInventory(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return clickResult;
|
||||
}
|
||||
|
||||
private void callClickEvent(@NotNull Player player, @Nullable AbstractInventory inventory, int slot,
|
||||
@NotNull ClickType clickType, @NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
final Inventory eventInventory = inventory instanceof Inventory ? (Inventory) inventory : null;
|
||||
EventDispatcher.call(new InventoryClickEvent(eventInventory, player, slot, clickType, clicked, cursor));
|
||||
}
|
||||
|
||||
public void clearCache(@NotNull Player player) {
|
||||
this.leftDraggingMap.remove(player);
|
||||
this.rightDraggingMap.remove(player);
|
||||
}
|
||||
|
||||
private record DragData(int slot, AbstractInventory inventory) {
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import net.minestom.server.item.ItemStack;
|
||||
|
||||
public final class InventoryClickResult {
|
||||
private ItemStack clicked;
|
||||
private ItemStack cursor;
|
||||
private boolean cancel;
|
||||
|
||||
public InventoryClickResult(ItemStack clicked, ItemStack cursor) {
|
||||
this.clicked = clicked;
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public ItemStack getClicked() {
|
||||
return clicked;
|
||||
}
|
||||
|
||||
void setClicked(ItemStack clicked) {
|
||||
this.clicked = clicked;
|
||||
}
|
||||
|
||||
public ItemStack getCursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void setCursor(ItemStack cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public boolean isCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
void setCancel(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
InventoryClickResult cancelled() {
|
||||
setCancel(true);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package net.minestom.server.inventory.condition;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
|
||||
/**
|
||||
* Can be added to any {@link AbstractInventory}
|
||||
* using {@link net.minestom.server.inventory.Inventory#addInventoryCondition(InventoryCondition)}
|
||||
* or {@link net.minestom.server.inventory.PlayerInventory#addInventoryCondition(InventoryCondition)}
|
||||
* in order to listen to any issued clicks.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface InventoryCondition {
|
||||
|
||||
/**
|
||||
* Called when a {@link Player} clicks in the inventory where this {@link InventoryCondition} has been added to.
|
||||
*
|
||||
* @param player the player who clicked in the inventory
|
||||
* @param slot the slot clicked, can be -999 if the click is out of the inventory
|
||||
* @param clickType the click type
|
||||
* @param inventoryConditionResult the result of this callback
|
||||
*/
|
||||
void accept(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package net.minestom.server.inventory.condition;
|
||||
|
||||
import net.minestom.server.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Used by {@link InventoryCondition} to step in inventory click processing.
|
||||
*/
|
||||
public class InventoryConditionResult {
|
||||
|
||||
private ItemStack clickedItem, cursorItem;
|
||||
private boolean cancel;
|
||||
|
||||
public InventoryConditionResult(ItemStack clickedItem, ItemStack cursorItem) {
|
||||
this.clickedItem = clickedItem;
|
||||
this.cursorItem = cursorItem;
|
||||
}
|
||||
|
||||
public ItemStack getClickedItem() {
|
||||
return clickedItem;
|
||||
}
|
||||
|
||||
public void setClickedItem(ItemStack clickedItem) {
|
||||
this.clickedItem = clickedItem;
|
||||
}
|
||||
|
||||
public ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
}
|
||||
|
||||
public void setCursorItem(ItemStack cursorItem) {
|
||||
this.cursorItem = cursorItem;
|
||||
}
|
||||
|
||||
public boolean isCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
public void setCancel(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package net.minestom.server.inventory.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryProperty;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AnvilInventory extends Inventory {
|
||||
public class AnvilInventory extends ContainerInventory {
|
||||
|
||||
private short repairCost;
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package net.minestom.server.inventory.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryProperty;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.potion.PotionEffect;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BeaconInventory extends Inventory {
|
||||
public class BeaconInventory extends ContainerInventory {
|
||||
|
||||
private short powerLevel;
|
||||
private PotionEffect firstPotionEffect;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package net.minestom.server.inventory.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryProperty;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BrewingStandInventory extends Inventory {
|
||||
public class BrewingStandInventory extends ContainerInventory {
|
||||
|
||||
private short brewTime;
|
||||
private short fuelTime;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package net.minestom.server.inventory.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryProperty;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.item.Enchantment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EnchantmentTableInventory extends Inventory {
|
||||
public class EnchantmentTableInventory extends ContainerInventory {
|
||||
|
||||
private final short[] levelRequirements = new short[EnchantmentSlot.values().length];
|
||||
private short seed;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package net.minestom.server.inventory.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryProperty;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FurnaceInventory extends Inventory {
|
||||
public class FurnaceInventory extends ContainerInventory {
|
||||
|
||||
private short remainingFuelTick;
|
||||
private short maximumFuelBurnTime;
|
||||
|
|
|
@ -2,7 +2,7 @@ package net.minestom.server.inventory.type;
|
|||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.network.packet.server.CachedPacket;
|
||||
import net.minestom.server.network.packet.server.play.TradeListPacket;
|
||||
|
@ -12,7 +12,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class VillagerInventory extends Inventory {
|
||||
public class VillagerInventory extends ContainerInventory {
|
||||
private final CachedPacket tradeCache = new CachedPacket(this::createTradePacket);
|
||||
private final List<TradeListPacket.Trade> trades = new ArrayList<>();
|
||||
private int villagerLevel;
|
||||
|
@ -82,14 +82,12 @@ public class VillagerInventory extends Inventory {
|
|||
public void update() {
|
||||
super.update();
|
||||
this.tradeCache.invalidate();
|
||||
sendPacketToViewers(tradeCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
final boolean result = super.addViewer(player);
|
||||
if (result) player.sendPacket(tradeCache);
|
||||
return result;
|
||||
public void update(@NotNull Player player) {
|
||||
super.update(player);
|
||||
player.sendPacket(tradeCache);
|
||||
}
|
||||
|
||||
private TradeListPacket createTradePacket() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component;
|
|||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.event.HoverEventSource;
|
||||
import net.minestom.server.adventure.MinestomAdventure;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagHandler;
|
||||
import net.minestom.server.tag.TagReadable;
|
||||
|
@ -20,7 +21,7 @@ import java.util.function.UnaryOperator;
|
|||
|
||||
/**
|
||||
* Represents an immutable item to be placed inside {@link net.minestom.server.inventory.PlayerInventory},
|
||||
* {@link net.minestom.server.inventory.Inventory} or even on the ground {@link net.minestom.server.entity.ItemEntity}.
|
||||
* {@link ContainerInventory} or even on the ground {@link net.minestom.server.entity.ItemEntity}.
|
||||
* <p>
|
||||
* An item stack cannot be null, {@link ItemStack#AIR} should be used instead.
|
||||
*/
|
||||
|
|
|
@ -170,7 +170,7 @@ public class BlockPlacementListener {
|
|||
if (playerBlockPlaceEvent.doesConsumeBlock()) {
|
||||
// Consume the block in the player's hand
|
||||
final ItemStack newUsedItem = usedItem.consume(1);
|
||||
playerInventory.setItemInHand(hand, newUsedItem);
|
||||
player.setItemInHand(hand, newUsedItem);
|
||||
} else {
|
||||
// Prevent invisible item on client
|
||||
playerInventory.update();
|
||||
|
|
|
@ -5,12 +5,14 @@ import net.minestom.server.event.EventDispatcher;
|
|||
import net.minestom.server.event.book.EditBookEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.client.play.ClientEditBookPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
|
||||
public class BookListener {
|
||||
|
||||
public static void listener(ClientEditBookPacket packet, Player player) {
|
||||
int slot = PlayerInventoryUtils.convertClientInventorySlot(packet.slot());
|
||||
int slot = packet.slot();
|
||||
if (slot < 0 || slot > 8) return;
|
||||
|
||||
// Do not need to convert slot as hotbar slots correspond to Minestom inventory slots
|
||||
ItemStack itemStack = player.getInventory().getItemStack(slot);
|
||||
EventDispatcher.call(new EditBookEvent(player, itemStack, packet.pages(), packet.title()));
|
||||
}
|
||||
|
|
|
@ -1,35 +1,24 @@
|
|||
package net.minestom.server.listener;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.client.play.ClientCreativeInventoryActionPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CreativeInventoryActionListener {
|
||||
public static void listener(ClientCreativeInventoryActionPacket packet, Player player) {
|
||||
if (!player.isCreative()) return;
|
||||
short slot = packet.slot();
|
||||
final ItemStack item = packet.item();
|
||||
if (slot == -1) {
|
||||
// Drop item
|
||||
player.dropItem(item);
|
||||
return;
|
||||
|
||||
ItemStack item = packet.item();
|
||||
|
||||
if (packet.slot() == -1) { // -1 here indicates a drop
|
||||
player.getInventory().handleClick(player, new Click.Info.CreativeDropItem(item));
|
||||
}
|
||||
// Bounds check
|
||||
// 0 is crafting result inventory slot, ignore attempts to place into it
|
||||
if (slot < 1 || slot > PlayerInventoryUtils.OFFHAND_SLOT) {
|
||||
return;
|
||||
}
|
||||
// Set item
|
||||
slot = (short) PlayerInventoryUtils.convertPlayerInventorySlot(slot, PlayerInventoryUtils.OFFSET);
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
if (Objects.equals(inventory.getItemStack(slot), item)) {
|
||||
// Item is already present, ignore
|
||||
return;
|
||||
}
|
||||
inventory.setItemStack(slot, item);
|
||||
|
||||
int slot = PlayerInventoryUtils.protocolToMinestom(packet.slot());
|
||||
if (slot == -1) return; // -1 after conversion indicates an invalid slot
|
||||
|
||||
player.getInventory().handleClick(player, new Click.Info.CreativeSetItem(slot, item));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,11 @@ import net.minestom.server.event.item.ItemUpdateStateEvent;
|
|||
import net.minestom.server.event.player.PlayerCancelDiggingEvent;
|
||||
import net.minestom.server.event.player.PlayerFinishDiggingEvent;
|
||||
import net.minestom.server.event.player.PlayerStartDiggingEvent;
|
||||
import net.minestom.server.event.player.PlayerSwapItemEvent;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.StackingRule;
|
||||
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
|
||||
import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket;
|
||||
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket;
|
||||
|
@ -43,13 +41,13 @@ public final class PlayerDiggingListener {
|
|||
if (!instance.isChunkLoaded(blockPosition)) return;
|
||||
diggingResult = finishDigging(player, instance, blockPosition, packet.blockFace());
|
||||
} else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM_STACK) {
|
||||
dropStack(player);
|
||||
player.getInventory().handleClick(player, new Click.Info.DropSlot(player.getHeldSlot(), true));
|
||||
} else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM) {
|
||||
dropSingle(player);
|
||||
player.getInventory().handleClick(player, new Click.Info.DropSlot(player.getHeldSlot(), false));
|
||||
} else if (status == ClientPlayerDiggingPacket.Status.UPDATE_ITEM_STATE) {
|
||||
updateItemState(player);
|
||||
} else if (status == ClientPlayerDiggingPacket.Status.SWAP_ITEM_HAND) {
|
||||
swapItemHand(player);
|
||||
player.getInventory().handleClick(player, new Click.Info.OffhandSwap(player.getHeldSlot()));
|
||||
}
|
||||
// Acknowledge start/cancel/finish digging status
|
||||
if (diggingResult != null) {
|
||||
|
@ -124,26 +122,6 @@ public final class PlayerDiggingListener {
|
|||
return false;
|
||||
}
|
||||
|
||||
private static void dropStack(Player player) {
|
||||
final ItemStack droppedItemStack = player.getInventory().getItemInMainHand();
|
||||
dropItem(player, droppedItemStack, ItemStack.AIR);
|
||||
}
|
||||
|
||||
private static void dropSingle(Player player) {
|
||||
final ItemStack handItem = player.getInventory().getItemInMainHand();
|
||||
final StackingRule stackingRule = StackingRule.get();
|
||||
final int handAmount = stackingRule.getAmount(handItem);
|
||||
if (handAmount <= 1) {
|
||||
// Drop the whole item without copy
|
||||
dropItem(player, handItem, ItemStack.AIR);
|
||||
} else {
|
||||
// Drop a single item
|
||||
dropItem(player,
|
||||
stackingRule.apply(handItem, 1), // Single dropped item
|
||||
stackingRule.apply(handItem, handAmount - 1)); // Updated hand
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateItemState(Player player) {
|
||||
LivingEntityMeta meta = player.getLivingEntityMeta();
|
||||
if (meta == null || !meta.isHandActive()) return;
|
||||
|
@ -162,17 +140,6 @@ public final class PlayerDiggingListener {
|
|||
}
|
||||
}
|
||||
|
||||
private static void swapItemHand(Player player) {
|
||||
final PlayerInventory inventory = player.getInventory();
|
||||
final ItemStack mainHand = inventory.getItemInMainHand();
|
||||
final ItemStack offHand = inventory.getItemInOffHand();
|
||||
PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(player, offHand, mainHand);
|
||||
EventDispatcher.callCancellable(swapItemEvent, () -> {
|
||||
inventory.setItemInMainHand(swapItemEvent.getMainHandItem());
|
||||
inventory.setItemInOffHand(swapItemEvent.getOffHandItem());
|
||||
});
|
||||
}
|
||||
|
||||
private static DiggingResult breakBlock(Instance instance,
|
||||
Player player,
|
||||
Point blockPosition, Block previousBlock, BlockFace blockFace) {
|
||||
|
@ -191,16 +158,6 @@ public final class PlayerDiggingListener {
|
|||
return new DiggingResult(updatedBlock, success);
|
||||
}
|
||||
|
||||
private static void dropItem(@NotNull Player player,
|
||||
@NotNull ItemStack droppedItem, @NotNull ItemStack handItem) {
|
||||
final PlayerInventory playerInventory = player.getInventory();
|
||||
if (player.dropItem(droppedItem)) {
|
||||
playerInventory.setItemInMainHand(handItem);
|
||||
} else {
|
||||
playerInventory.update();
|
||||
}
|
||||
}
|
||||
|
||||
private record DiggingResult(Block block, boolean success) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,8 @@ import net.minestom.server.network.packet.client.play.ClientUseItemPacket;
|
|||
public class UseItemListener {
|
||||
|
||||
public static void useItemListener(ClientUseItemPacket packet, Player player) {
|
||||
final PlayerInventory inventory = player.getInventory();
|
||||
final Player.Hand hand = packet.hand();
|
||||
ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand();
|
||||
ItemStack itemStack = player.getItemInHand(hand);
|
||||
//itemStack.onRightClick(player, hand);
|
||||
PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack);
|
||||
EventDispatcher.call(useItemEvent);
|
||||
|
@ -33,10 +32,10 @@ public class UseItemListener {
|
|||
// Equip armor with right click
|
||||
final EquipmentSlot equipmentSlot = material.registry().equipmentSlot();
|
||||
if (equipmentSlot != null) {
|
||||
final ItemStack currentlyEquipped = playerInventory.getEquipment(equipmentSlot);
|
||||
final ItemStack currentlyEquipped = player.getEquipment(equipmentSlot);
|
||||
if (currentlyEquipped.isAir()) {
|
||||
playerInventory.setEquipment(equipmentSlot, itemStack);
|
||||
playerInventory.setItemInHand(hand, currentlyEquipped);
|
||||
player.setEquipment(equipmentSlot, itemStack);
|
||||
player.setItemInHand(hand, currentlyEquipped);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,78 +2,29 @@ package net.minestom.server.listener;
|
|||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.inventory.InventoryButtonClickEvent;
|
||||
import net.minestom.server.event.inventory.InventoryCloseEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.network.packet.client.common.ClientPongPacket;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowButtonPacket;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
|
||||
import net.minestom.server.network.packet.client.play.ClientCloseWindowPacket;
|
||||
import net.minestom.server.network.packet.server.common.PingPacket;
|
||||
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
||||
|
||||
public class WindowListener {
|
||||
|
||||
public static void clickWindowListener(ClientClickWindowPacket packet, Player player) {
|
||||
final int windowId = packet.windowId();
|
||||
final AbstractInventory inventory = windowId == 0 ? player.getInventory() : player.getOpenInventory();
|
||||
if (inventory == null) {
|
||||
// Invalid packet
|
||||
return;
|
||||
}
|
||||
final boolean playerInventory = windowId == 0;
|
||||
final Inventory inventory = playerInventory ? player.getInventory() : player.getOpenInventory();
|
||||
|
||||
final short slot = packet.slot();
|
||||
final byte button = packet.button();
|
||||
final ClientClickWindowPacket.ClickType clickType = packet.clickType();
|
||||
// Prevent some invalid packets
|
||||
if (inventory == null || packet.slot() == -1) return;
|
||||
|
||||
boolean successful = false;
|
||||
|
||||
// prevent click in a non-interactive slot (why does it exist?)
|
||||
if (slot == -1) {
|
||||
return;
|
||||
}
|
||||
if (clickType == ClientClickWindowPacket.ClickType.PICKUP) {
|
||||
if (button == 0) {
|
||||
if (slot != -999) {
|
||||
successful = inventory.leftClick(player, slot);
|
||||
} else {
|
||||
successful = inventory.drop(player, true, slot, button);
|
||||
}
|
||||
} else if (button == 1) {
|
||||
if (slot != -999) {
|
||||
successful = inventory.rightClick(player, slot);
|
||||
} else {
|
||||
successful = inventory.drop(player, false, slot, button);
|
||||
}
|
||||
}
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.QUICK_MOVE) {
|
||||
successful = inventory.shiftClick(player, slot);
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.SWAP) {
|
||||
successful = inventory.changeHeld(player, slot, button);
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.CLONE) {
|
||||
successful = player.isCreative();
|
||||
if (successful) {
|
||||
setCursor(player, inventory, packet.clickedItem());
|
||||
}
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.THROW) {
|
||||
successful = inventory.drop(player, false, slot, button);
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.QUICK_CRAFT) {
|
||||
successful = inventory.dragging(player, slot, button);
|
||||
} else if (clickType == ClientClickWindowPacket.ClickType.PICKUP_ALL) {
|
||||
successful = inventory.doubleClick(player, slot);
|
||||
}
|
||||
|
||||
// Prevent ghost item when the click is cancelled
|
||||
if (!successful) {
|
||||
player.getInventory().update();
|
||||
if (inventory instanceof Inventory) {
|
||||
((Inventory) inventory).update(player);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent the player from picking a ghost item in cursor
|
||||
refreshCursorItem(player, inventory);
|
||||
Click.Preprocessor preprocessor = player.clickPreprocessor();
|
||||
final Click.Info info = preprocessor.processClick(packet, player.isCreative(), playerInventory ? null : inventory.getSize());
|
||||
if (info != null) inventory.handleClick(player, info);
|
||||
|
||||
// (Why is the ping packet necessary?)
|
||||
player.sendPacket(new PingPacket((1 << 30) | (windowId << 16)));
|
||||
|
@ -85,7 +36,10 @@ public class WindowListener {
|
|||
|
||||
public static void closeWindowListener(ClientCloseWindowPacket packet, Player player) {
|
||||
// if windowId == 0 then it is player's inventory, meaning that they hadn't been any open inventory packet
|
||||
InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(player.getOpenInventory(), player);
|
||||
var openInventory = player.getOpenInventory();
|
||||
if (openInventory == null) openInventory = player.getInventory();
|
||||
|
||||
InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(openInventory, player);
|
||||
EventDispatcher.call(inventoryCloseEvent);
|
||||
|
||||
player.closeInventory(true);
|
||||
|
@ -95,30 +49,11 @@ public class WindowListener {
|
|||
player.openInventory(newInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player the player to refresh the cursor item
|
||||
* @param inventory the player open inventory, null if not any (could be player inventory)
|
||||
*/
|
||||
private static void refreshCursorItem(Player player, AbstractInventory inventory) {
|
||||
ItemStack cursorItem;
|
||||
if (inventory instanceof PlayerInventory playerInventory) {
|
||||
cursorItem = playerInventory.getCursorItem();
|
||||
} else if (inventory instanceof Inventory standardInventory) {
|
||||
cursorItem = standardInventory.getCursorItem(player);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid inventory: " + inventory.getClass());
|
||||
}
|
||||
final SetSlotPacket setSlotPacket = SetSlotPacket.createCursorPacket(cursorItem);
|
||||
player.sendPacket(setSlotPacket);
|
||||
}
|
||||
public static void buttonClickListener(ClientClickWindowButtonPacket packet, Player player) {
|
||||
var openInventory = player.getOpenInventory();
|
||||
if (openInventory == null) openInventory = player.getInventory();
|
||||
|
||||
private static void setCursor(Player player, AbstractInventory inventory, ItemStack itemStack) {
|
||||
if (inventory instanceof PlayerInventory playerInventory) {
|
||||
playerInventory.setCursorItem(itemStack);
|
||||
} else if (inventory instanceof Inventory standardInventory) {
|
||||
standardInventory.setCursorItem(player, itemStack);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid inventory: " + inventory.getClass());
|
||||
}
|
||||
InventoryButtonClickEvent event = new InventoryButtonClickEvent(openInventory, player, packet.buttonId());
|
||||
EventDispatcher.call(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ public final class PacketListenerManager {
|
|||
setPlayListener(ClientChatMessagePacket.class, ChatMessageListener::chatMessageListener);
|
||||
setPlayListener(ClientClickWindowPacket.class, WindowListener::clickWindowListener);
|
||||
setPlayListener(ClientCloseWindowPacket.class, WindowListener::closeWindowListener);
|
||||
setPlayListener(ClientClickWindowButtonPacket.class, WindowListener::buttonClickListener);
|
||||
setPlayListener(ClientConfigurationAckPacket.class, PlayConfigListener::configAckListener);
|
||||
setPlayListener(ClientPongPacket.class, WindowListener::pong);
|
||||
setPlayListener(ClientEntityActionPacket.class, EntityActionListener::listener);
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
package net.minestom.server.utils.inventory;
|
||||
|
||||
public final class PlayerInventoryUtils {
|
||||
|
||||
public static final int OFFSET = 9;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Minestom uses different slot IDs for player inventories as the Minecraft protocol uses a strange system (e.g. the
|
||||
* crafting result is the first slot).<br>
|
||||
* These can be mapped 1:1 to and from protocol slots using {@link #minestomToProtocol(int)} and {@link #protocolToMinestom(int)}.<br>
|
||||
* <p>
|
||||
* Read about protocol slot IDs <a href="https://wiki.vg/Inventory">here</a>.
|
||||
*/
|
||||
public final class PlayerInventoryUtils {
|
||||
public static final int INVENTORY_SIZE = 46;
|
||||
public static final int INNER_SIZE = 36;
|
||||
|
||||
public static final int PROTOCOL_OFFSET = 9;
|
||||
|
||||
public static final int CRAFT_RESULT = 36;
|
||||
public static final int CRAFT_SLOT_1 = 37;
|
||||
|
@ -14,21 +28,58 @@ public final class PlayerInventoryUtils {
|
|||
public static final int CHESTPLATE_SLOT = 42;
|
||||
public static final int LEGGINGS_SLOT = 43;
|
||||
public static final int BOOTS_SLOT = 44;
|
||||
public static final int OFFHAND_SLOT = 45;
|
||||
public static final int OFF_HAND_SLOT = 45;
|
||||
|
||||
public static @NotNull IntStream getInnerShiftClickSlots(int size) {
|
||||
return IntStream.range(0, 36).map(i -> i + size);
|
||||
}
|
||||
|
||||
public static @NotNull IntStream getInnerDoubleClickSlots(int size) {
|
||||
return IntStream.range(0, 36).map(i -> i + size);
|
||||
}
|
||||
|
||||
private PlayerInventoryUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a packet slot to an internal one.
|
||||
* Converts a Minestom slot ID to a Minecraft protocol slot ID.<br>
|
||||
* This is the inverse of {@link #protocolToMinestom(int)}.
|
||||
*
|
||||
* @param slot the packet slot
|
||||
* @param offset the slot count separating the up part of the inventory to the bottom part (armor/craft in PlayerInventory, inventory slots in others)
|
||||
* the offset for the player inventory is {@link #OFFSET}
|
||||
* @return a packet which can be use internally with Minestom
|
||||
* @param slot the internal slot ID to convert
|
||||
* @return the protocol slot ID, or -1 if the given slot could not be converted
|
||||
*/
|
||||
public static int convertPlayerInventorySlot(int slot, int offset) {
|
||||
public static int minestomToProtocol(int slot) {
|
||||
return switch (slot) {
|
||||
case CRAFT_RESULT -> 0;
|
||||
case CRAFT_SLOT_1 -> 1;
|
||||
case CRAFT_SLOT_2 -> 2;
|
||||
case CRAFT_SLOT_3 -> 3;
|
||||
case CRAFT_SLOT_4 -> 4;
|
||||
case HELMET_SLOT -> 5;
|
||||
case CHESTPLATE_SLOT -> 6;
|
||||
case LEGGINGS_SLOT -> 7;
|
||||
case BOOTS_SLOT -> 8;
|
||||
case OFF_HAND_SLOT -> OFF_HAND_SLOT;
|
||||
default -> {
|
||||
if (slot >= 0 && slot <= 8) {
|
||||
yield slot + 36;
|
||||
} else if (slot >= 9 && slot <= 35) {
|
||||
yield slot;
|
||||
} else {
|
||||
yield -1; // Unknown slot ID
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Minecraft protocol slot ID to a Minestom slot ID.<br>
|
||||
* This is the inverse of {@link #minestomToProtocol(int)}.
|
||||
*
|
||||
* @param slot the protocol slot ID to convert
|
||||
* @return the Minestom slot ID, or -1 if the given slot could not be converted
|
||||
*/
|
||||
public static int protocolToMinestom(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> CRAFT_RESULT;
|
||||
case 1 -> CRAFT_SLOT_1;
|
||||
|
@ -39,57 +90,45 @@ public final class PlayerInventoryUtils {
|
|||
case 6 -> CHESTPLATE_SLOT;
|
||||
case 7 -> LEGGINGS_SLOT;
|
||||
case 8 -> BOOTS_SLOT;
|
||||
default -> convertSlot(slot, offset);
|
||||
case OFF_HAND_SLOT -> OFF_HAND_SLOT;
|
||||
default -> {
|
||||
if (slot >= 36 && slot <= 44) {
|
||||
yield slot - 36;
|
||||
} else if (slot >= 9 && slot <= 35) {
|
||||
yield slot;
|
||||
} else {
|
||||
yield -1; // Unknown slot ID
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public static int convertSlot(int slot, int offset) {
|
||||
final int rowSize = 9;
|
||||
slot -= offset;
|
||||
if (slot >= rowSize * 3 && slot < rowSize * 4) {
|
||||
slot = slot % 9;
|
||||
} else {
|
||||
slot = slot + rowSize;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to convert internal slot to one used in packets
|
||||
*
|
||||
* @param slot the internal slot
|
||||
* @return a slot id which can be used for packets
|
||||
*/
|
||||
public static int convertToPacketSlot(int slot) {
|
||||
if (slot > -1 && slot < 9) { // Held bar 0-8
|
||||
slot = slot + 36;
|
||||
} else if (slot > 8 && slot < 36) { // Inventory 9-35
|
||||
slot = slot;
|
||||
} else if (slot >= CRAFT_RESULT && slot <= CRAFT_SLOT_4) { // Crafting 36-40
|
||||
slot = slot - 36;
|
||||
} else if (slot >= HELMET_SLOT && slot <= BOOTS_SLOT) { // Armor 41-44
|
||||
slot = slot - 36;
|
||||
} else if (slot == OFFHAND_SLOT) { // Off hand
|
||||
slot = 45;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert the clients inventory slot to a Minestom slot.
|
||||
* The client's inventory does not count the crafting slots.
|
||||
* Converts the given slot into a protocol ID directly after the provided inventory.
|
||||
* This is intended for when a player's inner inventory is interacted with while a player has another inventory
|
||||
* open.<br>
|
||||
* This is the inverse of {@link #protocolToMinestom(int, int)}.
|
||||
*
|
||||
* @param slot the client slot
|
||||
* @return a slot which can be used internally with Minestom
|
||||
* @param slot the player slot that was interacted with
|
||||
* @param openInventorySize the size of the inventory opened by the player (not the player's inventory)
|
||||
* @return the protocol slot ID
|
||||
*/
|
||||
public static int convertClientInventorySlot(int slot) {
|
||||
if (slot == 36) return BOOTS_SLOT;
|
||||
if (slot == 37) return LEGGINGS_SLOT;
|
||||
if (slot == 38) return CHESTPLATE_SLOT;
|
||||
if (slot == 39) return HELMET_SLOT;
|
||||
if (slot == 40) return OFFHAND_SLOT;
|
||||
return slot;
|
||||
public static int minestomToProtocol(int slot, int openInventorySize) {
|
||||
return PlayerInventoryUtils.minestomToProtocol(slot) + openInventorySize - PROTOCOL_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given protocol ID that is directly after the provided inventory's slots into a player inventory slot
|
||||
* ID. This is intended for when a player's inner inventory is interacted with while a player has another inventory
|
||||
* open.<br>
|
||||
* This is the inverse of {@link #minestomToProtocol(int, int)}.
|
||||
*
|
||||
* @param slot the protocol slot ID, situated directly after the slot IDs for the open inventory
|
||||
* @param openInventorySize the size of the inventory opened by the player (not the player's inventory)
|
||||
* @return the player slot ID
|
||||
*/
|
||||
public static int protocolToMinestom(int slot, int openInventorySize) {
|
||||
if (slot < openInventorySize) return -1;
|
||||
return PlayerInventoryUtils.protocolToMinestom(slot - openInventorySize + PROTOCOL_OFFSET);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public class InventoryCloseStateTest {
|
|||
assertEquals(instance, player.getInstance());
|
||||
|
||||
var packetTracker = connection.trackIncoming(CloseWindowPacket.class);
|
||||
var inventory = new Inventory(InventoryType.CHEST_2_ROW, Component.text("Test"));
|
||||
var inventory = new ContainerInventory(InventoryType.CHEST_2_ROW, Component.text("Test"));
|
||||
player.openInventory(inventory);
|
||||
player.closeInventory(); // Closes the inventory server-side, should send a CloseWindowPacket
|
||||
player.openInventory(inventory);
|
||||
|
|
|
@ -5,7 +5,7 @@ import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
|||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.event.item.ItemDropEvent;
|
||||
import net.minestom.server.event.inventory.InventoryItemChangeEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
||||
|
@ -27,7 +27,7 @@ public class InventoryIntegrationTest {
|
|||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
assertEquals(instance, player.getInstance());
|
||||
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
player.openInventory(inventory);
|
||||
assertEquals(inventory, player.getOpenInventory());
|
||||
|
||||
|
@ -51,20 +51,20 @@ public class InventoryIntegrationTest {
|
|||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
assertEquals(instance, player.getInstance());
|
||||
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
player.openInventory(inventory);
|
||||
assertEquals(inventory, player.getOpenInventory());
|
||||
|
||||
var packetTracker = connection.trackIncoming(SetSlotPacket.class);
|
||||
inventory.setCursorItem(player, MAGIC_STACK);
|
||||
player.getInventory().setCursorItem(MAGIC_STACK);
|
||||
packetTracker.assertSingle(slot -> assertEquals(MAGIC_STACK, slot.itemStack())); // Setting a slot should send a packet
|
||||
|
||||
packetTracker = connection.trackIncoming(SetSlotPacket.class);
|
||||
inventory.setCursorItem(player, MAGIC_STACK);
|
||||
player.getInventory().setCursorItem(MAGIC_STACK);
|
||||
packetTracker.assertEmpty(); // Setting the same slot to the same ItemStack should not send another packet
|
||||
|
||||
packetTracker = connection.trackIncoming(SetSlotPacket.class);
|
||||
inventory.setCursorItem(player, ItemStack.AIR);
|
||||
player.getInventory().setCursorItem(ItemStack.AIR);
|
||||
packetTracker.assertSingle(slot -> assertEquals(ItemStack.AIR, slot.itemStack())); // Setting a slot should send a packet
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ public class InventoryIntegrationTest {
|
|||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
assertEquals(instance, player.getInstance());
|
||||
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
player.openInventory(inventory);
|
||||
assertEquals(inventory, player.getOpenInventory());
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class InventoryIntegrationTest {
|
|||
inventory.setItemStack(3, MAGIC_STACK);
|
||||
inventory.setItemStack(19, MAGIC_STACK);
|
||||
inventory.setItemStack(40, MAGIC_STACK);
|
||||
inventory.setCursorItem(player, MAGIC_STACK);
|
||||
player.getInventory().setCursorItem(MAGIC_STACK);
|
||||
|
||||
setSlotTracker.assertCount(5);
|
||||
|
||||
|
@ -116,7 +116,7 @@ public class InventoryIntegrationTest {
|
|||
var instance = env.createFlatInstance();
|
||||
var connection = env.createConnection();
|
||||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
final var inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
final var inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
player.openInventory(inventory);
|
||||
assertSame(inventory, player.getOpenInventory());
|
||||
player.closeInventory();
|
||||
|
@ -124,24 +124,24 @@ public class InventoryIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void openInventoryOnItemDropFromInventoryClosingTest(Env env) {
|
||||
public void openInventoryOnItemAddFromInventoryClosingTest(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var connection = env.createConnection();
|
||||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
var listener = env.listen(ItemDropEvent.class);
|
||||
final var firstInventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
var listener = env.listen(InventoryItemChangeEvent.class);
|
||||
final var firstInventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
player.openInventory(firstInventory);
|
||||
assertSame(firstInventory, player.getOpenInventory());
|
||||
firstInventory.setCursorItem(player, ItemStack.of(Material.STONE));
|
||||
player.getInventory().setCursorItem(ItemStack.of(Material.STONE));
|
||||
|
||||
listener.followup();
|
||||
player.closeInventory();
|
||||
assertNull(player.getOpenInventory());
|
||||
|
||||
player.openInventory(firstInventory);
|
||||
firstInventory.setCursorItem(player, ItemStack.of(Material.STONE));
|
||||
final var secondInventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
listener.followup(event -> event.getPlayer().openInventory(secondInventory));
|
||||
player.getInventory().setCursorItem(ItemStack.of(Material.STONE));
|
||||
final var secondInventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
listener.followup(event -> player.openInventory(secondInventory));
|
||||
player.closeInventory();
|
||||
assertSame(secondInventory, player.getOpenInventory());
|
||||
}
|
||||
|
@ -156,17 +156,16 @@ public class InventoryIntegrationTest {
|
|||
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
|
||||
assertEquals(instance, player.getInstance());
|
||||
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
Inventory inventory = new ContainerInventory(InventoryType.CHEST_6_ROW, Component.empty());
|
||||
player.openInventory(inventory);
|
||||
assertEquals(inventory, player.getOpenInventory());
|
||||
|
||||
// Ensure that slots not in the inner inventory are sent separately
|
||||
var packetTracker = connection.trackIncoming(SetSlotPacket.class);
|
||||
player.getInventory().setItemStack(PlayerInventoryUtils.OFFHAND_SLOT, MAGIC_STACK);
|
||||
player.getInventory().setItemStack(PlayerInventoryUtils.OFF_HAND_SLOT, MAGIC_STACK);
|
||||
packetTracker.assertSingle(slot -> {
|
||||
System.out.println(slot);
|
||||
assertEquals((byte) 0, slot.windowId());
|
||||
assertEquals(PlayerInventoryUtils.OFFHAND_SLOT, slot.slot());
|
||||
assertEquals(PlayerInventoryUtils.OFF_HAND_SLOT, slot.slot());
|
||||
assertEquals(MAGIC_STACK, slot.itemStack());
|
||||
});
|
||||
|
||||
|
@ -175,8 +174,7 @@ public class InventoryIntegrationTest {
|
|||
player.getInventory().setItemStack(0, MAGIC_STACK); // Test with first inner inventory slot
|
||||
packetTracker.assertSingle(slot -> {
|
||||
assertEquals(inventory.getWindowId(), slot.windowId());
|
||||
System.out.println(slot.slot());
|
||||
assertEquals(PlayerInventoryUtils.convertToPacketSlot(0) - PlayerInventoryUtils.OFFSET + inventory.getSize(), slot.slot());
|
||||
assertEquals(PlayerInventoryUtils.minestomToProtocol(0, inventory.getSize()), slot.slot());
|
||||
assertEquals(MAGIC_STACK, slot.itemStack());
|
||||
});
|
||||
|
||||
|
@ -184,7 +182,7 @@ public class InventoryIntegrationTest {
|
|||
player.getInventory().setItemStack(35, MAGIC_STACK); // Test with last inner inventory slot
|
||||
packetTracker.assertSingle(slot -> {
|
||||
assertEquals(inventory.getWindowId(), slot.windowId());
|
||||
assertEquals(PlayerInventoryUtils.convertToPacketSlot(35) - PlayerInventoryUtils.OFFSET + inventory.getSize(), slot.slot());
|
||||
assertEquals(PlayerInventoryUtils.minestomToProtocol(35, inventory.getSize()), slot.slot());
|
||||
assertEquals(MAGIC_STACK, slot.itemStack());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class InventoryTest {
|
|||
|
||||
@Test
|
||||
public void testCreation() {
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
assertEquals(InventoryType.CHEST_1_ROW, inventory.getInventoryType());
|
||||
assertEquals(Component.text("title"), inventory.getTitle());
|
||||
|
||||
|
@ -30,7 +30,7 @@ public class InventoryTest {
|
|||
var item1 = ItemStack.of(Material.DIAMOND);
|
||||
var item2 = ItemStack.of(Material.GOLD_INGOT);
|
||||
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
assertSame(ItemStack.AIR, inventory.getItemStack(0));
|
||||
inventory.setItemStack(0, item1);
|
||||
assertSame(item1, inventory.getItemStack(0));
|
||||
|
@ -54,7 +54,7 @@ public class InventoryTest {
|
|||
@Test
|
||||
public void testTake() {
|
||||
ItemStack item = ItemStack.of(Material.DIAMOND, 32);
|
||||
Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, "title");
|
||||
inventory.setItemStack(0, item);
|
||||
assertTrue(inventory.takeItemStack(item, TransactionOption.DRY_RUN));
|
||||
assertTrue(inventory.takeItemStack(item.withAmount(31), TransactionOption.DRY_RUN));
|
||||
|
@ -67,7 +67,7 @@ public class InventoryTest {
|
|||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
Inventory inventory = new Inventory(InventoryType.HOPPER, "title");
|
||||
ContainerInventory inventory = new ContainerInventory(InventoryType.HOPPER, "title");
|
||||
assertTrue(inventory.addItemStack(ItemStack.of(Material.DIAMOND, 32), TransactionOption.ALL_OR_NOTHING));
|
||||
assertTrue(inventory.addItemStack(ItemStack.of(Material.GOLD_BLOCK, 32), TransactionOption.ALL_OR_NOTHING));
|
||||
assertTrue(inventory.addItemStack(ItemStack.of(Material.MAP, 32), TransactionOption.ALL_OR_NOTHING));
|
||||
|
@ -79,7 +79,7 @@ public class InventoryTest {
|
|||
@Test
|
||||
public void testIds() {
|
||||
for (int i = 0; i <= 1000; ++i) {
|
||||
final byte windowId = new Inventory(InventoryType.CHEST_1_ROW, "title").getWindowId();
|
||||
final byte windowId = new ContainerInventory(InventoryType.CHEST_1_ROW, "title").getWindowId();
|
||||
assertTrue(windowId > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ public class PlayerCreativeSlotTest {
|
|||
assertEquals(instance, player.getInstance());
|
||||
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
player.addPacketToQueue(new ClientCreativeInventoryActionPacket((short) PlayerInventoryUtils.OFFHAND_SLOT, ItemStack.of(Material.STICK)));
|
||||
player.addPacketToQueue(new ClientCreativeInventoryActionPacket((short) PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.STICK)));
|
||||
player.interpretPacketQueue();
|
||||
assertEquals(Material.STICK, player.getInventory().getItemInOffHand().material());
|
||||
assertEquals(Material.STICK, player.getItemInOffHand().material());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -118,19 +118,19 @@ public class PlayerInventoryIntegrationTest {
|
|||
var equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
|
||||
|
||||
// Setting to an item should send EntityEquipmentPacket to viewer
|
||||
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
|
||||
playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
|
||||
equipmentTracker.assertSingle(entityEquipmentPacket -> {
|
||||
assertEquals(MAGIC_STACK, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET));
|
||||
});
|
||||
|
||||
// Setting to the same item shouldn't send packet
|
||||
equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
|
||||
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
|
||||
playerArmored.setEquipment(EquipmentSlot.HELMET, MAGIC_STACK);
|
||||
equipmentTracker.assertEmpty();
|
||||
|
||||
// Setting to air should send packet
|
||||
equipmentTracker = connectionViewer.trackIncoming(EntityEquipmentPacket.class);
|
||||
playerArmored.getInventory().setEquipment(EquipmentSlot.HELMET, ItemStack.AIR);
|
||||
playerArmored.setEquipment(EquipmentSlot.HELMET, ItemStack.AIR);
|
||||
equipmentTracker.assertSingle(entityEquipmentPacket -> {
|
||||
assertEquals(ItemStack.AIR, entityEquipmentPacket.equipments().get(EquipmentSlot.HELMET));
|
||||
});
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
package net.minestom.server.inventory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test conversion from packet slots to internal ones (used in events and inventory methods)
|
||||
*/
|
||||
public class PlayerSlotConversionTest {
|
||||
|
||||
@Test
|
||||
public void hotbar() {
|
||||
// Convert 36-44 into 0-8
|
||||
for (int i = 0; i < 9; i++) {
|
||||
assertEquals(i, convertPlayerInventorySlot(i + 36, OFFSET));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mainInventory() {
|
||||
// No conversion, slots should stay 9-35
|
||||
for (int i = 9; i < 9 * 4; i++) {
|
||||
assertEquals(i, convertPlayerInventorySlot(i, OFFSET));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armor() {
|
||||
assertEquals(HELMET_SLOT, 41);
|
||||
assertEquals(CHESTPLATE_SLOT, 42);
|
||||
assertEquals(LEGGINGS_SLOT, 43);
|
||||
assertEquals(BOOTS_SLOT, 44);
|
||||
assertEquals(OFFHAND_SLOT, 45);
|
||||
|
||||
// Convert 5-8 & 45 into 41-45
|
||||
assertEquals(HELMET_SLOT, convertPlayerInventorySlot(5, OFFSET));
|
||||
assertEquals(CHESTPLATE_SLOT, convertPlayerInventorySlot(6, OFFSET));
|
||||
assertEquals(LEGGINGS_SLOT, convertPlayerInventorySlot(7, OFFSET));
|
||||
assertEquals(BOOTS_SLOT, convertPlayerInventorySlot(8, OFFSET));
|
||||
assertEquals(OFFHAND_SLOT, convertPlayerInventorySlot(45, OFFSET));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void craft() {
|
||||
assertEquals(CRAFT_RESULT, 36);
|
||||
assertEquals(CRAFT_SLOT_1, 37);
|
||||
assertEquals(CRAFT_SLOT_2, 38);
|
||||
assertEquals(CRAFT_SLOT_3, 39);
|
||||
assertEquals(CRAFT_SLOT_4, 40);
|
||||
|
||||
// Convert 0-4 into 36-40
|
||||
assertEquals(CRAFT_RESULT, convertPlayerInventorySlot(0, OFFSET));
|
||||
assertEquals(CRAFT_SLOT_1, convertPlayerInventorySlot(1, OFFSET));
|
||||
assertEquals(CRAFT_SLOT_2, convertPlayerInventorySlot(2, OFFSET));
|
||||
assertEquals(CRAFT_SLOT_3, convertPlayerInventorySlot(3, OFFSET));
|
||||
assertEquals(CRAFT_SLOT_4, convertPlayerInventorySlot(4, OFFSET));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.GameMode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
import static net.minestom.server.network.packet.client.play.ClientClickWindowPacket.ClickType.*;
|
||||
|
||||
public class ClickPreprocessorTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPickupType() {
|
||||
assertProcessed(new Click.Info.LeftDropCursor(), clickPacket(PICKUP, 1, 0, -999));
|
||||
assertProcessed(new Click.Info.RightDropCursor(), clickPacket(PICKUP, 1, 1, -999));
|
||||
assertProcessed(new Click.Info.MiddleDropCursor(), clickPacket(CLONE, 1, 2, -999));
|
||||
|
||||
assertProcessed(new Click.Info.Left(0), clickPacket(PICKUP, 1, 0, 0));
|
||||
assertProcessed(new Click.Info.Left(SIZE), clickPacket(PICKUP, 1, 0, 5));
|
||||
assertProcessed(null, clickPacket(PICKUP, 1, 0, 99));
|
||||
|
||||
assertProcessed(new Click.Info.Right(0), clickPacket(PICKUP, 1, 1, 0));
|
||||
assertProcessed(new Click.Info.Right(SIZE), clickPacket(PICKUP, 1, 1, 5));
|
||||
assertProcessed(null, clickPacket(PICKUP, 1, 1, 99));
|
||||
|
||||
assertProcessed(null, clickPacket(PICKUP, 1, -1, 0));
|
||||
assertProcessed(null, clickPacket(PICKUP, 1, 2, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuickMoveType() {
|
||||
assertProcessed(new Click.Info.LeftShift(0), clickPacket(QUICK_MOVE, 1, 0, 0));
|
||||
assertProcessed(new Click.Info.LeftShift(SIZE), clickPacket(QUICK_MOVE, 1, 0, 5));
|
||||
assertProcessed(null, clickPacket(QUICK_MOVE, 1, 0, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwapType() {
|
||||
assertProcessed(null, clickPacket(SWAP, 1, 0, -1));
|
||||
assertProcessed(new Click.Info.HotbarSwap(0, 2), clickPacket(SWAP, 1, 0, 2));
|
||||
assertProcessed(new Click.Info.HotbarSwap(8, 2), clickPacket(SWAP, 1, 8, 2));
|
||||
assertProcessed(new Click.Info.OffhandSwap(2), clickPacket(SWAP, 1, 40, 2));
|
||||
|
||||
assertProcessed(null, clickPacket(SWAP, 1, 9, 2));
|
||||
assertProcessed(null, clickPacket(SWAP, 1, 39, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneType() {
|
||||
var player = createPlayer();
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
|
||||
assertProcessed(null, clickPacket(CLONE, 1, 0, 0));
|
||||
assertProcessed(player, new Click.Info.Middle(0), clickPacket(CLONE, 1, 0, 0));
|
||||
assertProcessed(player, null, clickPacket(CLONE, 1, 0, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowType() {
|
||||
assertProcessed(new Click.Info.DropSlot(0, true), clickPacket(THROW, 1, 1, 0));
|
||||
|
||||
assertProcessed(new Click.Info.DropSlot(0, false), clickPacket(THROW, 1, 0, 0));
|
||||
assertProcessed(new Click.Info.DropSlot(0, true), clickPacket(THROW, 1, 1, 0));
|
||||
|
||||
assertProcessed(new Click.Info.DropSlot(1, false), clickPacket(THROW, 1, 0, 1));
|
||||
assertProcessed(new Click.Info.DropSlot(1, true), clickPacket(THROW, 1, 1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuickCraft() {
|
||||
var processor = createPreprocessor();
|
||||
var player = createPlayer();
|
||||
|
||||
assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 8, 0));
|
||||
assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 9, 0));
|
||||
assertProcessed(player, null, clickPacket(QUICK_CRAFT, 1, 10, 0));
|
||||
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 0, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 1, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 1, 1));
|
||||
assertProcessed(processor, player, new Click.Info.LeftDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 2, -999));
|
||||
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 4, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 5, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 5, 1));
|
||||
assertProcessed(processor, player, new Click.Info.RightDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 6, -999));
|
||||
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 8, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 9, 0));
|
||||
assertProcessed(processor, player, null, clickPacket(QUICK_CRAFT, 1, 9, 1));
|
||||
assertProcessed(processor, player, new Click.Info.MiddleDrag(IntList.of(0, 1)), clickPacket(QUICK_CRAFT, 1, 10, -999));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package net.minestom.server.inventory.click;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
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.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public final class ClickUtils {
|
||||
public static final @NotNull InventoryType TYPE = InventoryType.HOPPER;
|
||||
|
||||
public static final int SIZE = TYPE.getSize(); // Default hopper size
|
||||
|
||||
public static @NotNull Inventory createInventory() {
|
||||
return new ContainerInventory(TYPE, "TestInventory");
|
||||
}
|
||||
|
||||
public static @NotNull Click.Preprocessor createPreprocessor() {
|
||||
return new Click.Preprocessor();
|
||||
}
|
||||
|
||||
public static @NotNull ItemStack magic(int amount) {
|
||||
return ItemStack.of(Material.STONE, amount);
|
||||
}
|
||||
|
||||
public static @NotNull ItemStack magic2(int amount) {
|
||||
return ItemStack.of(Material.DIRT, amount);
|
||||
}
|
||||
|
||||
public static @NotNull Player createPlayer() {
|
||||
return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() {
|
||||
@Override
|
||||
public void sendPacket(@NotNull SendablePacket packet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull SocketAddress getRemoteAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void assertClick(@NotNull List<Click.Change> initial, @NotNull Click.Info info, @NotNull List<Click.Change> expected) {
|
||||
var player = createPlayer();
|
||||
var inventory = createInventory();
|
||||
|
||||
ContainerInventory.apply(initial, player, inventory);
|
||||
var actual = inventory.handleClick(player, info);
|
||||
|
||||
assertChanges(expected, actual, inventory.getSize());
|
||||
}
|
||||
|
||||
public static void assertPlayerClick(@NotNull List<Click.Change> initial, @NotNull Click.Info info, @NotNull List<Click.Change> expected) {
|
||||
var player = createPlayer();
|
||||
var inventory = player.getInventory();
|
||||
|
||||
ContainerInventory.apply(initial, player, inventory);
|
||||
var actual = inventory.handleClick(player, info);
|
||||
|
||||
assertChanges(expected, actual, inventory.getSize());
|
||||
}
|
||||
|
||||
public static void assertChanges(List<Click.Change> expected, List<Click.Change> actual, int size) {
|
||||
assertEquals(ChangeResult.make(expected, size), ChangeResult.make(actual, size));
|
||||
}
|
||||
|
||||
private record ChangeResult(Map<Integer, ItemStack> main, Map<Integer, ItemStack> player,
|
||||
@Nullable ItemStack cursor, List<ItemStack> drops) {
|
||||
private static ChangeResult make(@NotNull List<Click.Change> changes, int size) {
|
||||
Map<Integer, ItemStack> main = new HashMap<>();
|
||||
Map<Integer, ItemStack> player = new HashMap<>();
|
||||
@Nullable ItemStack cursor = null;
|
||||
List<ItemStack> drops = new ArrayList<>();
|
||||
|
||||
for (var change : changes) {
|
||||
switch (change) {
|
||||
case Click.Change.Container(int slot, ItemStack item) -> {
|
||||
if (slot < size) {
|
||||
main.put(slot, item);
|
||||
} else {
|
||||
player.put(PlayerInventoryUtils.protocolToMinestom(slot, size), item);
|
||||
}
|
||||
}
|
||||
case Click.Change.Player(int slot, ItemStack item) -> player.put(slot, item);
|
||||
case Click.Change.Cursor(ItemStack item) -> cursor = item;
|
||||
case Click.Change.DropFromPlayer(ItemStack item) -> drops.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return new ChangeResult(main, player, cursor, drops);
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertProcessed(@NotNull Click.Preprocessor preprocessor, @NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) {
|
||||
assertEquals(info, preprocessor.processClick(packet, player.isCreative(), createInventory().getSize()));
|
||||
}
|
||||
|
||||
public static void assertProcessed(@NotNull Player player, @Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) {
|
||||
assertProcessed(new Click.Preprocessor(), player, info, packet);
|
||||
}
|
||||
|
||||
public static void assertProcessed(@Nullable Click.Info info, @NotNull ClientClickWindowPacket packet) {
|
||||
assertProcessed(createPlayer(), info, packet);
|
||||
}
|
||||
|
||||
public static @NotNull ClientClickWindowPacket clickPacket(@NotNull ClientClickWindowPacket.ClickType type, int windowId, int button, int slot) {
|
||||
return new ClientClickWindowPacket((byte) windowId, 0, (short) slot, (byte) button, type, List.of(), ItemStack.AIR);
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
package net.minestom.server.inventory.click.integration;
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.inventory.InventoryPreClickEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnvTest
|
||||
public class HeldClickIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void heldSelf(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = player.getInventory();
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
inventory.setItemStack(2, ItemStack.of(Material.GOLD_INGOT));
|
||||
inventory.setItemStack(3, ItemStack.of(Material.EGG));
|
||||
inventory.setItemStack(6, ItemStack.of(Material.DIAMOND));
|
||||
// Empty
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertTrue(event.getSlot() == 4 || event.getSlot() == 5);
|
||||
assertEquals(ClickType.CHANGE_HELD, event.getClickType());
|
||||
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, event.getCursorItem());
|
||||
|
||||
assertEquals(ItemStack.AIR, event.getClickedItem());
|
||||
});
|
||||
heldClick(player, 4, 5);
|
||||
}
|
||||
// Swap air
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertTrue(event.getSlot() == 1 || event.getSlot() == 0);
|
||||
assertEquals(ClickType.CHANGE_HELD, event.getClickType());
|
||||
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, event.getCursorItem());
|
||||
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), event.getClickedItem());
|
||||
});
|
||||
heldClick(player, 1, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(0));
|
||||
}
|
||||
// Swap items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertTrue(event.getSlot() == 0 || event.getSlot() == 2);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
});
|
||||
heldClick(player, 0, 2);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
|
||||
assertEquals(ItemStack.of(Material.GOLD_INGOT), inventory.getItemStack(0));
|
||||
}
|
||||
// Swap offhand
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertTrue(event.getSlot() == 3 || event.getSlot() == 45 /* Vanilla offhand slot is 40, Minestom is 45 */);
|
||||
});
|
||||
heldClick(player, 3, 40);
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(3));
|
||||
assertEquals(ItemStack.of(Material.EGG), inventory.getItemInOffHand());
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
heldClick(player, 2, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
|
||||
assertEquals(ItemStack.of(Material.GOLD_INGOT), inventory.getItemStack(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void heldExternal(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = new Inventory(InventoryType.HOPPER, "test");
|
||||
var playerInv = player.getInventory();
|
||||
player.openInventory(inventory);
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
inventory.setItemStack(2, ItemStack.of(Material.GOLD_INGOT));
|
||||
inventory.setItemStack(3, ItemStack.of(Material.EGG));
|
||||
inventory.setItemStack(4, ItemStack.of(Material.DIAMOND));
|
||||
// Empty
|
||||
{
|
||||
listener.followup(event -> {
|
||||
if (event.getInventory() != null) assertEquals(inventory, event.getInventory());
|
||||
assertEquals(0, event.getSlot());
|
||||
assertEquals(ClickType.CHANGE_HELD, event.getClickType());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
});
|
||||
heldClickOpenInventory(player, 0, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(0));
|
||||
}
|
||||
// Swap empty
|
||||
{
|
||||
listener.followup(event -> {
|
||||
if (event.getInventory() != null) assertEquals(inventory, event.getInventory());
|
||||
assertTrue(event.getSlot() == 1 || event.getSlot() == 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
});
|
||||
heldClickOpenInventory(player, 1, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), playerInv.getItemStack(0));
|
||||
}
|
||||
// Swap items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
if (event.getInventory() != null) assertEquals(inventory, event.getInventory());
|
||||
assertTrue(event.getSlot() == 2 || event.getSlot() == 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
});
|
||||
heldClickOpenInventory(player, 2, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
|
||||
assertEquals(ItemStack.of(Material.GOLD_INGOT), playerInv.getItemStack(0));
|
||||
}
|
||||
// Swap offhand
|
||||
{
|
||||
listener.followup(event -> {
|
||||
if (event.getInventory() != null) assertEquals(inventory, event.getInventory());
|
||||
assertTrue(event.getSlot() == 3 || event.getSlot() == 45);
|
||||
});
|
||||
heldClickOpenInventory(player, 3, 40);
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(3));
|
||||
assertEquals(ItemStack.of(Material.EGG), playerInv.getItemInOffHand());
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
heldClickOpenInventory(player, 2, 0);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
|
||||
assertEquals(ItemStack.of(Material.GOLD_INGOT), playerInv.getItemStack(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void heldClickOpenInventory(Player player, int slot, int target) {
|
||||
_heldClick(player.getOpenInventory(), true, player, slot, target);
|
||||
}
|
||||
|
||||
private void heldClick(Player player, int slot, int target) {
|
||||
_heldClick(player.getOpenInventory(), false, player, slot, target);
|
||||
}
|
||||
|
||||
private void _heldClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot, int target) {
|
||||
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
|
||||
if (clickOpenInventory) {
|
||||
assert openInventory != null;
|
||||
// Do not touch slot
|
||||
} else {
|
||||
int offset = openInventory != null ? openInventory.getInnerSize() : 0;
|
||||
slot = PlayerInventoryUtils.convertToPacketSlot(slot);
|
||||
if (openInventory != null) {
|
||||
slot = slot - 9 + offset;
|
||||
}
|
||||
}
|
||||
player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) target,
|
||||
ClientClickWindowPacket.ClickType.SWAP, List.of(), ItemStack.AIR));
|
||||
player.interpretPacketQueue();
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
package net.minestom.server.inventory.click.integration;
|
||||
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.inventory.InventoryPreClickEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@EnvTest
|
||||
public class LeftClickIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void leftSelf(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = player.getInventory();
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
// Empty click
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertEquals(0, event.getSlot());
|
||||
assertEquals(ClickType.LEFT_CLICK, event.getClickType());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
});
|
||||
leftClick(player, 0);
|
||||
}
|
||||
// Pickup diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
});
|
||||
leftClick(player, 1);
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
}
|
||||
// Place it back
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
});
|
||||
leftClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
leftClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(), "Left click cancellation did not work");
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
}
|
||||
// Change items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
event.setCursorItem(ItemStack.of(Material.DIAMOND));
|
||||
});
|
||||
leftClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 6), inventory.getItemStack(1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void leftExternal(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = new Inventory(InventoryType.HOPPER, "test");
|
||||
player.openInventory(inventory);
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
// Empty click in player inv
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertEquals(0, event.getSlot());
|
||||
assertEquals(ClickType.LEFT_CLICK, event.getClickType());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
});
|
||||
leftClick(player, 0);
|
||||
}
|
||||
// Pickup diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(inventory, event.getInventory());
|
||||
assertEquals(1, event.getSlot());
|
||||
// Ensure that the inventory didn't change yet
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
});
|
||||
leftClickOpenInventory(player, 1);
|
||||
// Verify inventory changes
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
}
|
||||
// Place it back
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(inventory, event.getInventory());
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
});
|
||||
leftClickOpenInventory(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
leftClickOpenInventory(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player), "Left click cancellation did not work");
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
}
|
||||
// Change items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory());
|
||||
assertEquals(9, event.getSlot());
|
||||
event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
event.setCursorItem(ItemStack.of(Material.DIAMOND));
|
||||
});
|
||||
leftClick(player, 9);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 6), player.getInventory().getItemStack(9));
|
||||
}
|
||||
}
|
||||
|
||||
private void leftClickOpenInventory(Player player, int slot) {
|
||||
_leftClick(player.getOpenInventory(), true, player, slot);
|
||||
}
|
||||
|
||||
private void leftClick(Player player, int slot) {
|
||||
_leftClick(player.getOpenInventory(), false, player, slot);
|
||||
}
|
||||
|
||||
private void _leftClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) {
|
||||
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
|
||||
if (clickOpenInventory) {
|
||||
assert openInventory != null;
|
||||
// Do not touch slot
|
||||
} else {
|
||||
int offset = openInventory != null ? openInventory.getInnerSize() : 0;
|
||||
slot = PlayerInventoryUtils.convertToPacketSlot(slot);
|
||||
if (openInventory != null) {
|
||||
slot = slot - 9 + offset;
|
||||
}
|
||||
}
|
||||
player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) 0,
|
||||
ClientClickWindowPacket.ClickType.PICKUP, List.of(), ItemStack.AIR));
|
||||
player.interpretPacketQueue();
|
||||
}
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
package net.minestom.server.inventory.click.integration;
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.inventory.InventoryPreClickEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@EnvTest
|
||||
public class RightClickIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void rightSelf(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = player.getInventory();
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
inventory.setItemStack(2, ItemStack.of(Material.DIAMOND));
|
||||
// Empty click
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertEquals(0, event.getSlot());
|
||||
assertEquals(ClickType.RIGHT_CLICK, event.getClickType());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
});
|
||||
rightClick(player, 0);
|
||||
}
|
||||
// Pickup diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
});
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
}
|
||||
// Place it back
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
});
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
}
|
||||
// Pickup diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
});
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
}
|
||||
// Stack diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(2, event.getSlot());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
|
||||
});
|
||||
rightClick(player, 2);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2));
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
rightClick(player, 2);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(), "Left click cancellation did not work");
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2));
|
||||
}
|
||||
// Change items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
event.setCursorItem(ItemStack.of(Material.DIAMOND));
|
||||
});
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 6), inventory.getItemStack(1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rightExternal(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||
var inventory = new Inventory(InventoryType.HOPPER, "test");
|
||||
player.openInventory(inventory);
|
||||
var listener = env.listen(InventoryPreClickEvent.class);
|
||||
inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
|
||||
// Empty click in player inv
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory()); // Player inventory
|
||||
assertEquals(0, event.getSlot());
|
||||
assertEquals(ClickType.RIGHT_CLICK, event.getClickType());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
});
|
||||
rightClick(player, 0);
|
||||
}
|
||||
// Pickup diamond
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertEquals(inventory, event.getInventory());
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
|
||||
});
|
||||
rightClickOpenInventory(player, 1);
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
}
|
||||
// Place back to player inv
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory());
|
||||
assertEquals(1, event.getSlot());
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.AIR, inventory.getItemStack(1));
|
||||
assertEquals(ItemStack.AIR, player.getInventory().getItemStack(1));
|
||||
});
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1));
|
||||
}
|
||||
// Cancel event
|
||||
{
|
||||
listener.followup(event -> event.setCancelled(true));
|
||||
rightClick(player, 1);
|
||||
assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1), "Left click cancellation did not work");
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
}
|
||||
// Change items
|
||||
{
|
||||
listener.followup(event -> {
|
||||
assertNull(event.getInventory());
|
||||
assertEquals(9, event.getSlot());
|
||||
event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
event.setCursorItem(ItemStack.of(Material.DIAMOND));
|
||||
});
|
||||
rightClick(player, 9);
|
||||
assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
|
||||
assertEquals(ItemStack.of(Material.DIAMOND, 6), player.getInventory().getItemStack(9));
|
||||
}
|
||||
}
|
||||
|
||||
private void rightClickOpenInventory(Player player, int slot) {
|
||||
_rightClick(player.getOpenInventory(), true, player, slot);
|
||||
}
|
||||
|
||||
private void rightClick(Player player, int slot) {
|
||||
_rightClick(player.getOpenInventory(), false, player, slot);
|
||||
}
|
||||
|
||||
private void _rightClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) {
|
||||
final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
|
||||
if (clickOpenInventory) {
|
||||
assert openInventory != null;
|
||||
// Do not touch slot
|
||||
} else {
|
||||
int offset = openInventory != null ? openInventory.getInnerSize() : 0;
|
||||
slot = PlayerInventoryUtils.convertToPacketSlot(slot);
|
||||
if (openInventory != null) {
|
||||
slot = slot - 9 + offset;
|
||||
}
|
||||
}
|
||||
player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) 1,
|
||||
ClientClickWindowPacket.ClickType.PICKUP, List.of(), ItemStack.AIR));
|
||||
player.interpretPacketQueue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.DropFromPlayer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryCreativeDropItemTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropItem() {
|
||||
assertClick(
|
||||
List.of(),
|
||||
new Click.Info.CreativeDropItem(magic(64)),
|
||||
List.of(new DropFromPlayer(magic(64)))
|
||||
);
|
||||
|
||||
// Make sure it doesn't drop a full stack
|
||||
assertClick(
|
||||
List.of(),
|
||||
new Click.Info.CreativeDropItem(magic(1)),
|
||||
List.of(new DropFromPlayer(magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryCreativeSetItemTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetItem() {
|
||||
assertClick(
|
||||
List.of(),
|
||||
new Click.Info.CreativeSetItem(0, magic(64)),
|
||||
List.of(new Container(0, magic(64)))
|
||||
);
|
||||
|
||||
// Make sure it doesn't set a full stack
|
||||
assertClick(
|
||||
List.of(),
|
||||
new Click.Info.CreativeSetItem(0, magic(1)),
|
||||
List.of(new Container(0, magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryDoubleClickTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.Double(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotTakeAny() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialTake() {
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(48)), new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of(new Container(1, magic(16)), new Cursor(magic(64)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTakeAll() {
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(32)), new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of(new Container(1, ItemStack.AIR), new Cursor(magic(64)))
|
||||
);
|
||||
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(16)), new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of(new Container(1, ItemStack.AIR), new Cursor(magic(48)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTakeSeparated() {
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(16)), new Container(2, magic(16)), new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of(new Container(1, ItemStack.AIR), new Container(2, ItemStack.AIR), new Cursor(magic(64)))
|
||||
);
|
||||
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(16)), new Container(2, magic(32)), new Cursor(magic(32))),
|
||||
new Click.Info.Double(0),
|
||||
List.of(new Container(1, ItemStack.AIR), new Container(2, magic(16)), new Cursor(magic(64)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCursorFull() {
|
||||
assertClick(
|
||||
List.of(new Container(1, magic(48)), new Cursor(magic(64))),
|
||||
new Click.Info.Double(0),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.DropFromPlayer;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryDropCursorTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.LeftDropCursor(), List.of());
|
||||
assertClick(List.of(), new Click.Info.MiddleDropCursor(), List.of());
|
||||
assertClick(List.of(), new Click.Info.RightDropCursor(), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropEntireStack() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.LeftDropCursor(),
|
||||
List.of(new Cursor(ItemStack.AIR), new DropFromPlayer(magic(32)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropSingleItem() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.RightDropCursor(),
|
||||
List.of(new Cursor(magic(31)), new DropFromPlayer(magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMiddleClickNoop() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.MiddleDropCursor(),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.DropFromPlayer;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryDropSlotTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.DropSlot(0, false), List.of());
|
||||
assertClick(List.of(), new Click.Info.DropSlot(0, true), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropEntireStack() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32))),
|
||||
new Click.Info.DropSlot(0, true),
|
||||
List.of(new Container(0, ItemStack.AIR), new DropFromPlayer(magic(32)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropSingleItem() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32))),
|
||||
new Click.Info.DropSlot(0, false),
|
||||
List.of(new Container(0, magic(31)), new DropFromPlayer(magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.inventory.click.Click.Change.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
|
||||
public class InventoryHotbarSwapTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
assertClick(List.of(), new Click.Info.HotbarSwap(i, 9), List.of());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwappedItems() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic2(1)), new Player(0, magic(1))),
|
||||
new Click.Info.HotbarSwap(0, 0),
|
||||
List.of(new Container(0, magic(1)), new Player(0, magic2(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
|
||||
public class InventoryLeftClickTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.Left(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertEntireStack() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32)), new Click.Change.Cursor(magic(32))),
|
||||
new Click.Info.Left(0),
|
||||
List.of(new Container(0, magic(64)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertPartialStack() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32)), new Cursor(magic(48))),
|
||||
new Click.Info.Left(0),
|
||||
List.of(new Container(0, magic(64)), new Cursor(magic(16)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchItems() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic2(1))),
|
||||
new Click.Info.Left(0),
|
||||
List.of(new Container(0, magic2(1)), new Cursor(magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryLeftDragTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCursor() {
|
||||
assertClick(List.of(), new Click.Info.LeftDrag(IntList.of(0)), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeNone() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of()),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOne() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of(0)),
|
||||
List.of(new Container(0, magic(32)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeExactlyEnough() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of(0, 1)),
|
||||
List.of(new Container(0, magic(16)), new Container(1, magic(16)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(30))),
|
||||
new Click.Info.LeftDrag(IntList.of(0, 1, 2)),
|
||||
List.of(
|
||||
new Container(0, magic(10)),
|
||||
new Container(1, magic(10)),
|
||||
new Container(2, magic(10)),
|
||||
new Cursor(ItemStack.AIR)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainderItems() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of(0, 1, 2)),
|
||||
List.of(
|
||||
new Container(0, magic(10)),
|
||||
new Container(1, magic(10)),
|
||||
new Container(2, magic(10)),
|
||||
new Cursor(magic(2))
|
||||
)
|
||||
);
|
||||
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(25))),
|
||||
new Click.Info.LeftDrag(IntList.of(0, 1, 2, 3)),
|
||||
List.of(
|
||||
new Container(0, magic(6)),
|
||||
new Container(1, magic(6)),
|
||||
new Container(2, magic(6)),
|
||||
new Container(3, magic(6)),
|
||||
new Cursor(magic(1))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOverExisting() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(16)), new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of(0)),
|
||||
List.of(new Container(0, magic(48)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOverFull() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(64)), new Cursor(magic(32))),
|
||||
new Click.Info.LeftDrag(IntList.of(0)),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryMiddleClickTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.Middle(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(64))),
|
||||
new Click.Info.Middle(0),
|
||||
List.of(new Cursor(magic(64)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyNotFull() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32))),
|
||||
new Click.Info.Middle(0),
|
||||
List.of(new Cursor(magic(64)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
|
||||
public class InventoryMiddleDragTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.MiddleDrag(IntList.of()), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingSlots() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic2(1))),
|
||||
new Click.Info.MiddleDrag(IntList.of(0)),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialExistingSlots() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic2(1))),
|
||||
new Click.Info.MiddleDrag(IntList.of(0, 1)),
|
||||
List.of(new Container(1, magic2(1)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullCopy() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(1))),
|
||||
new Click.Info.MiddleDrag(IntList.of(0, 1)),
|
||||
List.of(new Container(0, magic(1)), new Container(1, magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import net.minestom.server.inventory.click.Click.Change.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.OFF_HAND_SLOT;
|
||||
|
||||
public class InventoryOffhandSwapTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.OffhandSwap(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwappedItems() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic2(1)), new Player(OFF_HAND_SLOT, magic(1))),
|
||||
new Click.Info.OffhandSwap(0),
|
||||
List.of(new Container(0, magic(1)), new Player(OFF_HAND_SLOT, magic2(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.Cursor;
|
||||
import net.minestom.server.inventory.click.Click.Change.Container;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
|
||||
public class InventoryRightClickTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.Right(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOne() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32)), new Cursor(magic(32))),
|
||||
new Click.Info.Right(0),
|
||||
List.of(new Container(0, magic(33)), new Cursor(magic(31)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClickedStackFull() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(64)), new Cursor(magic(32))),
|
||||
new Click.Info.Right(0),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTakeHalf() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(32))),
|
||||
new Click.Info.Right(0),
|
||||
List.of(new Container(0, magic(16)), new Cursor(magic(16)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaveOne() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.Right(0),
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic(31)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchItems() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic2(1))),
|
||||
new Click.Info.Right(0),
|
||||
List.of(new Container(0, magic2(1)), new Cursor(magic(1)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.*;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.assertClick;
|
||||
import static net.minestom.server.inventory.click.ClickUtils.magic;
|
||||
|
||||
public class InventoryRightDragTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCursor() {
|
||||
assertClick(List.of(), new Click.Info.RightDrag(IntList.of(0)), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeNone() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.RightDrag(IntList.of()),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOne() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(32))),
|
||||
new Click.Info.RightDrag(IntList.of(0)),
|
||||
List.of(new Container(0, magic(1)), new Cursor(magic(31)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeExactlyEnough() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(2))),
|
||||
new Click.Info.RightDrag(IntList.of(0, 1)),
|
||||
List.of(new Container(0, magic(1)), new Container(1, magic(1)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooManySlots() {
|
||||
assertClick(
|
||||
List.of(new Cursor(magic(2))),
|
||||
new Click.Info.RightDrag(IntList.of(0, 1, 2)),
|
||||
List.of(new Container(0, magic(1)), new Container(1, magic(1)), new Cursor(ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOverExisting() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(16)), new Cursor(magic(32))),
|
||||
new Click.Info.RightDrag(IntList.of(0)),
|
||||
List.of(new Container(0, magic(17)), new Cursor(magic(31)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistributeOverFull() {
|
||||
assertClick(
|
||||
List.of(new Container(0, magic(64)), new Cursor(magic(32))),
|
||||
new Click.Info.RightDrag(IntList.of(0)),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package net.minestom.server.inventory.click.type;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.inventory.click.Click.Change.*;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.inventory.click.ClickUtils.*;
|
||||
|
||||
public class InventoryShiftClickTest {
|
||||
|
||||
static {
|
||||
MinecraftServer.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChanges() {
|
||||
assertClick(List.of(), new Click.Info.LeftShift(0), List.of());
|
||||
assertClick(List.of(), new Click.Info.RightShift(0), List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTransfer() {
|
||||
assertClick(
|
||||
List.of(new Player(9, magic(32))),
|
||||
new Click.Info.LeftShift(SIZE),
|
||||
List.of(new Container(0, magic(32)), new Player(9, ItemStack.AIR))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeparatedTransfer() {
|
||||
assertClick(
|
||||
List.of(
|
||||
new Player(9, magic(64)),
|
||||
new Container(0, magic(32)),
|
||||
new Container(1, magic(32))
|
||||
),
|
||||
new Click.Info.LeftShift(SIZE),
|
||||
List.of(
|
||||
new Player(9, ItemStack.AIR),
|
||||
new Container(0, magic(64)),
|
||||
new Container(1, magic(64))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeparatedAndNewTransfer() {
|
||||
assertClick(
|
||||
List.of(
|
||||
new Player(9, magic(64)),
|
||||
new Container(0, magic(32))
|
||||
),
|
||||
new Click.Info.LeftShift(SIZE),
|
||||
List.of(
|
||||
new Player(9, ItemStack.AIR),
|
||||
new Container(0, magic(64)),
|
||||
new Container(1, magic(32))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialTransfer() {
|
||||
assertClick(
|
||||
List.of(
|
||||
new Player(9, magic(64)),
|
||||
new Container(0, magic(32)),
|
||||
new Container(1, magic2(1)),
|
||||
new Container(2, magic2(1)),
|
||||
new Container(3, magic2(1)),
|
||||
new Container(4, magic2(1))
|
||||
),
|
||||
new Click.Info.LeftShift(SIZE),
|
||||
List.of(new Player(9, magic(32)), new Container(0, magic(64)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotTransfer() {
|
||||
assertClick(
|
||||
List.of(
|
||||
new Player(9, magic(64)),
|
||||
new Container(0, magic(64)),
|
||||
new Container(1, magic2(1)),
|
||||
new Container(2, magic2(1)),
|
||||
new Container(3, magic2(1)),
|
||||
new Container(4, magic2(1))
|
||||
),
|
||||
new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9
|
||||
List.of()
|
||||
);
|
||||
|
||||
assertClick(
|
||||
List.of(
|
||||
new Player(9, magic(64)),
|
||||
new Container(0, magic2(1)),
|
||||
new Container(1, magic2(1)),
|
||||
new Container(2, magic2(1)),
|
||||
new Container(3, magic2(1)),
|
||||
new Container(4, magic2(1))
|
||||
),
|
||||
new Click.Info.LeftShift(SIZE), // Equivalent to player slot 9
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayerInteraction() {
|
||||
assertPlayerClick(
|
||||
List.of(new Container(9, magic(32))),
|
||||
new Click.Info.LeftShift(9),
|
||||
List.of(new Container(9, ItemStack.AIR), new Container(0, magic(32)))
|
||||
);
|
||||
|
||||
assertPlayerClick(
|
||||
List.of(new Container(8, magic(32))),
|
||||
new Click.Info.LeftShift(8),
|
||||
List.of(new Container(8, ItemStack.AIR), new Container(9, magic(32)))
|
||||
);
|
||||
|
||||
assertPlayerClick(
|
||||
List.of(new Container(9, ItemStack.of(Material.IRON_CHESTPLATE))),
|
||||
new Click.Info.LeftShift(9),
|
||||
List.of(new Container(9, ItemStack.AIR), new Container(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE)))
|
||||
);
|
||||
|
||||
assertPlayerClick(
|
||||
List.of(new Container(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.of(Material.IRON_CHESTPLATE))),
|
||||
new Click.Info.LeftShift(PlayerInventoryUtils.CHESTPLATE_SLOT),
|
||||
List.of(new Container(PlayerInventoryUtils.CHESTPLATE_SLOT, ItemStack.AIR), new Container(9, ItemStack.of(Material.IRON_CHESTPLATE)))
|
||||
);
|
||||
|
||||
assertPlayerClick(
|
||||
List.of(new Container(9, ItemStack.of(Material.SHIELD))),
|
||||
new Click.Info.LeftShift(9),
|
||||
List.of(new Container(9, ItemStack.AIR), new Container(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD)))
|
||||
);
|
||||
|
||||
assertPlayerClick(
|
||||
List.of(new Container(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.of(Material.SHIELD))),
|
||||
new Click.Info.LeftShift(PlayerInventoryUtils.OFF_HAND_SLOT),
|
||||
List.of(new Container(PlayerInventoryUtils.OFF_HAND_SLOT, ItemStack.AIR), new Container(9, ItemStack.of(Material.SHIELD)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue