diff --git a/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java b/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java index 1e4c612..8193f97 100644 --- a/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java +++ b/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java @@ -77,7 +77,6 @@ public abstract class BaseAnimation implements Animation { } public void cancel() { - System.out.println("ani cancel"); task.cancel(); } diff --git a/src/main/java/de/studiocode/invgui/gui/GUI.java b/src/main/java/de/studiocode/invgui/gui/GUI.java index 5fa1cdc..20f8451 100644 --- a/src/main/java/de/studiocode/invgui/gui/GUI.java +++ b/src/main/java/de/studiocode/invgui/gui/GUI.java @@ -1,7 +1,9 @@ package de.studiocode.invgui.gui; +import de.studiocode.invgui.gui.SlotElement.ItemStackHolder; import de.studiocode.invgui.gui.impl.*; import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.virtualinventory.VirtualInventory; import de.studiocode.invgui.window.Window; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; @@ -11,8 +13,9 @@ import org.jetbrains.annotations.NotNull; /** * A GUI is a container for width * height {@link SlotElement}s.
- * Each {@link SlotElement} can either be an {@link Item} or a - * reference to another {@link GUI}s slot index.
+ * Each {@link SlotElement} can either be an {@link Item}, + * a reference to a {@link VirtualInventory}'s or another {@link GUI}'s + * slot index.
* A {@link GUI} is not an {@link Inventory}, nor does * it access one. It just contains {@link SlotElement}s and their positions.
* In order to create an {@link Inventory} which is visible @@ -114,7 +117,7 @@ public interface GUI { * * @param x The x coordinate * @param y The y coordinate - * @return The {@link Item} which is placed on that slot or null if there is none + * @return The {@link Item} which is placed on that slot or null if there isn't one */ Item getItem(int x, int y); @@ -122,7 +125,7 @@ public interface GUI { * Gets the {@link Item} placed on that slot. * * @param index The slot index - * @return The {@link Item} which is placed on that slot or null if there is none + * @return The {@link Item} which is placed on that slot or null if there isn't one */ Item getItem(int index); @@ -133,6 +136,23 @@ public interface GUI { */ void addItems(@NotNull Item... items); + /** + * Gets the {@link ItemStackHolder} on these coordinates. + * + * @param x The x coordinate + * @param y The y coordinate + * @return The {@link ItemStackHolder} which is placed on that slot or null if there isn't one + */ + ItemStackHolder getItemStackHolder(int x, int y); + + /** + * Gets the {@link ItemStackHolder} placed on that slot. + * + * @param index The slot index + * @return The {@link ItemStackHolder} which is placed on that slot or null if there isn't one + */ + ItemStackHolder getItemStackHolder(int index); + /** * Removes an {@link Item} by its coordinates. * @@ -225,6 +245,4 @@ public interface GUI { */ void fillRectangle(int x, int y, int width, int height, Item item, boolean replaceExisting); - // TODO: more filling methods - } diff --git a/src/main/java/de/studiocode/invgui/gui/SlotElement.java b/src/main/java/de/studiocode/invgui/gui/SlotElement.java index 2e9e0d3..eaed102 100644 --- a/src/main/java/de/studiocode/invgui/gui/SlotElement.java +++ b/src/main/java/de/studiocode/invgui/gui/SlotElement.java @@ -1,47 +1,108 @@ package de.studiocode.invgui.gui; import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.virtualinventory.VirtualInventory; +import org.bukkit.inventory.ItemStack; -public class SlotElement { +import java.util.UUID; + +public interface SlotElement { - private final Item item; - private final GUI gui; - private final int slotNumber; - - public SlotElement(Item item) { - this.item = item; - this.gui = null; - this.slotNumber = -1; + interface ItemStackHolder { + + ItemStack getItemStack(UUID viewerUUID); + } - public SlotElement(GUI gui, int slotNumber) { - this.gui = gui; - this.slotNumber = slotNumber; - this.item = null; + /** + * Contains an Item + */ + class ItemSlotElement implements SlotElement, ItemStackHolder { + + private final Item item; + + public ItemSlotElement(Item item) { + this.item = item; + } + + public Item getItem() { + return item; + } + + @Override + public ItemStack getItemStack(UUID viewerUUID) { + return item.getItemBuilder().buildFor(viewerUUID); + } + } - public boolean isItem() { - return item != null; + /** + * Links to a slot in a virtual inventory + */ + class VISlotElement implements SlotElement, ItemStackHolder { + + private final VirtualInventory virtualInventory; + private final int index; + + public VISlotElement(VirtualInventory virtualInventory, int index) { + this.virtualInventory = virtualInventory; + this.index = index; + } + + public VirtualInventory getVirtualInventory() { + return virtualInventory; + } + + public int getIndex() { + return index; + } + + public ItemStack getItemStack() { + return virtualInventory.getItemStack(index); + } + + @Override + public ItemStack getItemStack(UUID viewerUUID) { + return getItemStack(); + } + } - public Item getItem() { - return item; - } - - public boolean isGui() { - return gui != null; - } - - public GUI getGui() { - return gui; - } - - public int getSlotNumber() { - return slotNumber; - } - - public Item getItemFromGui() { - return gui.getItem(slotNumber); + /** + * Links to a slot in another GUI + */ + class LinkedSlotElement implements SlotElement { + + private final GUI gui; + + private final int slot; + + public LinkedSlotElement(GUI gui, int slot) { + this.gui = gui; + this.slot = slot; + } + + public GUI getGui() { + return gui; + } + + public int getSlotIndex() { + return slot; + } + + public SlotElement getBottomSlotElement() { + LinkedSlotElement element = this; + while (true) { + SlotElement below = element.getGui().getSlotElement(element.getSlotIndex()); + if (below instanceof LinkedSlotElement) element = (LinkedSlotElement) below; + else return below; + } + } + + public ItemStackHolder getItemStackHolder() { + return (ItemStackHolder) getBottomSlotElement(); + } + } } diff --git a/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java index 5b430c1..9b12331 100644 --- a/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java +++ b/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java @@ -1,6 +1,7 @@ package de.studiocode.invgui.gui.builder; import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement; import de.studiocode.invgui.item.Item; import org.bukkit.inventory.ShapedRecipe; import org.jetbrains.annotations.NotNull; @@ -25,10 +26,19 @@ public interface GUIBuilder { * Sets an ingredient for the structure String, which will later be * used to set up the inventory correctly. * - * @param c The ingredient key + * @param key The ingredient key * @param item The {@link Item} */ - void setIngredient(char c, @NotNull Item item); + void setIngredient(char key, @NotNull Item item); + + /** + * Sets an ingredient for the structure String, which will later be + * used to set up the inventory correctly. + * + * @param key The ingredient key + * @param slotElement The {@link SlotElement} + */ + void setIngredient(char key, @NotNull SlotElement slotElement); /** * Builds the {@link GUI}. diff --git a/src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java index 86fda06..99a89ee 100644 --- a/src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java @@ -1,6 +1,7 @@ package de.studiocode.invgui.gui.builder.impl; import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement; import de.studiocode.invgui.gui.builder.GUIBuilder; import de.studiocode.invgui.item.Item; import org.jetbrains.annotations.NotNull; @@ -38,6 +39,11 @@ public abstract class BaseGUIBuilder implements GUIBuilder { ingredientMap.put(key, new Ingredient(item)); } + @Override + public void setIngredient(char key, @NotNull SlotElement slotElement) { + ingredientMap.put(key, new Ingredient(slotElement)); + } + public void setIngredient(char key, int special) { ingredientMap.put(key, new Ingredient(special)); } @@ -63,11 +69,12 @@ public abstract class BaseGUIBuilder implements GUIBuilder { return ingredients; } - protected void setItems(GUI gui) { + protected void setSlotElements(GUI gui) { Ingredient[] ingredients = parseStructure(); for (int i = 0; i < gui.getSize(); i++) { Ingredient ingredient = ingredients[i]; - if (ingredient.isItem()) gui.setItem(i, ingredient.getItem()); + if (ingredient != null && ingredient.isSlotElement()) + gui.setSlotElement(i, ingredient.getSlotElement()); } } @@ -88,35 +95,40 @@ public abstract class BaseGUIBuilder implements GUIBuilder { static class Ingredient { - private final Item item; + private final SlotElement slotElement; private final int special; public Ingredient(Item item) { - this.item = item; + this.slotElement = new SlotElement.ItemSlotElement(item); + this.special = -1; + } + + public Ingredient(SlotElement slotElement) { + this.slotElement = slotElement; this.special = -1; } public Ingredient(int special) { this.special = special; - this.item = null; + this.slotElement = null; } - public Item getItem() { + public SlotElement getSlotElement() { if (isSpecial()) throw new IllegalStateException("Ingredient is special"); - return item; + return slotElement; } public int getSpecial() { - if (isItem()) throw new IllegalStateException("Ingredient is item"); + if (isSlotElement()) throw new IllegalStateException("Ingredient is item"); return special; } - public boolean isItem() { - return item != null; + public boolean isSlotElement() { + return slotElement != null; } public boolean isSpecial() { - return item == null; + return slotElement == null; } } diff --git a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java index d726957..df377f8 100644 --- a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java @@ -11,7 +11,7 @@ public class SimpleGUIBuilder extends BaseGUIBuilder { @Override public SimpleGUI build() { SimpleGUI gui = new SimpleGUI(getWidth(), getHeight()); - setItems(gui); + setSlotElements(gui); return gui; } diff --git a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java index 079a2c8..029ad61 100644 --- a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java @@ -22,7 +22,7 @@ public class SimplePagedGUIsBuilder extends PagedGUIBuilder { SimplePagedGUIs gui = new SimplePagedGUIs(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), getForwardItemIndex(), getForwardBuilder(), guis, getListSlots()); - setItems(gui); + setSlotElements(gui); return gui; } diff --git a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java index 1ba6bd5..1aca903 100644 --- a/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java @@ -22,7 +22,7 @@ public class SimplePagedItemsGUIBuilder extends PagedGUIBuilder { SimplePagedItemsGUI gui = new SimplePagedItemsGUI(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), getForwardItemIndex(), getForwardBuilder(), items, getListSlots()); - setItems(gui); + setSlotElements(gui); return gui; } diff --git a/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java index 649dc1b..43a31fa 100644 --- a/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java +++ b/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java @@ -1,29 +1,41 @@ package de.studiocode.invgui.gui.impl; -import de.studiocode.invgui.gui.GUI; import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.gui.SlotElement.ItemStackHolder; import de.studiocode.invgui.item.Item; -import de.studiocode.invgui.util.ArrayUtils; import de.studiocode.invgui.util.SlotUtils; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; import java.util.SortedSet; -public abstract class BaseGUI implements GUI { +/** + * A subclass of {@link IndexedGUI} which contains all the + * coordinate-based methods as well as all filling methods. + */ +public abstract class BaseGUI extends IndexedGUI { protected final int width; protected final int height; - protected final int size; - protected final SlotElement[] slotElements; public BaseGUI(int width, int height) { + super(width * height); this.width = width; this.height = height; - this.size = width * height; - slotElements = new SlotElement[size]; + } + + @Override + public void setSlotElement(int x, int y, @NotNull SlotElement slotElement) { + setSlotElement(convToIndex(x, y), slotElement); + } + + @Override + public SlotElement getSlotElement(int x, int y) { + return getSlotElement(convToIndex(x, y)); + } + + @Override + public ItemStackHolder getItemStackHolder(int x, int y) { + return getItemStackHolder(convToIndex(x, y)); } @Override @@ -31,61 +43,33 @@ public abstract class BaseGUI implements GUI { setItem(convToIndex(x, y), item); } - @Override - public void setItem(int index, Item item) { - remove(index); - if (item != null) slotElements[index] = new SlotElement(item); - } - @Override public Item getItem(int x, int y) { return getItem(convToIndex(x, y)); } - @Override - public Item getItem(int index) { - SlotElement slotElement = slotElements[index]; - if (slotElement == null) return null; - return slotElement.isItem() ? slotElement.getItem() : slotElement.getItemFromGui(); - } - - @Override - public void addItems(@NotNull Item... items) { - for (Item item : items) { - int emptyIndex = ArrayUtils.findFirstEmptyIndex(items); - if (emptyIndex == -1) break; - setItem(emptyIndex, item); - } - } - @Override public void remove(int x, int y) { remove(convToIndex(x, y)); } @Override - public void remove(int index) { - SlotElement slotElement = slotElements[index]; - if (slotElement == null) return; - if (slotElement.isItem()) { - slotElements[index] = null; - } else throw new IllegalArgumentException("Slot " + index + " is part of a nested GUI"); + public int getWidth() { + return width; } @Override - public void nest(int offset, @NotNull GUI gui) { - for (int i = 0; i < gui.getSize(); i++) slotElements[i + offset] = new SlotElement(gui, i); + public int getHeight() { + return height; } - @Override - public void handleClick(int slotNumber, Player player, ClickType clickType, InventoryClickEvent event) { - SlotElement slotElement = slotElements[slotNumber]; - if (slotElement == null) return; - if (slotElement.isGui()) - slotElement.getGui().handleClick(slotElement.getSlotNumber(), player, clickType, event); - else slotElement.getItem().handleClick(clickType, player, event); + private int convToIndex(int x, int y) { + if (x >= width || y >= height) throw new IllegalArgumentException("Coordinates out of bounds"); + return SlotUtils.convertToIndex(x, y, width); } + // filling methods + public void fill(@NotNull SortedSet slots, Item item, boolean replaceExisting) { for (int slot : slots) { if (!replaceExisting && slotElements[slot] != null) continue; @@ -128,49 +112,4 @@ public abstract class BaseGUI implements GUI { fill(SlotUtils.getSlotsRect(x, y, width, height, this.width), item, replaceExisting); } - @Override - public void setSlotElement(int x, int y, @NotNull SlotElement slotElement) { - setSlotElement(convToIndex(x, y), slotElement); - } - - @Override - public void setSlotElement(int index, @NotNull SlotElement slotElement) { - slotElements[index] = slotElement; - } - - @Override - public SlotElement getSlotElement(int x, int y) { - return getSlotElement(convToIndex(x, y)); - } - - @Override - public SlotElement getSlotElement(int index) { - return slotElements[index]; - } - - @Override - public SlotElement[] getSlotElements() { - return slotElements.clone(); - } - - @Override - public int getSize() { - return size; - } - - @Override - public int getWidth() { - return width; - } - - @Override - public int getHeight() { - return height; - } - - private int convToIndex(int x, int y) { - if (x >= width || y >= height) throw new IllegalArgumentException("Coordinates out of bounds"); - return SlotUtils.convertToIndex(x, y, width); - } - } diff --git a/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java new file mode 100644 index 0000000..6b19fda --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/IndexedGUI.java @@ -0,0 +1,164 @@ +package de.studiocode.invgui.gui.impl; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.gui.SlotElement.ItemSlotElement; +import de.studiocode.invgui.gui.SlotElement.ItemStackHolder; +import de.studiocode.invgui.gui.SlotElement.LinkedSlotElement; +import de.studiocode.invgui.gui.SlotElement.VISlotElement; +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.util.ArrayUtils; +import de.studiocode.invgui.virtualinventory.VirtualInventory; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +abstract class IndexedGUI implements GUI { + + protected final int size; + protected final SlotElement[] slotElements; + + public IndexedGUI(int size) { + this.size = size; + slotElements = new SlotElement[size]; + } + + @Override + public void handleClick(int slotNumber, Player player, ClickType clickType, InventoryClickEvent event) { + SlotElement slotElement = slotElements[slotNumber]; + if (slotElement instanceof LinkedSlotElement) { + LinkedSlotElement linkedElement = (LinkedSlotElement) slotElement; + linkedElement.getGui().handleClick(linkedElement.getSlotIndex(), player, clickType, event); + } else if (slotElement instanceof ItemSlotElement) { + event.setCancelled(true); // if it is an Item, don't let the player move it + ItemSlotElement itemElement = (ItemSlotElement) slotElement; + itemElement.getItem().handleClick(clickType, player, event); + } else if (slotElement instanceof VISlotElement) { + handleVISlotElementClick((VISlotElement) slotElement, event); + } else event.setCancelled(true); // Only VISlotElements have allowed interactions + } + + 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()) { + + case CLONE_STACK: + case DROP_ALL_CURSOR: + case DROP_ONE_CURSOR: + // empty, this does not affect anything + break; + + case DROP_ONE_SLOT: + case PICKUP_ONE: + if (!virtualInventory.removeOne(index)) + 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); + break; + + case PICKUP_HALF: + if (!virtualInventory.removeHalf(index, clickedAmount)) + event.setCancelled(true); + break; + + default: + // action not supported + event.setCancelled(true); + break; + } + } + + @Override + public void setSlotElement(int index, @NotNull SlotElement slotElement) { + slotElements[index] = slotElement; + } + + @Override + public SlotElement getSlotElement(int index) { + return slotElements[index]; + } + + @Override + public SlotElement[] getSlotElements() { + return slotElements.clone(); + } + + @Override + public void setItem(int index, Item item) { + remove(index); + if (item != null) slotElements[index] = new ItemSlotElement(item); + } + + @Override + public void addItems(@NotNull Item... items) { + for (Item item : items) { + int emptyIndex = ArrayUtils.findFirstEmptyIndex(items); + if (emptyIndex == -1) break; + setItem(emptyIndex, item); + } + } + + @Override + public Item getItem(int index) { + SlotElement slotElement = slotElements[index]; + + if (slotElement != null) { + if (slotElement instanceof ItemSlotElement) { + return ((ItemSlotElement) slotElement).getItem(); + } else if (slotElement instanceof LinkedSlotElement) { + SlotElement bottom = ((LinkedSlotElement) slotElement).getBottomSlotElement(); + if (bottom instanceof ItemSlotElement) return ((ItemSlotElement) bottom).getItem(); + } + } + + return null; + } + + @Override + public ItemStackHolder getItemStackHolder(int index) { + SlotElement slotElement = slotElements[index]; + if (slotElement instanceof ItemStackHolder) { + return (ItemStackHolder) slotElement; + } else if (slotElement instanceof LinkedSlotElement) { + return ((LinkedSlotElement) slotElement).getItemStackHolder(); + } else return null; + } + + @Override + public void remove(int index) { + slotElements[index] = null; + } + + @Override + public void nest(int offset, @NotNull GUI gui) { + for (int i = 0; i < gui.getSize(); i++) slotElements[i + offset] = new LinkedSlotElement(gui, i); + } + + @Override + public int getSize() { + return size; + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java index 609faa5..e58a70c 100644 --- a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java +++ b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java @@ -16,8 +16,6 @@ public class SimplePagedGUIs extends PagedGUI { super(width, height, false, itemListSlots); this.guis = guis; - System.out.println("control slot " + backItemSlot + " fwd " + forwardItemSlot); - setControlItems(backItemSlot, new BackItem(backBuilder), forwardItemSlot, new ForwardItem(forwardBuilder)); update(); } diff --git a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java index 90e80f7..29ff667 100644 --- a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java +++ b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java @@ -1,6 +1,7 @@ package de.studiocode.invgui.gui.impl; import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.gui.SlotElement.ItemSlotElement; import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.itembuilder.ItemBuilder; @@ -32,7 +33,7 @@ public class SimplePagedItemsGUI extends PagedGUI { int from = page * length; int to = Math.min(from + length, items.size()); - return items.subList(from, to).stream().map(SlotElement::new).collect(Collectors.toList()); + return items.subList(from, to).stream().map(ItemSlotElement::new).collect(Collectors.toList()); } } diff --git a/src/main/java/de/studiocode/invgui/util/ArrayUtils.java b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java index e41b1db..ce8602b 100644 --- a/src/main/java/de/studiocode/invgui/util/ArrayUtils.java +++ b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java @@ -2,8 +2,9 @@ package de.studiocode.invgui.util; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; public class ArrayUtils { @@ -15,12 +16,23 @@ public class ArrayUtils { return -1; } - public static List findAllOccurrences(@NotNull Object[] array, @NotNull Object toFind) { - List occurrences = new ArrayList<>(); +// 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<>(); for (int index = 0; index < array.length; index++) { - Object obj = array[index]; - if (obj != null && obj.equals(toFind)) occurrences.add(index); + T t = array[index]; + if (predicate.test(t)) occurrences.put(index, t); } return occurrences; diff --git a/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java b/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java new file mode 100644 index 0000000..3dd3be5 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/virtualinventory/VirtualInventory.java @@ -0,0 +1,128 @@ +package de.studiocode.invgui.virtualinventory; + +import de.studiocode.invgui.InvGui; +import de.studiocode.invgui.window.Window; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class VirtualInventory { + + private transient final Set windows = new HashSet<>(); + + private int size; + private ItemStack[] items; + + public VirtualInventory(int size) { + this.size = size; + this.items = new ItemStack[size]; + } + + public int getSize() { + return size; + } + + public void resize(int size) { + this.size = size; + this.items = Arrays.copyOf(items, size); + } + + // TODO + public void addItem(ItemStack... itemStacks) { + throw new UnsupportedOperationException("not implemented yet"); + } + + public void setItem(int index, ItemStack itemStack) { + items[index] = itemStack.clone(); + notifyWindows(); + } + + public ItemStack getItemStack(int index) { + return items[index]; + } + + public boolean removeItem(int index) { + if (hasItem(index)) { + 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); + 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); + 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; + + if (items[index] == null) { + setItem(index, itemStack); + } else { + items[index].setAmount(expectedCurrentAmount + 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; + + if (items[index] == null) { + ItemStack single = itemStack.clone(); + single.setAmount(1); + setItem(index, single); + } else { + items[index].setAmount(expectedCurrentAmount + 1); + } + + notifyWindows(); + return true; + } + + public boolean hasItem(int index) { + return items[index] != null; + } + + public void addWindow(Window window) { + windows.add(window); + } + + public void removeWindow(Window window) { + windows.remove(window); + } + + private void notifyWindows() { + Bukkit.getScheduler().runTask(InvGui.getInstance().getPlugin(), () -> + windows.forEach(window -> window.handleVirtualInventoryUpdate(this))); + } + +} diff --git a/src/main/java/de/studiocode/invgui/window/Window.java b/src/main/java/de/studiocode/invgui/window/Window.java index cd419ed..0239656 100644 --- a/src/main/java/de/studiocode/invgui/window/Window.java +++ b/src/main/java/de/studiocode/invgui/window/Window.java @@ -4,6 +4,7 @@ import de.studiocode.invgui.animation.Animation; import de.studiocode.invgui.gui.GUI; import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.itembuilder.ItemBuilder; +import de.studiocode.invgui.virtualinventory.VirtualInventory; import de.studiocode.invgui.window.impl.BaseWindow; import de.studiocode.invgui.window.impl.DropperWindow; import de.studiocode.invgui.window.impl.HopperWindow; @@ -80,6 +81,15 @@ public interface Window { */ void handleItemBuilderUpdate(Item item); + /** + * A method called by the {@link VirtualInventory} to notify the + * Window that one if it's contents has been updated and the {@link ItemStack}'s + * displayed in the {@link Inventory} should be replaced. + * + * @param virtualInventory The {@link VirtualInventory} + */ + void handleVirtualInventoryUpdate(VirtualInventory virtualInventory); + /** * Removes the {@link Window} from the {@link WindowManager} list. * If this method is called, the {@link Window} can't be shown again. diff --git a/src/main/java/de/studiocode/invgui/window/WindowManager.java b/src/main/java/de/studiocode/invgui/window/WindowManager.java index bc74947..8962bdc 100644 --- a/src/main/java/de/studiocode/invgui/window/WindowManager.java +++ b/src/main/java/de/studiocode/invgui/window/WindowManager.java @@ -6,9 +6,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.*; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; @@ -34,7 +32,7 @@ public class WindowManager implements Listener { * @return The {@link WindowManager} instance */ public static WindowManager getInstance() { - return hasInstance() ? instance = new WindowManager() : instance; + return !hasInstance() ? instance = new WindowManager() : instance; } /** @@ -101,7 +99,23 @@ public class WindowManager implements Listener { @EventHandler public void handleInventoryClick(InventoryClickEvent event) { - findWindow(event.getClickedInventory()).ifPresent(window -> window.handleClick(event)); + Optional w = findWindow(event.getClickedInventory()); + if (w.isPresent()) { // player clicked window + w.ifPresent(window -> window.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); + } + } + } + + @EventHandler + public void handleInventoryDrag(InventoryDragEvent event) { + // currently, dragging items is not supported + findWindow(event.getInventory()).ifPresent(w -> event.setCancelled(true)); } @EventHandler(priority = EventPriority.HIGHEST) 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 43f32e3..c872c6d 100644 --- a/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java +++ b/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java @@ -3,8 +3,12 @@ package de.studiocode.invgui.window.impl; import de.studiocode.invgui.InvGui; import de.studiocode.invgui.animation.Animation; import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement.ItemSlotElement; +import de.studiocode.invgui.gui.SlotElement.ItemStackHolder; +import de.studiocode.invgui.gui.SlotElement.VISlotElement; import de.studiocode.invgui.item.Item; import de.studiocode.invgui.util.ArrayUtils; +import de.studiocode.invgui.virtualinventory.VirtualInventory; import de.studiocode.invgui.window.Window; import de.studiocode.invgui.window.WindowManager; import org.bukkit.Bukkit; @@ -13,11 +17,9 @@ import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -28,7 +30,7 @@ public abstract class BaseWindow implements Window { private final UUID viewerUUID; private final Inventory inventory; private final boolean closeOnEvent; - private final Item[] itemsDisplayed; + private final ItemStackHolder[] itemsDisplayed; private Animation animation; private boolean closeable; @@ -41,7 +43,7 @@ public abstract class BaseWindow implements Window { this.inventory = inventory; this.closeable = closeable; this.closeOnEvent = closeOnEvent; - this.itemsDisplayed = new Item[size]; + this.itemsDisplayed = new ItemStackHolder[size]; initItems(); WindowManager.getInstance().addWindow(this); @@ -49,39 +51,61 @@ public abstract class BaseWindow implements Window { private void initItems() { for (int i = 0; i < size; i++) { - Item item = gui.getItem(i); - if (item != null) redrawItem(i, item, true); + ItemStackHolder holder = gui.getItemStackHolder(i); + if (holder != null) redrawItem(i, holder, true); } } - private void redrawItem(int index, Item item, boolean setItem) { - inventory.setItem(index, item == null ? null : item.getItemBuilder().buildFor(viewerUUID)); + private void redrawItem(int index, ItemStackHolder holder, boolean setItem) { + // put ItemStack in inventory + ItemStack itemStack = holder == null ? null : holder.getItemStack(viewerUUID); + inventory.setItem(index, itemStack); + if (setItem) { - // tell the Item that this is now its Window - if (item != null) item.addWindow(this); - // tell the previous item (if there is one) that this is no longer its window - Item previousItem = itemsDisplayed[index]; - if (previousItem != null) previousItem.removeWindow(this); + ItemStackHolder previousHolder = itemsDisplayed[index]; + if (previousHolder instanceof ItemSlotElement) { + ItemSlotElement element = (ItemSlotElement) previousHolder; + Item item = element.getItem(); + // check if the Item isn't still present on another index + if (getItemSlotElements(item).size() == 1) { + // only if not, remove Window from list in Item + item.removeWindow(this); + } + } else if (previousHolder instanceof VISlotElement) { + VISlotElement element = (VISlotElement) previousHolder; + VirtualInventory virtualInventory = element.getVirtualInventory(); + // check if the VirtualInventory isn't still present on another index + if (getVISlotElements(element.getVirtualInventory()).size() == 1) { + // only if not, remove Window from list in VirtualInventory + virtualInventory.removeWindow(this); + } + } - itemsDisplayed[index] = item; + // tell the Item or VirtualInventory that it is being displayed in this Window + if (holder instanceof ItemSlotElement) { + ((ItemSlotElement) holder).getItem().addWindow(this); + } else if (holder instanceof VISlotElement) { + ((VISlotElement) holder).getVirtualInventory().addWindow(this); + } + + itemsDisplayed[index] = holder; } } @Override public void handleTick() { for (int i = 0; i < size; i++) { - Item item = gui.getItem(i); - if (itemsDisplayed[i] != item) redrawItem(i, item, true); + ItemStackHolder holder = gui.getItemStackHolder(i); + if (itemsDisplayed[i] != holder) redrawItem(i, holder, true); } } @Override public void handleClick(InventoryClickEvent event) { - event.setCancelled(true); if (animation == null) { // if not in animation, let the gui handle the click gui.handleClick(event.getSlot(), (Player) event.getWhoClicked(), event.getClick(), event); - } + } else event.setCancelled(true); } @Override @@ -104,9 +128,24 @@ public abstract class BaseWindow implements Window { @Override public void handleItemBuilderUpdate(Item item) { - for (int i : ArrayUtils.findAllOccurrences(itemsDisplayed, item)) { - redrawItem(i, item, false); - } + getItemSlotElements(item).forEach((index, slotElement) -> + redrawItem(index, slotElement, false)); + } + + @Override + public void handleVirtualInventoryUpdate(VirtualInventory virtualInventory) { + getVISlotElements(virtualInventory).forEach((index, slotElement) -> + redrawItem(index, slotElement, false)); + } + + private Map getItemSlotElements(Item item) { + return ArrayUtils.findAllOccurrences(itemsDisplayed, holder -> holder instanceof ItemSlotElement + && ((ItemSlotElement) holder).getItem() == item); + } + + private Map getVISlotElements(VirtualInventory virtualInventory) { + return ArrayUtils.findAllOccurrences(itemsDisplayed, holder -> holder instanceof VISlotElement + && ((VISlotElement) holder).getVirtualInventory() == virtualInventory); } @Override @@ -117,6 +156,8 @@ public abstract class BaseWindow implements Window { Arrays.stream(itemsDisplayed) .filter(Objects::nonNull) + .filter(holder -> holder instanceof ItemSlotElement) + .map(holder -> ((ItemSlotElement) holder).getItem()) .forEach(item -> item.removeWindow(this)); if (closeForViewer) closeForViewer(); @@ -148,7 +189,11 @@ public abstract class BaseWindow implements Window { animation.addShowHandler((frame, index) -> redrawItem(index, itemsDisplayed[index], false)); animation.addFinishHandler(() -> this.animation = null); animation.setSlots(IntStream.range(0, size) - .filter(i -> itemsDisplayed[i] != null) + .filter(i -> { + ItemStackHolder element = itemsDisplayed[i]; + return !(element == null || (element instanceof VISlotElement + && !((VISlotElement) element).getVirtualInventory().hasItem(((VISlotElement) element).getIndex()))); + }) .boxed() .collect(Collectors.toCollection(ArrayList::new)));