Rewrote VirtualInventory

- VirtualInventory now allows for a custom max stack size on each individual slot
- Cleaned up methods, added better descriptions
- Allow changing the new item stack in the ItemUpdateEvent
- Added support for InventoryAction.HOTBAR_SWAP
This commit is contained in:
NichtStudioCode 2021-06-02 22:04:38 +02:00
parent 048568cfac
commit b17fc27ee5
6 changed files with 482 additions and 463 deletions

@ -45,23 +45,23 @@ public interface SlotElement {
class VISlotElement implements SlotElement { class VISlotElement implements SlotElement {
private final VirtualInventory virtualInventory; private final VirtualInventory virtualInventory;
private final int index; private final int slot;
public VISlotElement(VirtualInventory virtualInventory, int index) { public VISlotElement(VirtualInventory virtualInventory, int slot) {
this.virtualInventory = virtualInventory; this.virtualInventory = virtualInventory;
this.index = index; this.slot = slot;
} }
public VirtualInventory getVirtualInventory() { public VirtualInventory getVirtualInventory() {
return virtualInventory; return virtualInventory;
} }
public int getIndex() { public int getSlot() {
return index; return slot;
} }
public ItemStack getItemStack() { public ItemStack getItemStack() {
return virtualInventory.getItemStack(index); return virtualInventory.getUnsafeItemStack(slot);
} }
@Override @Override

@ -19,6 +19,7 @@ import de.studiocode.invui.window.Window;
import de.studiocode.invui.window.WindowManager; import de.studiocode.invui.window.WindowManager;
import de.studiocode.invui.window.impl.merged.MergedWindow; import de.studiocode.invui.window.impl.merged.MergedWindow;
import de.studiocode.invui.window.impl.merged.split.SplitWindow; import de.studiocode.invui.window.impl.merged.split.SplitWindow;
import de.studiocode.invui.window.impl.single.SingleWindow;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
@ -67,9 +68,10 @@ abstract class IndexedGUI implements GUI {
} else event.setCancelled(true); // Only VISlotElements have allowed interactions } else event.setCancelled(true); // Only VISlotElements have allowed interactions
} }
@SuppressWarnings("deprecation")
private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) { private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) {
VirtualInventory virtualInventory = element.getVirtualInventory(); VirtualInventory virtualInventory = element.getVirtualInventory();
int index = element.getIndex(); int slot = element.getSlot();
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
ItemStack cursor = event.getCursor(); ItemStack cursor = event.getCursor();
@ -77,8 +79,8 @@ abstract class IndexedGUI implements GUI {
UpdateReason updateReason = new PlayerUpdateReason(player, event); UpdateReason updateReason = new PlayerUpdateReason(player, event);
if (virtualInventory.isSynced(index, clicked)) { if (virtualInventory.isSynced(slot, clicked)) {
boolean cancelled = false; boolean cancel = false;
switch (event.getAction()) { switch (event.getAction()) {
@ -90,82 +92,126 @@ abstract class IndexedGUI implements GUI {
case DROP_ONE_SLOT: case DROP_ONE_SLOT:
case PICKUP_ONE: case PICKUP_ONE:
cancelled = virtualInventory.removeOne(updateReason, index); cancel = virtualInventory.changeItemAmount(updateReason, slot, -1) != -1;
break; break;
case DROP_ALL_SLOT: case DROP_ALL_SLOT:
case PICKUP_ALL: case PICKUP_ALL:
cancelled = virtualInventory.removeItem(updateReason, index); cancel = !virtualInventory.setItemStack(updateReason, slot, null);
// set null
break; break;
case PICKUP_HALF: case PICKUP_HALF:
cancelled = virtualInventory.removeHalf(updateReason, index); int amount = virtualInventory.getAmount(slot);
break; int halfAmount = amount / 2;
int newAmount = virtualInventory.changeItemAmount(updateReason, slot, halfAmount);
case PLACE_ALL: // amount did not change as predicted
cancelled = virtualInventory.place(updateReason, index, cursor); if (newAmount != halfAmount) {
break; cancel = true;
case PLACE_ONE: // action wasn't completely cancelled
cancelled = virtualInventory.placeOne(updateReason, index, cursor); if (newAmount != amount) {
break; int cursorAmount = amount - newAmount;
cancel = true;
case PLACE_SOME: ItemStack newCursorStack = clicked.clone();
cancelled = virtualInventory.setToMaxAmount(updateReason, index); newCursorStack.setAmount(cursorAmount);
break; event.setCursor(newCursorStack);
case SWAP_WITH_CURSOR:
cancelled = virtualInventory.setItemStack(updateReason, index, event.getCursor());
break;
case COLLECT_TO_CURSOR:
cancelled = true;
ItemStack newCursor = cursor.clone();
newCursor.setAmount(virtualInventory.collectToCursor(updateReason, newCursor));
player.setItemOnCursor(newCursor);
break;
case MOVE_TO_OTHER_INVENTORY:
cancelled = true;
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
ItemStack invStack = virtualInventory.getItemStack(index);
ItemUpdateEvent updateEvent = virtualInventory.createAndCallEvent(index, updateReason, invStack, null);
if (!updateEvent.isCancelled()) {
int leftOverAmount;
if (window instanceof MergedWindow) {
GUI otherGui;
if (window instanceof SplitWindow) {
SplitWindow splitWindow = (SplitWindow) window;
GUI[] guis = splitWindow.getGuis();
otherGui = guis[0] == this ? guis[1] : guis[0];
} else {
otherGui = this;
}
leftOverAmount = ((IndexedGUI) otherGui).putIntoVirtualInventories(updateReason, invStack, virtualInventory);
} else {
leftOverAmount = 0;
HashMap<Integer, ItemStack> leftover = event.getWhoClicked().getInventory().addItem(virtualInventory.getItemStack(index));
if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount();
} }
virtualInventory.setAmountSilently(index, leftOverAmount);
} }
break; break;
case PLACE_SOME:
case PLACE_ALL:
int amountLeft = virtualInventory.putItemStack(updateReason, slot, cursor);
if (amountLeft > 0) {
cancel = true;
if (amountLeft != cursor.getAmount())
cursor.setAmount(amountLeft);
}
break;
case PLACE_ONE:
ItemStack itemStack = cursor.clone();
itemStack.setAmount(1);
cancel = virtualInventory.putItemStack(updateReason, slot, itemStack) != 0;
break;
case SWAP_WITH_CURSOR:
cancel = !virtualInventory.setItemStack(updateReason, slot, event.getCursor());
break;
case COLLECT_TO_CURSOR:
cancel = true;
ItemStack newCursorStack = cursor.clone();
newCursorStack.setAmount(virtualInventory.collectToCursor(updateReason, newCursorStack));
event.setCursor(newCursorStack);
break;
case MOVE_TO_OTHER_INVENTORY:
cancel = true;
handleMoveToOtherInventory(player, event, virtualInventory, slot, updateReason);
break;
case HOTBAR_MOVE_AND_READD:
case HOTBAR_SWAP:
cancel = handleHotbarSwap(player, event, virtualInventory, slot, updateReason);
break;
default: default:
// TODO: Hotbar swap
// action not supported // action not supported
cancelled = true; cancel = true;
break; break;
} }
if (cancelled) event.setCancelled(true); event.setCancelled(cancel);
} else event.setCancelled(true); } else event.setCancelled(true);
} }
private void handleMoveToOtherInventory(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) {
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
ItemStack invStack = inventory.getItemStack(slot);
ItemUpdateEvent updateEvent = inventory.callUpdateEvent(reason, slot, invStack, null);
if (!updateEvent.isCancelled()) {
int leftOverAmount;
if (window instanceof MergedWindow) {
GUI otherGui;
if (window instanceof SplitWindow) {
SplitWindow splitWindow = (SplitWindow) window;
GUI[] guis = splitWindow.getGuis();
otherGui = guis[0] == this ? guis[1] : guis[0];
} else {
otherGui = this;
}
leftOverAmount = ((IndexedGUI) otherGui).putIntoVirtualInventories(reason, invStack, inventory);
} else {
leftOverAmount = 0;
HashMap<Integer, ItemStack> leftover = event.getWhoClicked().getInventory().addItem(inventory.getItemStack(slot));
if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount();
}
invStack.setAmount(leftOverAmount);
inventory.setItemStackSilently(slot, invStack);
}
}
private boolean handleHotbarSwap(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) {
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
if (window instanceof SingleWindow) {
int hotbarButton = event.getHotbarButton();
ItemStack hotbarItem = player.getInventory().getItem(hotbarButton);
if (hotbarItem != null) hotbarItem = hotbarItem.clone();
return !inventory.setItemStack(reason, slot, hotbarItem);
} // TODO: add support for merged windows
return true;
}
@Override @Override
public boolean handleItemDrag(UpdateReason updateReason, int slot, ItemStack oldStack, ItemStack newStack) { public boolean handleItemDrag(UpdateReason updateReason, int slot, ItemStack oldStack, ItemStack newStack) {
SlotElement element = getSlotElement(slot); SlotElement element = getSlotElement(slot);
@ -173,13 +219,13 @@ abstract class IndexedGUI implements GUI {
if (element instanceof VISlotElement) { if (element instanceof VISlotElement) {
VISlotElement viSlotElement = ((VISlotElement) element); VISlotElement viSlotElement = ((VISlotElement) element);
VirtualInventory virtualInventory = viSlotElement.getVirtualInventory(); VirtualInventory virtualInventory = viSlotElement.getVirtualInventory();
int viIndex = viSlotElement.getIndex(); int viSlot = viSlotElement.getSlot();
if (virtualInventory.isSynced(viIndex, oldStack)) { if (virtualInventory.isSynced(viSlot, oldStack)) {
return virtualInventory.setItemStack(updateReason, viIndex, newStack); return virtualInventory.setItemStack(updateReason, viSlot, newStack);
} }
} }
return true; return false;
} }
@Override @Override

@ -6,6 +6,7 @@ import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent;
import de.studiocode.invui.virtualinventory.event.UpdateReason; import de.studiocode.invui.virtualinventory.event.UpdateReason;
import de.studiocode.invui.window.Window; import de.studiocode.invui.window.Window;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
@ -15,44 +16,43 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
// TODO: clean up import static java.lang.Math.min;
public class VirtualInventory implements ConfigurationSerializable {
private final Set<Window> windows = new HashSet<>(); public class VirtualInventory implements ConfigurationSerializable {
private final UUID uuid; private final UUID uuid;
private int size; private int size;
private ItemStack[] items; private ItemStack[] items;
private int[] stackSizes;
private final Set<Window> windows = new HashSet<>();
private Consumer<ItemUpdateEvent> itemUpdateHandler; private Consumer<ItemUpdateEvent> itemUpdateHandler;
/** /**
* Creates a new {@link VirtualInventory}. * Constructs a new {@link VirtualInventory}
* *
* @param uuid The {@link UUID} this {@link VirtualInventory} should have. * @param uuid The {@link UUID} of this {@link VirtualInventory}. Can be null, only used for serialization.
* Can be null, only used for serialization. * @param size The amount of slots this {@link VirtualInventory} has.
* @param size The size of the {@link VirtualInventory} * @param items A predefined array of content. Can be null. Will not get copied!
* @param items An array of {@link ItemStack} which reflects the contents of this * @param stackSizes An array of maximum allowed stack sizes for the each slot in the {@link VirtualInventory}.
* {@link VirtualInventory}, therefore the length of that array has
* to be the same as <code>size</code>.
*/ */
public VirtualInventory(@Nullable UUID uuid, int size, @NotNull ItemStack[] items) { public VirtualInventory(@Nullable UUID uuid, int size, @Nullable ItemStack[] items, int[] stackSizes) {
if (size < 1) throw new IllegalArgumentException("size cannot be smaller than 1");
if (items.length != size) throw new IllegalArgumentException("items length has to be the same as size");
this.uuid = uuid; this.uuid = uuid;
this.size = size; this.size = size;
this.items = items; this.items = items == null ? new ItemStack[size] : items;
if (stackSizes == null) {
this.stackSizes = new int[size];
Arrays.fill(this.stackSizes, 64);
} else this.stackSizes = stackSizes;
} }
/** /**
* Creates a new {@link VirtualInventory}. * Constructs a new {@link VirtualInventory}
* *
* @param uuid The {@link UUID} this {@link VirtualInventory} should have. * @param uuid The {@link UUID} of this {@link VirtualInventory}. Can be null, only used for serialization.
* Can be null, only used for serialization. * @param size The amount of slots this {@link VirtualInventory} has.
* @param size The size of the {@link VirtualInventory}
*/ */
public VirtualInventory(@Nullable UUID uuid, int size) { public VirtualInventory(@Nullable UUID uuid, int size) {
this(uuid, size, new ItemStack[size]); this(uuid, size, null, null);
} }
/** /**
@ -63,281 +63,342 @@ public class VirtualInventory implements ConfigurationSerializable {
*/ */
public static VirtualInventory deserialize(@NotNull Map<String, Object> args) { public static VirtualInventory deserialize(@NotNull Map<String, Object> args) {
//noinspection unchecked //noinspection unchecked
return new VirtualInventory(UUID.fromString((String) args.get("uuid")), return new VirtualInventory(
(int) args.get("size"), ((ArrayList<ItemStack>) args.get("items")).toArray(new ItemStack[0])); UUID.fromString((String) args.get("uuid")),
(int) args.get("size"),
((ArrayList<ItemStack>) args.get("items")).toArray(new ItemStack[0]),
((ArrayList<Integer>) args.get("stackSizes")).stream().mapToInt(Integer::intValue).toArray()
);
}
/**
* Serializes this {@link VirtualInventory} to a {@link Map}
*
* @return A {@link Map} that contains the serialized data of this {@link VirtualInventory}
*/
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("uuid", uuid.toString());
result.put("size", size);
result.put("stackSizes", stackSizes);
result.put("items", items);
return result;
}
/**
* Gets a set of {@link Window}s that display this {@link VirtualInventory}.
*
* @return An unmodifiable view of the set that contains all {@link Window}s that display
* content of this {@link VirtualInventory}.
*/
public Set<Window> getWindows() {
return Collections.unmodifiableSet(windows);
}
/**
* Adds a {@link Window} to the set of {@link Window}s, telling the {@link VirtualInventory} that
* its contents are now being displayed in that {@link Window}.
*
* @param window The {@link Window} to be added.
*/
public void addWindow(Window window) {
windows.add(window);
}
/**
* Removes a {@link Window} from the set of {@link Window}s, telling the {@link VirtualInventory} that
* its contents are no longer being displayed in that {@link Window}.
*
* @param window The {@link Window} to be removed.
*/
public void removeWindow(Window window) {
windows.remove(window);
}
/**
* Notifies all {@link Window}s displaying this {@link VirtualInventory} to update their
* representative {@link ItemStack}s.
* This method should only be called manually in very specific cases like when the
* {@link ItemMeta} of an {@link ItemStack} in this inventory has changed.
*/
public void notifyWindows() {
Bukkit.getScheduler().runTask(InvUI.getInstance().getPlugin(), () ->
windows.forEach(window -> window.handleVirtualInventoryUpdate(this)));
}
/**
* Changes the size of the {@link VirtualInventory}.
* {@link ItemStack}s in slots which are no longer valid will be removed from the {@link VirtualInventory}.
* This method does not call an event.
*
* @param size The new size of the {@link VirtualInventory}
*/
public void resize(int size) {
this.size = size;
this.items = Arrays.copyOf(items, size);
this.stackSizes = Arrays.copyOf(stackSizes, size);
}
/**
* Sets a handler which is called every time something gets updated in the {@link VirtualInventory}.
*
* @param itemUpdateHandler The new item update handler
*/
public void setItemUpdateHandler(Consumer<ItemUpdateEvent> itemUpdateHandler) {
this.itemUpdateHandler = itemUpdateHandler;
}
/**
* Gets the {@link UUID} of this {@link VirtualInventory}.
*
* @return The {@link UUID}
*/
public UUID getUuid() {
return uuid;
} }
/** /**
* Gets the size of this {@link VirtualInventory}. * Gets the size of this {@link VirtualInventory}.
* *
* @return The size of this {@link VirtualInventory} * @return How many slots this {@link VirtualInventory} has.
*/ */
public int getSize() { public int getSize() {
return size; return size;
} }
/** /**
* Gets a deep copy of the {@link ItemStack}s in this {@link VirtualInventory} * Gets a copy of the contents of this {@link VirtualInventory}.
* *
* @return A copy of the {@link ItemStack}s in this {@link VirtualInventory} * @return A deep copy of the {@link ItemStack}s this {@link VirtualInventory} contains.
*/ */
public ItemStack[] getItems() { public ItemStack[] getItems() {
return Arrays.stream(items) return Arrays.stream(items).map(item -> item != null ? item.clone() : null).toArray(ItemStack[]::new);
.map(itemStack -> itemStack != null ? itemStack.clone() : null)
.toArray(ItemStack[]::new);
} }
/** /**
* Changes the size of this {@link VirtualInventory}, removing * Gets a clone of the {@link ItemStack} on that slot.
* existing {@link ItemStack}s reduced.
* *
* @param size The new size of this {@link VirtualInventory} * @param slot The slot
* @return The {@link ItemStack} on the given slot
*/ */
public void resize(int size) { public ItemStack getItemStack(int slot) {
this.size = size; ItemStack itemStack = items[slot];
this.items = Arrays.copyOf(items, size); return itemStack != null ? itemStack.clone() : null;
} }
/** /**
* Checks if the {@link ItemStack} on that slot index is the same * Returns the actual {@link ItemStack} on that slot.
* <br>
* Not a clone, should be handled carefully as changes done on that item will not call any
* Window updates (and create inconsistency between server and client),
* in which case a manual call of {@link #notifyWindows} is needed.
* <br>
* Modifying this {@link ItemStack} will not call an {@link ItemUpdateEvent}.
*
* @param slot The slot
* @return The actual {@link ItemStack} on that slot
*/
public ItemStack getUnsafeItemStack(int slot) {
return items[slot];
}
/**
* Gets the amount of items on a slot.
*
* @param slot The slot
* @return The amount of items on that slot
*/
public int getAmount(int slot) {
ItemStack currentStack = items[slot];
return currentStack != null ? currentStack.getAmount() : 0;
}
/**
* Gets the maximum stack size for a specific slot. If there is an {@link ItemStack} on that
* slot, the returned value will be the minimum of both the slot limit and {@link Material#getMaxStackSize()}.
*
* @param slot The slot
* @param alternative The alternative maximum stack size if no {@link ItemStack} is placed on that slot.
* Should probably be the max stack size of the {@link Material} that will be added.
* @return The current maximum allowed stack size on the specific slot.
*/
public int getMaxStackSize(int slot, int alternative) {
ItemStack currentItem = items[slot];
int slotMaxStackSize = stackSizes == null ? 64 : stackSizes[slot];
if (alternative != -1)
return min(currentItem != null ? currentItem.getMaxStackSize() : alternative, slotMaxStackSize);
else return slotMaxStackSize;
}
/**
* Sets all the maximum allowed stack sizes
*
* @param maxStackSizes All max stack sizes
*/
public void setMaxStackSizes(int[] maxStackSizes) {
this.stackSizes = maxStackSizes;
}
/**
* Sets the maximum allowed stack size on a specific slot.
*
* @param slot The slot
* @param maxStackSize The max stack size
*/
public void setMaxStackSize(int slot, int maxStackSize) {
stackSizes[slot] = maxStackSize;
}
/**
* Creates an {@link ItemUpdateEvent} and calls the {@link #itemUpdateHandler} to handle it.
*
* @param updateReason The {@link UpdateReason}
* @param slot The slot of the affected {@link ItemStack}
* @param previousItemStack The {@link ItemStack} that was previously on that slot
* @param newItemStack The {@link ItemStack} that will be on that slot
* @return The {@link ItemUpdateEvent} after it has been handled by the {@link #itemUpdateHandler}
*/
public ItemUpdateEvent callUpdateEvent(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) {
ItemUpdateEvent event = new ItemUpdateEvent(this, slot, updateReason, previousItemStack, newItemStack);
if (itemUpdateHandler != null) itemUpdateHandler.accept(event);
return event;
}
/**
* Checks if the {@link ItemStack} on that slot is the same
* as the assumed {@link ItemStack} provided as parameter. * as the assumed {@link ItemStack} provided as parameter.
* *
* @param index The slot index * @param slot The slot
* @param assumedStack The assumed {@link ItemStack} * @param assumedStack The assumed {@link ItemStack}
* @return If the {@link ItemStack} on that slot is the same as the assumed {@link ItemStack} * @return If the {@link ItemStack} on that slot is the same as the assumed {@link ItemStack}
*/ */
public boolean isSynced(int index, ItemStack assumedStack) { public boolean isSynced(int slot, ItemStack assumedStack) {
ItemStack actualStack = items[index]; ItemStack actualStack = items[slot];
return (actualStack == null && assumedStack == null) return (actualStack == null && assumedStack == null)
|| (actualStack != null && actualStack.equals(assumedStack)); || (actualStack != null && actualStack.equals(assumedStack));
} }
/** /**
* Sets an {@link ItemStack} on a specific slot. * Changes the {@link ItemStack} on a specific slot to that one, regardless of what was
* previously on that slot.
* <br>
* This method does not call an {@link ItemUpdateEvent} and ignores the maximum allowed stack size of
* both the {@link Material} and the slot.
* <br>
* This method will always be successful.
* *
* @param updateReason The reason for item update, can be null. * @param slot The slot
* @param index The slot index * @param itemStack The {@link ItemStack} to set.
* @param itemStack The {@link ItemStack} that should be put on that slot
* @return If the action has been cancelled
*/ */
public boolean setItemStack(@Nullable UpdateReason updateReason, int index, ItemStack itemStack) { public void setItemStackSilently(int slot, @Nullable ItemStack itemStack) {
ItemStack newStack = itemStack.clone(); if (itemStack != null && itemStack.getAmount() == 0) items[slot] = null;
ItemUpdateEvent event = createAndCallEvent(index, updateReason, items[index], newStack); else items[slot] = itemStack;
notifyWindows();
}
/**
* Changes the {@link ItemStack} on a specific slot to that one, regardless of what was
* previously on that slot.
* <br>
* This method ignores the maximum allowed stack size of both the {@link Material} and the slot.
*
* @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @param slot The slot
* @param itemStack The {@link ItemStack} to set.
* @return If the action was successful
*/
public boolean forceSetItemStack(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack itemStack) {
ItemUpdateEvent event = callUpdateEvent(updateReason, slot, items[slot], itemStack);
if (!event.isCancelled()) { if (!event.isCancelled()) {
items[index] = newStack; setItemStackSilently(slot, event.getNewItemStack());
notifyWindows(); return true;
return false;
} }
return false;
return true;
} }
/** /**
* Gets the {@link ItemStack} on a specific slot. * Changes the {@link ItemStack} on a specific slot to the given one, regardless of what previously was on
* that slot.
* <br>
* This method will fail if the given {@link ItemStack} does not completely fit inside because of the
* maximum allowed stack size.
* *
* @param index The slot index * @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @return The {@link ItemStack} on that slot * @param slot The slot
* @param itemStack The {@link ItemStack} to set.
* @return If the action was successful
*/ */
public ItemStack getItemStack(int index) { public boolean setItemStack(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack itemStack) {
return items[index]; int maxStackSize = getMaxStackSize(slot, itemStack != null ? itemStack.getMaxStackSize() : -1);
if (itemStack != null && itemStack.getAmount() > maxStackSize) return false;
return forceSetItemStack(updateReason, slot, itemStack);
} }
/** /**
* Checks if there is an {@link ItemStack} on a specific slot. * Adds an {@link ItemStack} on a specific slot.
* *
* @param index The slot index * @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @return If there is an {@link ItemStack} on that slot * @param slot The slot
* @param itemStack The {@link ItemStack} to add.
* @return The amount of items that did not fit on that slot.
*/ */
public boolean hasItemStack(int index) { public int putItemStack(@Nullable UpdateReason updateReason, int slot, @NotNull ItemStack itemStack) {
return items[index] != null; ItemStack currentStack = items[slot];
} if (currentStack == null || currentStack.isSimilar(itemStack)) {
int currentAmount = currentStack == null ? 0 : currentStack.getAmount();
int maxStackSize = getMaxStackSize(slot, itemStack.getMaxStackSize());
if (currentAmount < maxStackSize) {
ItemStack newItemStack = itemStack.clone();
newItemStack.setAmount(min(currentAmount + itemStack.getAmount(), maxStackSize));
/** ItemUpdateEvent event = callUpdateEvent(updateReason, slot, currentStack, newItemStack);
* Sets an {@link ItemStack} on a specific slot or adds the amount if (!event.isCancelled()) {
* if there already is an {@link ItemStack} on that slot. newItemStack = event.getNewItemStack();
* items[slot] = newItemStack;
* @param updateReason The reason for item update, can be null. notifyWindows();
* @param index The slot index
* @param itemStack The {@link ItemStack} to place
* @return If the action has been cancelled
*/
public boolean place(@Nullable UpdateReason updateReason, int index, ItemStack itemStack) {
ItemStack currentStack = items[index];
ItemStack newStack; return itemStack.getAmount() - (newItemStack.getAmount() - currentAmount);
if (currentStack == null) { }
newStack = itemStack.clone();
} else {
newStack = currentStack.clone();
newStack.setAmount(newStack.getAmount() + itemStack.getAmount());
}
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return false;
}
return true;
}
/**
* Puts on of an {@link ItemStack} on a specific slots or adds one
* if there is already an {@link ItemStack} on that slot.
*
* @param updateReason The reason for item update, can be null.
* @param index The slot index
* @param itemStack The {@link ItemStack} to place one of
* @return If the action has been cancelled
*/
public boolean placeOne(@Nullable UpdateReason updateReason, int index, ItemStack itemStack) {
ItemStack currentStack = items[index];
ItemStack newStack;
if (currentStack == null) {
newStack = itemStack.clone();
newStack.setAmount(1);
} else {
newStack = currentStack.clone();
newStack.setAmount(newStack.getAmount() + 1);
}
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return false;
}
return true;
}
/**
* Changes the amount of an {@link ItemStack} on a specific slot without calling the {@link ItemUpdateEvent}
*
* @param index The slot index
* @param amount The new amount
*/
public void setAmountSilently(int index, int amount) {
ItemStack currentStack = items[index];
if (currentStack != null) {
if (amount == 0) items[index] = null;
else currentStack.setAmount(amount);
notifyWindows();
}
}
/**
* Changes the amount of an {@link ItemStack} on a specific slot
* to the {@link ItemStack}'s {@link ItemStack#getMaxStackSize()}.
*
* @param updateReason The reason for item update, can be null.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean setToMaxAmount(@Nullable UpdateReason updateReason, int index) {
ItemStack currentStack = items[index];
if (currentStack != null) {
ItemStack newStack = currentStack.clone();
newStack.setAmount(newStack.getMaxStackSize());
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return false;
} }
} }
return true; return itemStack.getAmount();
} }
/** /**
* Removes an {@link ItemStack} on a specific slot from * Changes the amount of an {@link ItemStack} on a slot to the given value.
* the {@link VirtualInventory}.
* *
* @param updateReason The reason for item update, can be null. * @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @param index The slot index * @param slot The slot
* @return If the action has been cancelled * @param amount The amount to change to.
* @return The amount that it actually changed to.
* @throws IllegalStateException If there is no ItemStack on that slot.
*/ */
public boolean removeItem(@Nullable UpdateReason updateReason, int index) { public int changeItemAmount(@Nullable UpdateReason updateReason, int slot, int amount) {
ItemStack currentStack = items[index]; ItemStack currentStack = items[slot];
if (currentStack != null) { if (currentStack == null) throw new IllegalStateException("There is currently no ItemStack on that slot");
int maxStackSize = getMaxStackSize(slot, -1);
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, null); ItemStack newItemStack;
if (!event.isCancelled()) { if (amount != 0) {
items[index] = null; newItemStack = currentStack.clone();
notifyWindows(); newItemStack.setAmount(min(amount, maxStackSize));
} else {
return false; newItemStack = null;
}
} }
return true; ItemUpdateEvent event = callUpdateEvent(updateReason, slot, currentStack, newItemStack);
} if (!event.isCancelled()) {
newItemStack = event.getNewItemStack();
items[slot] = newItemStack;
notifyWindows();
/** return newItemStack != null ? newItemStack.getAmount() : 0;
* Removes one from an {@link ItemStack} on a specific slot.
*
* @param updateReason The reason for item update, can be null.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean removeOne(@Nullable UpdateReason updateReason, int index) {
ItemStack currentStack = items[index];
if (currentStack != null) {
int newAmount = currentStack.getAmount() - 1;
if (newAmount > 0) {
ItemStack newStack = currentStack.clone();
newStack.setAmount(newAmount);
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return false;
}
} else return removeItem(updateReason, index);
} }
return true; return amount;
}
/**
* Removes half of the {@link ItemStack} on a specific slot.
*
* @param updateReason The reason for item update, can be null.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean removeHalf(@Nullable UpdateReason updateReason, int index) {
ItemStack currentStack = items[index];
if (currentStack != null) {
int newAmount = currentStack.getAmount() / 2;
if (newAmount > 0) {
ItemStack newStack = currentStack.clone();
newStack.setAmount(newAmount);
ItemUpdateEvent event = createAndCallEvent(index, updateReason, currentStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return false;
}
} else return removeItem(updateReason, index);
}
return true;
} }
/** /**
@ -345,9 +406,9 @@ public class VirtualInventory implements ConfigurationSerializable {
* This method does not work the same way as Bukkit's addItem method * This method does not work the same way as Bukkit's addItem method
* as it respects the max stack size of the item type. * as it respects the max stack size of the item type.
* *
* @param updateReason The reason for item update, can be null. * @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @param itemStack The {@link ItemStack} to add * @param itemStack The {@link ItemStack} to add
* @return The amount of items that couldn't be added * @return The amount of items that didn't fit
* @see #simulateAdd(ItemStack) * @see #simulateAdd(ItemStack)
* @see #simulateMultiAdd(List) * @see #simulateMultiAdd(List)
*/ */
@ -357,14 +418,19 @@ public class VirtualInventory implements ConfigurationSerializable {
// find all slots where the item partially fits and add it there // find all slots where the item partially fits and add it there
for (int partialSlot : findPartialSlots(itemStack)) { for (int partialSlot : findPartialSlots(itemStack)) {
amountLeft = addTo(updateReason, partialSlot, amountLeft); ItemStack stackToPut = itemStack.clone();
stackToPut.setAmount(amountLeft);
amountLeft = putItemStack(updateReason, partialSlot, stackToPut);
if (amountLeft == 0) break; if (amountLeft == 0) break;
} }
// find all empty slots and put the item there // find all empty slots and put the item there
for (int emptySlot : ArrayUtils.findEmptyIndices(items)) { for (int emptySlot : ArrayUtils.findEmptyIndices(items)) {
ItemStack stackToPut = itemStack.clone();
stackToPut.setAmount(amountLeft);
amountLeft = putItemStack(updateReason, emptySlot, stackToPut);
if (amountLeft == 0) break; if (amountLeft == 0) break;
amountLeft = addToEmpty(updateReason, emptySlot, itemStack, amountLeft);
} }
// if items have been added, notify windows // if items have been added, notify windows
@ -383,18 +449,19 @@ public class VirtualInventory implements ConfigurationSerializable {
* @return How many items wouldn't fit in the inventory when added * @return How many items wouldn't fit in the inventory when added
*/ */
public int simulateAdd(ItemStack itemStack) { public int simulateAdd(ItemStack itemStack) {
int maxStackSize = itemStack.getMaxStackSize();
int amountLeft = itemStack.getAmount(); int amountLeft = itemStack.getAmount();
// find all slots where the item partially fits // find all slots where the item partially fits
for (int partialSlot : findPartialSlots(itemStack)) { for (int partialSlot : findPartialSlots(itemStack)) {
ItemStack partialItem = items[partialSlot]; ItemStack partialItem = items[partialSlot];
int maxStackSize = getMaxStackSize(partialSlot, -1);
amountLeft = Math.max(0, amountLeft - (maxStackSize - partialItem.getAmount())); amountLeft = Math.max(0, amountLeft - (maxStackSize - partialItem.getAmount()));
if (amountLeft == 0) break; if (amountLeft == 0) break;
} }
// remaining items would be added to empty slots // remaining items would be added to empty slots
for (int ignored : ArrayUtils.findEmptyIndices(items)) { for (int emptySlot : ArrayUtils.findEmptyIndices(items)) {
int maxStackSize = getMaxStackSize(emptySlot, itemStack.getMaxStackSize());
amountLeft -= Math.min(amountLeft, maxStackSize); amountLeft -= Math.min(amountLeft, maxStackSize);
} }
@ -415,7 +482,7 @@ public class VirtualInventory implements ConfigurationSerializable {
public int[] simulateMultiAdd(List<ItemStack> itemStacks) { public int[] simulateMultiAdd(List<ItemStack> itemStacks) {
if (itemStacks.size() < 2) throw new IllegalArgumentException("Illegal amount of ItemStacks in List"); if (itemStacks.size() < 2) throw new IllegalArgumentException("Illegal amount of ItemStacks in List");
VirtualInventory copiedInv = new VirtualInventory(null, size, getItems()); VirtualInventory copiedInv = new VirtualInventory(null, size, getItems(), stackSizes.clone());
int[] result = new int[itemStacks.size()]; int[] result = new int[itemStacks.size()];
for (int index = 0; index != itemStacks.size(); index++) { for (int index = 0; index != itemStacks.size(); index++) {
result[index] = copiedInv.addItem(null, itemStacks.get(index)); result[index] = copiedInv.addItem(null, itemStacks.get(index));
@ -425,18 +492,13 @@ public class VirtualInventory implements ConfigurationSerializable {
} }
/** /**
* Checks if the {@link VirtualInventory} could theoretically hold the * Finds all {@link ItemStack}s similar to the provided {@link ItemStack} and removes them from
* provided {@link ItemStack}. * their slot until the maximum stack size of the {@link Material} is reached.
* *
* @param itemStacks The {@link ItemStack}s * @param updateReason The reason used in the {@link ItemUpdateEvent}.
* @return If the {@link VirtualInventory} can fit all these items * @param itemStack The {@link ItemStack} to find matches to
* @return The amount of collected items
*/ */
public boolean canHold(List<ItemStack> itemStacks) {
if (itemStacks.size() == 0) return true;
else if (itemStacks.size() == 1) return simulateAdd(itemStacks.get(0)) == 0;
else return Arrays.stream(simulateMultiAdd(itemStacks)).allMatch(i -> i == 0);
}
public int collectToCursor(@Nullable UpdateReason updateReason, ItemStack itemStack) { public int collectToCursor(@Nullable UpdateReason updateReason, ItemStack itemStack) {
int amount = itemStack.getAmount(); int amount = itemStack.getAmount();
int maxStackSize = itemStack.getMaxStackSize(); int maxStackSize = itemStack.getMaxStackSize();
@ -444,15 +506,13 @@ public class VirtualInventory implements ConfigurationSerializable {
// find partial slots and take items from there // find partial slots and take items from there
for (int partialSlot : findPartialSlots(itemStack)) { for (int partialSlot : findPartialSlots(itemStack)) {
amount += takeFrom(updateReason, partialSlot, maxStackSize - amount); amount += takeFrom(updateReason, partialSlot, maxStackSize - amount);
if (amount == maxStackSize) break; if (amount == maxStackSize) return amount;
} }
// if only taking from partial stacks wasn't enough, take from a full slot // only taking from partial stacks wasn't enough, take from a full slot
if (amount < itemStack.getMaxStackSize()) { for (int fullSlot : findFullSlots(itemStack)) {
int fullSlot = findFullSlot(itemStack); amount += takeFrom(updateReason, fullSlot, maxStackSize - amount);
if (fullSlot != -1) { if (amount == maxStackSize) return amount;
amount += takeFrom(updateReason, fullSlot, maxStackSize - amount);
}
} }
} }
@ -461,52 +521,28 @@ public class VirtualInventory implements ConfigurationSerializable {
private List<Integer> findPartialSlots(ItemStack itemStack) { private List<Integer> findPartialSlots(ItemStack itemStack) {
List<Integer> partialSlots = new ArrayList<>(); List<Integer> partialSlots = new ArrayList<>();
for (int i = 0; i < items.length; i++) { for (int slot = 0; slot < size; slot++) {
ItemStack currentStack = items[i]; ItemStack currentStack = items[slot];
if (currentStack != null && currentStack.getAmount() < currentStack.getMaxStackSize() if (itemStack.isSimilar(currentStack)) {
&& currentStack.isSimilar(itemStack)) partialSlots.add(i); int maxStackSize = getMaxStackSize(slot, -1);
if (currentStack.getAmount() < maxStackSize) partialSlots.add(slot);
}
} }
return partialSlots; return partialSlots;
} }
private int findFullSlot(ItemStack itemStack) { private List<Integer> findFullSlots(ItemStack itemStack) {
for (int i = 0; i < items.length; i++) { List<Integer> fullSlots = new ArrayList<>();
ItemStack currentStack = items[i]; for (int slot = 0; slot < size; slot++) {
if (currentStack != null ItemStack currentStack = items[slot];
&& currentStack.getAmount() == currentStack.getMaxStackSize() if (itemStack.isSimilar(currentStack)) {
&& currentStack.isSimilar(itemStack)) return i; int maxStackSize = getMaxStackSize(slot, -1);
if (currentStack.getAmount() == maxStackSize) fullSlots.add(slot);
}
} }
return -1; return fullSlots;
}
private int addTo(@Nullable UpdateReason updateReason, int index, int amount) {
ItemStack itemStack = items[index];
int maxAddable = Math.min(itemStack.getMaxStackSize() - itemStack.getAmount(), amount);
int currentAmount = itemStack.getAmount();
int newAmount = currentAmount + maxAddable;
ItemStack newStack = itemStack.clone();
newStack.setAmount(newAmount);
ItemUpdateEvent event = createAndCallEvent(index, updateReason, itemStack, newStack);
if (!event.isCancelled()) {
items[index] = newStack;
notifyWindows();
return amount - maxAddable;
} else return amount;
}
private int addToEmpty(@Nullable UpdateReason updateReason, int index, @NotNull ItemStack type, int amount) {
int maxAddable = Math.min(type.getType().getMaxStackSize(), amount);
ItemStack newStack = type.clone();
newStack.setAmount(maxAddable);
if (setItemStack(updateReason, index, newStack)) return amount;
else return amount - maxAddable;
} }
private int takeFrom(@Nullable UpdateReason updateReason, int index, int maxTake) { private int takeFrom(@Nullable UpdateReason updateReason, int index, int maxTake) {
@ -520,7 +556,7 @@ public class VirtualInventory implements ConfigurationSerializable {
newStack.setAmount(amount - take); newStack.setAmount(amount - take);
} else newStack = null; } else newStack = null;
ItemUpdateEvent event = createAndCallEvent(index, updateReason, itemStack, newStack); ItemUpdateEvent event = callUpdateEvent(updateReason, index, itemStack, newStack);
if (!event.isCancelled()) { if (!event.isCancelled()) {
items[index] = newStack; items[index] = newStack;
notifyWindows(); notifyWindows();
@ -530,94 +566,4 @@ public class VirtualInventory implements ConfigurationSerializable {
return 0; return 0;
} }
/**
* Adds a {@link Window} to the window set, telling the {@link VirtualInventory} that it is
* currently being displayed in that {@link Window}.
*
* @param window The {@link Window} the {@link VirtualInventory} is currently displayed in.
*/
public void addWindow(Window window) {
windows.add(window);
}
/**
* Removes an {@link Window} from the window set, telling the {@link VirtualInventory} that it
* is no longer being displayed in that {@link Window}.
*
* @param window The {@link Window} the {@link VirtualInventory} is no longer displayed in.
*/
public void removeWindow(Window window) {
windows.remove(window);
}
/**
* Gets an immutable view of the {@link Set} that contains all the {@link Window}s that
* display this {@link VirtualInventory}.
*
* @return An UnmodifiableSet of all the {@link Window}s that show this {@link VirtualInventory}.
*/
public Set<Window> getWindows() {
return Collections.unmodifiableSet(windows);
}
/**
* Notifies all {@link Window}s displaying this {@link VirtualInventory} to update their
* representative {@link ItemStack}s.
* This method should only be called manually in very specific cases like when the
* {@link ItemMeta} of an {@link ItemStack} in this inventory has changed.
*/
public void notifyWindows() {
Bukkit.getScheduler().runTask(InvUI.getInstance().getPlugin(), () ->
windows.forEach(window -> window.handleVirtualInventoryUpdate(this)));
}
/**
* Creates an {@link ItemUpdateEvent} and calls the {@link #itemUpdateHandler} to handle it.
*
* @param index The slot index of the affected {@link ItemStack}
* @param updateReason The {@link UpdateReason}
* @param previousItemStack The {@link ItemStack} that was previously on that slot
* @param newItemStack The {@link ItemStack} that will be on that slot
* @return The {@link ItemUpdateEvent} after it has been handled by the {@link #itemUpdateHandler}
*/
public ItemUpdateEvent createAndCallEvent(int index, UpdateReason updateReason, ItemStack previousItemStack, ItemStack newItemStack) {
ItemUpdateEvent event = new ItemUpdateEvent(this, index, updateReason, previousItemStack, newItemStack);
if (itemUpdateHandler != null) itemUpdateHandler.accept(event);
return event;
}
/**
* Gets the {@link UUID} of this {@link VirtualInventory}.
*
* @return The {@link UUID} of this {@link VirtualInventory}
*/
public UUID getUuid() {
return uuid;
}
/**
* Sets the item update handler which will get called every time
* an item gets updated in this {@link VirtualInventory}.
*
* @param itemUpdateHandler The item update handler
*/
public void setItemUpdateHandler(Consumer<ItemUpdateEvent> itemUpdateHandler) {
this.itemUpdateHandler = itemUpdateHandler;
}
/**
* Serializes this {@link VirtualInventory} to a {@link Map}
*
* @return A {@link Map} that contains the serialized data of this {@link VirtualInventory}
*/
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("uuid", uuid.toString());
result.put("size", size);
result.put("items", items);
return result;
}
} }

@ -3,6 +3,7 @@ package de.studiocode.invui.virtualinventory;
import de.studiocode.invui.InvUI; import de.studiocode.invui.InvUI;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
@ -44,6 +45,16 @@ public class VirtualInventoryManager {
return virtualInventory; return virtualInventory;
} }
public VirtualInventory createNew(@NotNull UUID uuid, int size, ItemStack[] items, int[] stackSizes) {
if (inventories.containsKey(uuid))
throw new IllegalArgumentException("A Virtual Inventory with that UUID already exists");
VirtualInventory virtualInventory = new VirtualInventory(uuid, size, items, stackSizes);
inventories.put(uuid, virtualInventory);
return virtualInventory;
}
public VirtualInventory getByUuid(@NotNull UUID uuid) { public VirtualInventory getByUuid(@NotNull UUID uuid) {
return inventories.get(uuid); return inventories.get(uuid);
} }
@ -53,6 +64,11 @@ public class VirtualInventoryManager {
return virtualInventory == null ? createNew(uuid, size) : virtualInventory; return virtualInventory == null ? createNew(uuid, size) : virtualInventory;
} }
public VirtualInventory getOrCreate(UUID uuid, int size, ItemStack[] items, int[] stackSizes) {
VirtualInventory virtualInventory = getByUuid(uuid);
return virtualInventory == null ? createNew(uuid, size, items, stackSizes) : virtualInventory;
}
public void remove(VirtualInventory virtualInventory) { public void remove(VirtualInventory virtualInventory) {
inventories.remove(virtualInventory.getUuid(), virtualInventory); inventories.remove(virtualInventory.getUuid(), virtualInventory);
getSaveFile(virtualInventory).delete(); getSaveFile(virtualInventory).delete();

@ -13,7 +13,7 @@ public class ItemUpdateEvent {
private final VirtualInventory virtualInventory; private final VirtualInventory virtualInventory;
private final ItemStack previousItemStack; private final ItemStack previousItemStack;
private final ItemStack newItemStack; private ItemStack newItemStack;
private final UpdateReason updateReason; private final UpdateReason updateReason;
private final int slot; private final int slot;
@ -76,6 +76,17 @@ public class ItemUpdateEvent {
return newItemStack; return newItemStack;
} }
/**
* Change the {@link ItemStack} that will appear in the {@link VirtualInventory}
* to a different one.
*
* @param newItemStack The {@link ItemStack} to appear in the {@link VirtualInventory}
* if the {@link ItemUpdateEvent} is not cancelled.
*/
public void setNewItemStack(@Nullable ItemStack newItemStack) {
this.newItemStack = newItemStack;
}
/** /**
* Gets the slot that is affected. * Gets the slot that is affected.
* *

@ -106,7 +106,7 @@ public abstract class BaseWindow implements Window {
// get the GUI at that index and ask for permission to drag an Item there // get the GUI at that index and ask for permission to drag an Item there
Pair<GUI, Integer> pair = getGuiAt(rawSlot); Pair<GUI, Integer> pair = getGuiAt(rawSlot);
if (pair != null && pair.getFirst().handleItemDrag(updateReason, pair.getSecond(), currentStack, newItems.get(rawSlot))) { if (pair != null && !pair.getFirst().handleItemDrag(updateReason, pair.getSecond(), currentStack, newItems.get(rawSlot))) {
// the drag was cancelled // the drag was cancelled
int currentAmount = currentStack == null ? 0 : currentStack.getAmount(); int currentAmount = currentStack == null ? 0 : currentStack.getAmount();
int newAmount = newItems.get(rawSlot).getAmount(); int newAmount = newItems.get(rawSlot).getAmount();