diff --git a/src/main/java/de/studiocode/invui/gui/impl/IndexedGUI.java b/src/main/java/de/studiocode/invui/gui/impl/IndexedGUI.java index 39ffd8c..23bb925 100644 --- a/src/main/java/de/studiocode/invui/gui/impl/IndexedGUI.java +++ b/src/main/java/de/studiocode/invui/gui/impl/IndexedGUI.java @@ -15,7 +15,6 @@ import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent; import de.studiocode.invui.window.Window; import de.studiocode.invui.window.WindowManager; import de.studiocode.invui.window.impl.merged.MergedWindow; -import de.studiocode.invui.window.impl.merged.combined.CombinedWindow; import de.studiocode.invui.window.impl.merged.split.SplitWindow; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -112,11 +111,17 @@ abstract class IndexedGUI implements GUI { case SWAP_WITH_CURSOR: cancelled = virtualInventory.setItemStack(player, index, event.getCursor()); break; + + case COLLECT_TO_CURSOR: + cancelled = true; + ItemStack newCursor = cursor.clone(); + newCursor.setAmount(virtualInventory.collectToCursor(player, newCursor)); + player.setItemOnCursor(newCursor); + break; case MOVE_TO_OTHER_INVENTORY: - event.setCancelled(true); + cancelled = true; Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); - if (window instanceof CombinedWindow) break; // can't move if there is no other gui ItemStack invStack = virtualInventory.getItemStack(index); ItemUpdateEvent updateEvent = new ItemUpdateEvent(virtualInventory, index, player, invStack, null); @@ -146,8 +151,9 @@ abstract class IndexedGUI implements GUI { break; default: + // TODO: Hotbar swap // action not supported - event.setCancelled(true); + cancelled = true; break; } diff --git a/src/main/java/de/studiocode/invui/util/InventoryUtils.java b/src/main/java/de/studiocode/invui/util/InventoryUtils.java index 5e64990..c2f62b3 100644 --- a/src/main/java/de/studiocode/invui/util/InventoryUtils.java +++ b/src/main/java/de/studiocode/invui/util/InventoryUtils.java @@ -4,6 +4,7 @@ import de.studiocode.invui.gui.GUI; import org.bukkit.Bukkit; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; public class InventoryUtils { @@ -19,4 +20,16 @@ public class InventoryUtils { else return Bukkit.createInventory(null, type, title); } + public static boolean containsSimilar(Inventory inventory, ItemStack itemStack) { + for (int i = 0; i < inventory.getSize(); i++) { + ItemStack currentStack = inventory.getItem(i); + if (currentStack != null && currentStack.getType().isAir()) currentStack = null; + + if ((currentStack == null && itemStack == null) + || (currentStack != null && currentStack.isSimilar(itemStack))) return true; + } + + return false; + } + } diff --git a/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java b/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java index 7fa0dcc..c6b02b6 100644 --- a/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java +++ b/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java @@ -2,7 +2,6 @@ package de.studiocode.invui.virtualinventory; import de.studiocode.invui.InvUI; import de.studiocode.invui.util.ArrayUtils; -import de.studiocode.invui.util.Pair; import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent; import de.studiocode.invui.window.Window; import org.bukkit.Bukkit; @@ -100,11 +99,10 @@ public class VirtualInventory implements ConfigurationSerializable { /** * Sets an {@link ItemStack} on a specific slot. - * + * * @param player The player that did this or null if it wasn't a player. * @param index The slot index * @param itemStack The {@link ItemStack} that should be put on that slot - * * @return If the action has been cancelled */ public boolean setItemStack(Player player, int index, ItemStack itemStack) { @@ -116,7 +114,7 @@ public class VirtualInventory implements ConfigurationSerializable { return false; } - + return true; } @@ -339,9 +337,10 @@ public class VirtualInventory implements ConfigurationSerializable { int amountLeft = originalAmount; // find all slots where the item partially fits and add it there - Pair partialSlot; - while ((partialSlot = findPartialSlot(itemStack)) != null && amountLeft != 0) + for (int partialSlot : findPartialSlots(itemStack)) { amountLeft = addTo(player, partialSlot, amountLeft); + if (amountLeft == 0) break; + } if (amountLeft != 0) { // there are still items left, put the rest on an empty slot @@ -361,19 +360,52 @@ public class VirtualInventory implements ConfigurationSerializable { return amountLeft; } - private Pair findPartialSlot(ItemStack itemStack) { + public int collectToCursor(Player player, ItemStack itemStack) { + int amount = itemStack.getAmount(); + int maxStackSize = itemStack.getMaxStackSize(); + if (amount < itemStack.getMaxStackSize()) { + // find partial slots and take items from there + for (int partialSlot : findPartialSlots(itemStack)) { + amount += takeFrom(player, partialSlot, maxStackSize - amount); + if (amount == maxStackSize) break; + } + + // if only taking from partial stacks wasn't enough, take from a full slot + if (amount < itemStack.getMaxStackSize()) { + int fullSlot = findFullSlot(itemStack); + if (fullSlot != -1) { + amount += takeFrom(player, fullSlot, maxStackSize - amount); + } + } + } + + return amount; + } + + private List findPartialSlots(ItemStack itemStack) { + List partialSlots = new ArrayList<>(); for (int i = 0; i < items.length; i++) { ItemStack currentStack = items[i]; if (currentStack != null && currentStack.getAmount() < currentStack.getMaxStackSize() - && currentStack.isSimilar(itemStack)) return new Pair<>(i, currentStack); + && currentStack.isSimilar(itemStack)) partialSlots.add(i); } - return null; + return partialSlots; } - private int addTo(Player player, Pair partialSlot, int amount) { - int index = partialSlot.getFirst(); - ItemStack itemStack = partialSlot.getSecond(); + private int findFullSlot(ItemStack itemStack) { + for (int i = 0; i < items.length; i++) { + ItemStack currentStack = items[i]; + if (currentStack != null + && currentStack.getAmount() == currentStack.getMaxStackSize() + && currentStack.isSimilar(itemStack)) return i; + } + + return -1; + } + + private int addTo(Player player, int index, int amount) { + ItemStack itemStack = items[index]; int maxAddable = Math.min(itemStack.getMaxStackSize() - itemStack.getAmount(), amount); @@ -391,6 +423,27 @@ public class VirtualInventory implements ConfigurationSerializable { } else return amount; } + private int takeFrom(Player player, int index, int maxTake) { + ItemStack itemStack = items[index]; + int amount = itemStack.getAmount(); + int take = Math.min(amount, maxTake); + + ItemStack newStack; + if (take != amount) { + newStack = itemStack.clone(); + newStack.setAmount(amount - take); + } else newStack = null; + + ItemUpdateEvent event = createAndCallEvent(index, player, itemStack, newStack); + if (!event.isCancelled()) { + items[index] = newStack; + notifyWindows(); + return take; + } + + return 0; + } + /** * Adds a {@link Window} to the window set, telling the {@link VirtualInventory} that it is * currently being displayed in that {@link Window}. diff --git a/src/main/java/de/studiocode/invui/window/Window.java b/src/main/java/de/studiocode/invui/window/Window.java index d2e8c84..dff192c 100644 --- a/src/main/java/de/studiocode/invui/window/Window.java +++ b/src/main/java/de/studiocode/invui/window/Window.java @@ -5,6 +5,7 @@ import de.studiocode.invui.gui.GUIParent; import de.studiocode.invui.item.Item; import de.studiocode.invui.item.itembuilder.ItemBuilder; import de.studiocode.invui.virtualinventory.VirtualInventory; +import de.studiocode.invui.window.impl.merged.MergedWindow; import de.studiocode.invui.window.impl.merged.combined.SimpleCombinedWindow; import de.studiocode.invui.window.impl.merged.split.SimpleSplitWindow; import de.studiocode.invui.window.impl.single.SimpleWindow; @@ -53,7 +54,7 @@ public interface Window extends GUIParent { /** * A method called by the {@link WindowManager} to notify the {@link Window} * that {@link ItemStack}s have been dragged inside it. - * + * * @param event The {@link InventoryDragEvent} associated with this action. */ void handleDrag(InventoryDragEvent event); @@ -62,11 +63,22 @@ public interface Window extends GUIParent { * A method called by the {@link WindowManager} to notify the {@link Window} * that {@link ItemStack}s have been shift-clicked from the lower * {@link Inventory} to this {@link Window} - * + * * @param event The {@link InventoryClickEvent} associated with this action. */ void handleItemShift(InventoryClickEvent event); + /** + * A method called by the {@link WindowManager} to notify the {@link Window} + * that a {@link Player} is trying to collect {@link ItemStack} to the cursor + * by double-clicking in the player inventory. + * This method is not called when the player inventory is also part of the + * {@link Window}. ({@link MergedWindow}) + * + * @param event The {@link InventoryClickEvent} associated with this action. + */ + void handleCursorCollect(InventoryClickEvent event); + /** * A method called by the {@link WindowManager} to notify the {@link Window} * that its underlying {@link Inventory} is being opened. diff --git a/src/main/java/de/studiocode/invui/window/WindowManager.java b/src/main/java/de/studiocode/invui/window/WindowManager.java index b85bfcc..e7c6953 100644 --- a/src/main/java/de/studiocode/invui/window/WindowManager.java +++ b/src/main/java/de/studiocode/invui/window/WindowManager.java @@ -116,9 +116,21 @@ public class WindowManager implements Listener { w.get().handleClick(event); } else { Optional w1 = findWindow(event.getView().getTopInventory()); - // player shift-clicked from lower inventory to window - if (w1.isPresent() && event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY) - w1.get().handleItemShift(event); + // player inventory (not merged window) clicked + if (w1.isPresent()) { + Window window = w1.get(); + switch (event.getAction()) { + // items have been shift clicked from player inv to Window + case MOVE_TO_OTHER_INVENTORY: + window.handleItemShift(event); + break; + + // items have been collected by clicking a slot in the player inv + case COLLECT_TO_CURSOR: + window.handleCursorCollect(event); + break; + } + } } } diff --git a/src/main/java/de/studiocode/invui/window/impl/merged/MergedWindow.java b/src/main/java/de/studiocode/invui/window/impl/merged/MergedWindow.java index 686741a..7754b45 100644 --- a/src/main/java/de/studiocode/invui/window/impl/merged/MergedWindow.java +++ b/src/main/java/de/studiocode/invui/window/impl/merged/MergedWindow.java @@ -120,7 +120,12 @@ public abstract class MergedWindow extends BaseWindow { @Override public void handleItemShift(InventoryClickEvent event) { - // empty + // empty, should not be called by the WindowManager + } + + @Override + public void handleCursorCollect(InventoryClickEvent event) { + // empty, should not be called by the WindowManager } @Override diff --git a/src/main/java/de/studiocode/invui/window/impl/single/SingleWindow.java b/src/main/java/de/studiocode/invui/window/impl/single/SingleWindow.java index 7e083a0..ca51b66 100644 --- a/src/main/java/de/studiocode/invui/window/impl/single/SingleWindow.java +++ b/src/main/java/de/studiocode/invui/window/impl/single/SingleWindow.java @@ -2,6 +2,7 @@ package de.studiocode.invui.window.impl.single; import de.studiocode.invui.gui.GUI; import de.studiocode.invui.gui.SlotElement; +import de.studiocode.invui.util.InventoryUtils; import de.studiocode.invui.util.Pair; import de.studiocode.invui.window.Window; import de.studiocode.invui.window.impl.BaseWindow; @@ -64,6 +65,18 @@ public abstract class SingleWindow extends BaseWindow { gui.handleClick(event.getSlot(), (Player) event.getWhoClicked(), event.getClick(), event); } + @Override + public void handleItemShift(InventoryClickEvent event) { + gui.handleItemShift(event); + } + + @Override + public void handleCursorCollect(InventoryClickEvent event) { + // TODO: Allow collecting from player inventory and VirtualInventory + // only cancel when this would affect the window inventory + if (InventoryUtils.containsSimilar(inventory, event.getCursor())) event.setCancelled(true); + } + @Override protected Pair getGuiAt(int index) { return index < gui.getSize() ? new Pair<>(gui, index) : null; @@ -74,11 +87,6 @@ public abstract class SingleWindow extends BaseWindow { return gui.getSlotElement(index); } - @Override - public void handleItemShift(InventoryClickEvent event) { - gui.handleItemShift(event); - } - @Override public void handleViewerDeath(PlayerDeathEvent event) { // empty