Minestom/src/main/java/net/minestom/server/inventory/AbstractInventory.java

259 lines
9.8 KiB
Java

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;
}
}