VirtualInventory shift click

Added the possibility to shift-click items in and out of VirtualInventories
Added handling for the PLACE_SOME InventoryAction
This commit is contained in:
NichtStudioCode 2021-01-24 19:55:45 +01:00
parent ddf5c070e8
commit 97db33277e
7 changed files with 203 additions and 76 deletions

@ -9,6 +9,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
@ -187,6 +188,14 @@ public interface GUI {
*/
void handleClick(int slot, Player player, ClickType clickType, InventoryClickEvent event);
/**
* A method called when an {@link ItemStack} has been shift-clicked into this
* {@link GUI}.
*
* @param event The {@link InventoryClickEvent} associated with this action
*/
void handleItemShift(InventoryClickEvent event);
// ---- fill methods ----
/**

@ -15,6 +15,10 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
abstract class IndexedGUI implements GUI {
protected final int size;
@ -46,7 +50,6 @@ abstract class IndexedGUI implements GUI {
ItemStack cursor = event.getCursor();
ItemStack clicked = event.getCurrentItem();
int clickedAmount = clicked == null ? 0 : clicked.getAmount();
switch (event.getAction()) {
@ -58,29 +61,52 @@ abstract class IndexedGUI implements GUI {
case DROP_ONE_SLOT:
case PICKUP_ONE:
if (!virtualInventory.removeOne(index))
event.setCancelled(true);
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.removeOne(index);
} else event.setCancelled(true);
break;
case DROP_ALL_SLOT:
case PICKUP_ALL:
if (!virtualInventory.removeItem(index))
event.setCancelled(true);
break;
case PLACE_ALL:
if (!virtualInventory.place(index, cursor, clickedAmount))
event.setCancelled(true);
break;
case PLACE_ONE:
if (!virtualInventory.placeOne(index, cursor, clickedAmount))
event.setCancelled(true);
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.removeItem(index);
} else event.setCancelled(true);
break;
case PICKUP_HALF:
if (!virtualInventory.removeHalf(index, clickedAmount))
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.removeHalf(index);
} else event.setCancelled(true);
break;
case PLACE_ALL:
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.place(index, cursor);
} else event.setCancelled(true);
break;
case PLACE_ONE:
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.placeOne(index, cursor);
} else event.setCancelled(true);
break;
case PLACE_SOME:
if (virtualInventory.isSynced(index, clicked)) {
virtualInventory.setMaxAmount(index);
} else event.setCancelled(true);
break;
case MOVE_TO_OTHER_INVENTORY:
event.setCancelled(true);
if (virtualInventory.isSynced(index, clicked)) {
int leftOverAmount = 0;
HashMap<Integer, ItemStack> leftover =
event.getWhoClicked().getInventory().addItem(virtualInventory.getItemStack(index));
if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount();
virtualInventory.setAmount(index, leftOverAmount);
}
break;
default:
@ -90,6 +116,41 @@ abstract class IndexedGUI implements GUI {
}
}
@Override
public void handleItemShift(InventoryClickEvent event) {
event.setCancelled(true);
ItemStack clicked = event.getCurrentItem();
List<VirtualInventory> virtualInventories = getAllVirtualInventories();
if (virtualInventories.size() > 0) {
int amountLeft = clicked.getAmount();
for (VirtualInventory virtualInventory : virtualInventories) {
ItemStack toAdd = clicked.clone();
toAdd.setAmount(amountLeft);
amountLeft = virtualInventory.addItem(toAdd);
if (amountLeft == 0) break;
}
if (amountLeft != 0) event.getCurrentItem().setAmount(amountLeft);
else event.getClickedInventory().setItem(event.getSlot(), null);
}
}
private List<VirtualInventory> getAllVirtualInventories() {
List<VirtualInventory> virtualInventories = new ArrayList<>();
ArrayUtils
.findAllOccurrences(slotElements, element -> element instanceof VISlotElement)
.values().stream()
.map(element -> ((VISlotElement) element).getVirtualInventory())
.forEach(vi -> {
if (!virtualInventories.contains(vi)) virtualInventories.add(vi);
});
return virtualInventories;
}
@Override
public void setSlotElement(int index, @NotNull SlotElement slotElement) {
slotElements[index] = slotElement;

@ -16,17 +16,6 @@ public class ArrayUtils {
return -1;
}
// public static <T> List<Integer> findAllOccurrences(@NotNull T[] array, @NotNull T toFind) {
// List<Integer> occurrences = new ArrayList<>();
//
// for (int index = 0; index < array.length; index++) {
// T t = array[index];
// if (t != null && t.equals(toFind)) occurrences.add(index);
// }
//
// return occurrences;
// }
public static <T> Map<Integer, T> findAllOccurrences(@NotNull T[] array, Predicate<T> predicate) {
Map<Integer, T> occurrences = new HashMap<>();

@ -1,6 +1,7 @@
package de.studiocode.invgui.virtualinventory;
import de.studiocode.invgui.InvGui;
import de.studiocode.invgui.util.ArrayUtils;
import de.studiocode.invgui.window.Window;
import org.bukkit.Bukkit;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
@ -37,9 +38,55 @@ public class VirtualInventory implements ConfigurationSerializable {
this.items = Arrays.copyOf(items, size);
}
// TODO
public void addItem(ItemStack... itemStacks) {
throw new UnsupportedOperationException("not implemented yet");
public boolean isSynced(int index, ItemStack assumedStack) {
ItemStack actualStack = items[index];
return (actualStack == null && assumedStack == null)
|| (actualStack != null && actualStack.equals(assumedStack));
}
public int addItem(ItemStack itemStack) {
final int originalAmount = itemStack.getAmount();
int amountLeft = originalAmount;
addItems:
{
// find all slots where the item partially fits and add it there
ItemStack partialStack;
while ((partialStack = findPartialSlot(itemStack)) != null) {
amountLeft = addTo(partialStack, amountLeft);
if (amountLeft == 0) break addItems;
}
// there are still items left, put the rest on an empty slot
int emptyIndex = ArrayUtils.findFirstEmptyIndex(items);
if (emptyIndex != -1) {
ItemStack leftover = itemStack.clone();
leftover.setAmount(amountLeft);
items[emptyIndex] = leftover;
amountLeft = 0;
}
}
// if items have been added, notify windows
if (originalAmount != amountLeft) notifyWindows();
// return how many items couldn't be added
return amountLeft;
}
private ItemStack findPartialSlot(ItemStack itemStack) {
for (ItemStack currentStack : items) {
if (currentStack != null && currentStack.getAmount() < currentStack.getMaxStackSize()
&& currentStack.isSimilar(itemStack)) return currentStack;
}
return null;
}
private int addTo(ItemStack itemStack, int amount) {
int maxAddable = Math.min(itemStack.getMaxStackSize() - itemStack.getAmount(), amount);
itemStack.setAmount(itemStack.getAmount() + maxAddable);
return amount - maxAddable;
}
public void setItem(int index, ItemStack itemStack) {
@ -47,72 +94,81 @@ public class VirtualInventory implements ConfigurationSerializable {
notifyWindows();
}
public void setAmount(int index, int amount) {
ItemStack itemStack = items[index];
if (itemStack != null) {
if (amount == 0) items[index] = null;
else itemStack.setAmount(amount);
notifyWindows();
}
}
public void setMaxAmount(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
itemStack.setAmount(itemStack.getMaxStackSize());
notifyWindows();
}
}
public ItemStack getItemStack(int index) {
return items[index];
}
public boolean removeItem(int index) {
if (hasItem(index)) {
public void removeItem(int index) {
if (items[index] != null) {
items[index] = null;
notifyWindows();
return true;
}
}
return false;
}
public void removeOne(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int amount = itemStack.getAmount() - 1;
if (amount > 0) itemStack.setAmount(amount);
else items[index] = null;
public boolean removeOne(int index) {
if (!hasItem(index)) return false;
int amount = items[index].getAmount() - 1;
if (amount > 0) {
items[index].setAmount(amount);
notifyWindows();
} else removeItem(index);
return true;
}
}
public boolean removeHalf(int index, int expectedCurrentAmount) {
if (!hasItem(index) || items[index].getAmount() != expectedCurrentAmount) return false;
public void removeHalf(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int amount = itemStack.getAmount() / 2;
if (amount > 0) itemStack.setAmount(amount);
else items[index] = null;
int amount = items[index].getAmount() / 2;
if (amount > 0) {
items[index].setAmount(amount);
notifyWindows();
} else removeItem(index);
return true;
}
}
public boolean place(int index, ItemStack itemStack, int expectedCurrentAmount) {
int currentAmount = items[index] == null ? 0 : items[index].getAmount();
if (currentAmount != expectedCurrentAmount) return false;
public void place(int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
if (items[index] == null) {
if (there == null) {
setItem(index, itemStack);
} else {
items[index].setAmount(expectedCurrentAmount + itemStack.getAmount());
there.setAmount(currentAmount + itemStack.getAmount());
notifyWindows();
}
return true;
}
public boolean placeOne(int index, ItemStack itemStack, int expectedCurrentAmount) {
int currentAmount = items[index] == null ? 0 : items[index].getAmount();
if (currentAmount != expectedCurrentAmount) return false;
public void placeOne(int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
if (items[index] == null) {
if (there == null) {
ItemStack single = itemStack.clone();
single.setAmount(1);
setItem(index, single);
} else {
items[index].setAmount(expectedCurrentAmount + 1);
}
there.setAmount(currentAmount + 1);
notifyWindows();
return true;
}
}
public boolean hasItem(int index) {

@ -56,6 +56,13 @@ public interface Window {
*/
void handleClick(InventoryClickEvent event);
/**
* A method called by the {@link WindowManager} to notify the Window
* that {@link ItemStack}s have been shift-clicked from the lower
* {@link Inventory} to this {@link Window}
*/
void handleItemShift(InventoryClickEvent event);
/**
* A method called by the {@link WindowManager} to notify the Window
* that its underlying {@link Inventory} is being opened.

@ -93,14 +93,12 @@ public class WindowManager implements Listener {
public void handleInventoryClick(InventoryClickEvent event) {
Optional<Window> w = findWindow(event.getClickedInventory());
if (w.isPresent()) { // player clicked window
w.ifPresent(window -> window.handleClick(event));
w.get().handleClick(event);
} else {
Optional<Window> w1 = findWindow(event.getView().getTopInventory());
if (w1.isPresent()) { // player clicked lower inventory while looking at window
// don't let the player shift-click items to another inventory
if (event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY)
event.setCancelled(true);
}
// player shift-clicked from lower inventory to window
if (w1.isPresent() && event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY)
w1.get().handleItemShift(event);
}
}

@ -108,6 +108,13 @@ public abstract class BaseWindow implements Window {
} else event.setCancelled(true);
}
@Override
public void handleItemShift(InventoryClickEvent event) {
if (animation == null) { // if not in animation, let the gui handle the item shift
gui.handleItemShift(event);
} else event.setCancelled(true);
}
@Override
public void handleOpen(InventoryOpenEvent event) {
if (!event.getPlayer().equals(getViewer()))