mirror of https://github.com/Minestom/Minestom.git
278 lines
9.2 KiB
Java
278 lines
9.2 KiB
Java
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;
|
|
}
|
|
}
|