diff --git a/src/main/java/de/studiocode/invgui/gui/GUI.java b/src/main/java/de/studiocode/invgui/gui/GUI.java index 20f8451..4ee6332 100644 --- a/src/main/java/de/studiocode/invgui/gui/GUI.java +++ b/src/main/java/de/studiocode/invgui/gui/GUI.java @@ -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 ---- /** diff --git a/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java index 6b19fda..6c33f57 100644 --- a/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java +++ b/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java @@ -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; @@ -43,10 +47,9 @@ abstract class IndexedGUI implements GUI { private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) { VirtualInventory virtualInventory = element.getVirtualInventory(); int index = element.getIndex(); - + 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); + if (virtualInventory.isSynced(index, clicked)) { + virtualInventory.removeItem(index); + } else event.setCancelled(true); break; - + + case PICKUP_HALF: + if (virtualInventory.isSynced(index, clicked)) { + virtualInventory.removeHalf(index); + } else event.setCancelled(true); + break; + case PLACE_ALL: - if (!virtualInventory.place(index, cursor, clickedAmount)) - event.setCancelled(true); + if (virtualInventory.isSynced(index, clicked)) { + virtualInventory.place(index, cursor); + } else event.setCancelled(true); break; case PLACE_ONE: - if (!virtualInventory.placeOne(index, cursor, clickedAmount)) - event.setCancelled(true); + if (virtualInventory.isSynced(index, clicked)) { + virtualInventory.placeOne(index, cursor); + } else event.setCancelled(true); break; - - case PICKUP_HALF: - if (!virtualInventory.removeHalf(index, clickedAmount)) - event.setCancelled(true); + + 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 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 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 getAllVirtualInventories() { + List 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; diff --git a/src/main/java/de/studiocode/invgui/util/ArrayUtils.java b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java index ce8602b..6f522e4 100644 --- a/src/main/java/de/studiocode/invgui/util/ArrayUtils.java +++ b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java @@ -16,17 +16,6 @@ public class ArrayUtils { return -1; } -// public static List findAllOccurrences(@NotNull T[] array, @NotNull T toFind) { -// List 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 Map findAllOccurrences(@NotNull T[] array, Predicate predicate) { Map occurrences = new HashMap<>(); diff --git a/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java b/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java index 5867528..e11ca34 100644 --- a/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java +++ b/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java @@ -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 boolean removeOne(int index) { - if (!hasItem(index)) return false; - - int amount = items[index].getAmount() - 1; - if (amount > 0) { - items[index].setAmount(amount); + 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; + notifyWindows(); - } else removeItem(index); - - return true; + } } - public boolean removeHalf(int index, int expectedCurrentAmount) { - if (!hasItem(index) || items[index].getAmount() != expectedCurrentAmount) return false; - - int amount = items[index].getAmount() / 2; - - if (amount > 0) { - items[index].setAmount(amount); + 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; + 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(); } - - notifyWindows(); - return true; } public boolean hasItem(int index) { diff --git a/src/main/java/de/studiocode/invgui/window/Window.java b/src/main/java/de/studiocode/invgui/window/Window.java index 0239656..ca5162a 100644 --- a/src/main/java/de/studiocode/invgui/window/Window.java +++ b/src/main/java/de/studiocode/invgui/window/Window.java @@ -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. diff --git a/src/main/java/de/studiocode/invgui/window/WindowManager.java b/src/main/java/de/studiocode/invgui/window/WindowManager.java index 8c0483e..fec92a7 100644 --- a/src/main/java/de/studiocode/invgui/window/WindowManager.java +++ b/src/main/java/de/studiocode/invgui/window/WindowManager.java @@ -29,7 +29,7 @@ public class WindowManager implements Listener { /** * Gets the {@link WindowManager} instance or creates a new one if there isn't one. - * + * * @return The {@link WindowManager} instance */ public static WindowManager getInstance() { @@ -39,7 +39,7 @@ public class WindowManager implements Listener { /** * Adds a {@link Window} to the list of windows. * This method is usually called by the {@link Window} itself. - * + * * @param window The {@link Window} to add */ public void addWindow(Window window) { @@ -49,7 +49,7 @@ public class WindowManager implements Listener { /** * Removes a {@link Window} from the list of windows. * This method is usually called by the {@link Window} itself. - * + * * @param window The {@link Window} to remove */ public void removeWindow(Window window) { @@ -93,14 +93,12 @@ public class WindowManager implements Listener { public void handleInventoryClick(InventoryClickEvent event) { Optional w = findWindow(event.getClickedInventory()); if (w.isPresent()) { // player clicked window - w.ifPresent(window -> window.handleClick(event)); + w.get().handleClick(event); } else { Optional 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); } } diff --git a/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java b/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java index c872c6d..3c63f42 100644 --- a/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java +++ b/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java @@ -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()))