VirtualInventory

This commit is contained in:
NichtStudioCode 2021-01-22 19:27:54 +01:00
parent b90853e165
commit 14f2d97e93
17 changed files with 597 additions and 186 deletions

@ -77,7 +77,6 @@ public abstract class BaseAnimation implements Animation {
} }
public void cancel() { public void cancel() {
System.out.println("ani cancel");
task.cancel(); task.cancel();
} }

@ -1,7 +1,9 @@
package de.studiocode.invgui.gui; package de.studiocode.invgui.gui;
import de.studiocode.invgui.gui.SlotElement.ItemStackHolder;
import de.studiocode.invgui.gui.impl.*; import de.studiocode.invgui.gui.impl.*;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import de.studiocode.invgui.virtualinventory.VirtualInventory;
import de.studiocode.invgui.window.Window; import de.studiocode.invgui.window.Window;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; 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.<br> * A GUI is a container for width * height {@link SlotElement}s.<br>
* Each {@link SlotElement} can either be an {@link Item} or a * Each {@link SlotElement} can either be an {@link Item},
* reference to another {@link GUI}s slot index.</br> * a reference to a {@link VirtualInventory}'s or another {@link GUI}'s
* slot index.<br>
* A {@link GUI} is not an {@link Inventory}, nor does * A {@link GUI} is not an {@link Inventory}, nor does
* it access one. It just contains {@link SlotElement}s and their positions.<br> * it access one. It just contains {@link SlotElement}s and their positions.<br>
* In order to create an {@link Inventory} which is visible * In order to create an {@link Inventory} which is visible
@ -114,7 +117,7 @@ public interface GUI {
* *
* @param x The x coordinate * @param x The x coordinate
* @param y The y 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); Item getItem(int x, int y);
@ -122,7 +125,7 @@ public interface GUI {
* Gets the {@link Item} placed on that slot. * Gets the {@link Item} placed on that slot.
* *
* @param index The slot index * @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); Item getItem(int index);
@ -133,6 +136,23 @@ public interface GUI {
*/ */
void addItems(@NotNull Item... items); 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. * 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); void fillRectangle(int x, int y, int width, int height, Item item, boolean replaceExisting);
// TODO: more filling methods
} }

@ -1,47 +1,108 @@
package de.studiocode.invgui.gui; package de.studiocode.invgui.gui;
import de.studiocode.invgui.item.Item; 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; interface ItemStackHolder {
private final GUI gui;
private final int slotNumber; ItemStack getItemStack(UUID viewerUUID);
public SlotElement(Item item) {
this.item = item;
this.gui = null;
this.slotNumber = -1;
} }
public SlotElement(GUI gui, int slotNumber) { /**
this.gui = gui; * Contains an Item
this.slotNumber = slotNumber; */
this.item = null; 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; * Links to a slot in another GUI
} */
class LinkedSlotElement implements SlotElement {
public boolean isGui() {
return gui != null; private final GUI gui;
}
private final int slot;
public GUI getGui() {
return gui; public LinkedSlotElement(GUI gui, int slot) {
} this.gui = gui;
this.slot = slot;
public int getSlotNumber() { }
return slotNumber;
} public GUI getGui() {
return gui;
public Item getItemFromGui() { }
return gui.getItem(slotNumber);
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();
}
} }
} }

@ -1,6 +1,7 @@
package de.studiocode.invgui.gui.builder; package de.studiocode.invgui.gui.builder;
import de.studiocode.invgui.gui.GUI; import de.studiocode.invgui.gui.GUI;
import de.studiocode.invgui.gui.SlotElement;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapedRecipe;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -25,10 +26,19 @@ public interface GUIBuilder {
* Sets an ingredient for the structure String, which will later be * Sets an ingredient for the structure String, which will later be
* used to set up the inventory correctly. * used to set up the inventory correctly.
* *
* @param c The ingredient key * @param key The ingredient key
* @param item The {@link Item} * @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}. * Builds the {@link GUI}.

@ -1,6 +1,7 @@
package de.studiocode.invgui.gui.builder.impl; package de.studiocode.invgui.gui.builder.impl;
import de.studiocode.invgui.gui.GUI; import de.studiocode.invgui.gui.GUI;
import de.studiocode.invgui.gui.SlotElement;
import de.studiocode.invgui.gui.builder.GUIBuilder; import de.studiocode.invgui.gui.builder.GUIBuilder;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -38,6 +39,11 @@ public abstract class BaseGUIBuilder implements GUIBuilder {
ingredientMap.put(key, new Ingredient(item)); 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) { public void setIngredient(char key, int special) {
ingredientMap.put(key, new Ingredient(special)); ingredientMap.put(key, new Ingredient(special));
} }
@ -63,11 +69,12 @@ public abstract class BaseGUIBuilder implements GUIBuilder {
return ingredients; return ingredients;
} }
protected void setItems(GUI gui) { protected void setSlotElements(GUI gui) {
Ingredient[] ingredients = parseStructure(); Ingredient[] ingredients = parseStructure();
for (int i = 0; i < gui.getSize(); i++) { for (int i = 0; i < gui.getSize(); i++) {
Ingredient ingredient = ingredients[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 { static class Ingredient {
private final Item item; private final SlotElement slotElement;
private final int special; private final int special;
public Ingredient(Item item) { 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; this.special = -1;
} }
public Ingredient(int special) { public Ingredient(int special) {
this.special = special; this.special = special;
this.item = null; this.slotElement = null;
} }
public Item getItem() { public SlotElement getSlotElement() {
if (isSpecial()) throw new IllegalStateException("Ingredient is special"); if (isSpecial()) throw new IllegalStateException("Ingredient is special");
return item; return slotElement;
} }
public int getSpecial() { public int getSpecial() {
if (isItem()) throw new IllegalStateException("Ingredient is item"); if (isSlotElement()) throw new IllegalStateException("Ingredient is item");
return special; return special;
} }
public boolean isItem() { public boolean isSlotElement() {
return item != null; return slotElement != null;
} }
public boolean isSpecial() { public boolean isSpecial() {
return item == null; return slotElement == null;
} }
} }

@ -11,7 +11,7 @@ public class SimpleGUIBuilder extends BaseGUIBuilder {
@Override @Override
public SimpleGUI build() { public SimpleGUI build() {
SimpleGUI gui = new SimpleGUI(getWidth(), getHeight()); SimpleGUI gui = new SimpleGUI(getWidth(), getHeight());
setItems(gui); setSlotElements(gui);
return gui; return gui;
} }

@ -22,7 +22,7 @@ public class SimplePagedGUIsBuilder extends PagedGUIBuilder {
SimplePagedGUIs gui = new SimplePagedGUIs(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), SimplePagedGUIs gui = new SimplePagedGUIs(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(),
getForwardItemIndex(), getForwardBuilder(), guis, getListSlots()); getForwardItemIndex(), getForwardBuilder(), guis, getListSlots());
setItems(gui); setSlotElements(gui);
return gui; return gui;
} }

@ -22,7 +22,7 @@ public class SimplePagedItemsGUIBuilder extends PagedGUIBuilder {
SimplePagedItemsGUI gui = new SimplePagedItemsGUI(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), SimplePagedItemsGUI gui = new SimplePagedItemsGUI(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(),
getForwardItemIndex(), getForwardBuilder(), items, getListSlots()); getForwardItemIndex(), getForwardBuilder(), items, getListSlots());
setItems(gui); setSlotElements(gui);
return gui; return gui;
} }

@ -1,29 +1,41 @@
package de.studiocode.invgui.gui.impl; package de.studiocode.invgui.gui.impl;
import de.studiocode.invgui.gui.GUI;
import de.studiocode.invgui.gui.SlotElement; import de.studiocode.invgui.gui.SlotElement;
import de.studiocode.invgui.gui.SlotElement.ItemStackHolder;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import de.studiocode.invgui.util.ArrayUtils;
import de.studiocode.invgui.util.SlotUtils; 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 org.jetbrains.annotations.NotNull;
import java.util.SortedSet; 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 width;
protected final int height; protected final int height;
protected final int size;
protected final SlotElement[] slotElements;
public BaseGUI(int width, int height) { public BaseGUI(int width, int height) {
super(width * height);
this.width = width; this.width = width;
this.height = height; 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 @Override
@ -31,61 +43,33 @@ public abstract class BaseGUI implements GUI {
setItem(convToIndex(x, y), item); setItem(convToIndex(x, y), item);
} }
@Override
public void setItem(int index, Item item) {
remove(index);
if (item != null) slotElements[index] = new SlotElement(item);
}
@Override @Override
public Item getItem(int x, int y) { public Item getItem(int x, int y) {
return getItem(convToIndex(x, 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 @Override
public void remove(int x, int y) { public void remove(int x, int y) {
remove(convToIndex(x, y)); remove(convToIndex(x, y));
} }
@Override @Override
public void remove(int index) { public int getWidth() {
SlotElement slotElement = slotElements[index]; return width;
if (slotElement == null) return;
if (slotElement.isItem()) {
slotElements[index] = null;
} else throw new IllegalArgumentException("Slot " + index + " is part of a nested GUI");
} }
@Override @Override
public void nest(int offset, @NotNull GUI gui) { public int getHeight() {
for (int i = 0; i < gui.getSize(); i++) slotElements[i + offset] = new SlotElement(gui, i); return height;
} }
@Override private int convToIndex(int x, int y) {
public void handleClick(int slotNumber, Player player, ClickType clickType, InventoryClickEvent event) { if (x >= width || y >= height) throw new IllegalArgumentException("Coordinates out of bounds");
SlotElement slotElement = slotElements[slotNumber]; return SlotUtils.convertToIndex(x, y, width);
if (slotElement == null) return;
if (slotElement.isGui())
slotElement.getGui().handleClick(slotElement.getSlotNumber(), player, clickType, event);
else slotElement.getItem().handleClick(clickType, player, event);
} }
// filling methods
public void fill(@NotNull SortedSet<Integer> slots, Item item, boolean replaceExisting) { public void fill(@NotNull SortedSet<Integer> slots, Item item, boolean replaceExisting) {
for (int slot : slots) { for (int slot : slots) {
if (!replaceExisting && slotElements[slot] != null) continue; 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); 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);
}
} }

@ -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;
}
}

@ -16,8 +16,6 @@ public class SimplePagedGUIs extends PagedGUI {
super(width, height, false, itemListSlots); super(width, height, false, itemListSlots);
this.guis = guis; this.guis = guis;
System.out.println("control slot " + backItemSlot + " fwd " + forwardItemSlot);
setControlItems(backItemSlot, new BackItem(backBuilder), forwardItemSlot, new ForwardItem(forwardBuilder)); setControlItems(backItemSlot, new BackItem(backBuilder), forwardItemSlot, new ForwardItem(forwardBuilder));
update(); update();
} }

@ -1,6 +1,7 @@
package de.studiocode.invgui.gui.impl; package de.studiocode.invgui.gui.impl;
import de.studiocode.invgui.gui.SlotElement; import de.studiocode.invgui.gui.SlotElement;
import de.studiocode.invgui.gui.SlotElement.ItemSlotElement;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import de.studiocode.invgui.item.itembuilder.ItemBuilder; import de.studiocode.invgui.item.itembuilder.ItemBuilder;
@ -32,7 +33,7 @@ public class SimplePagedItemsGUI extends PagedGUI {
int from = page * length; int from = page * length;
int to = Math.min(from + length, items.size()); 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());
} }
} }

@ -2,8 +2,9 @@ package de.studiocode.invgui.util;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.HashMap;
import java.util.List; import java.util.Map;
import java.util.function.Predicate;
public class ArrayUtils { public class ArrayUtils {
@ -15,12 +16,23 @@ public class ArrayUtils {
return -1; return -1;
} }
public static List<Integer> findAllOccurrences(@NotNull Object[] array, @NotNull Object toFind) { // public static <T> List<Integer> findAllOccurrences(@NotNull T[] array, @NotNull T toFind) {
List<Integer> occurrences = new ArrayList<>(); // 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<>();
for (int index = 0; index < array.length; index++) { for (int index = 0; index < array.length; index++) {
Object obj = array[index]; T t = array[index];
if (obj != null && obj.equals(toFind)) occurrences.add(index); if (predicate.test(t)) occurrences.put(index, t);
} }
return occurrences; return occurrences;

@ -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<Window> 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)));
}
}

@ -4,6 +4,7 @@ import de.studiocode.invgui.animation.Animation;
import de.studiocode.invgui.gui.GUI; import de.studiocode.invgui.gui.GUI;
import de.studiocode.invgui.item.Item; import de.studiocode.invgui.item.Item;
import de.studiocode.invgui.item.itembuilder.ItemBuilder; 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.BaseWindow;
import de.studiocode.invgui.window.impl.DropperWindow; import de.studiocode.invgui.window.impl.DropperWindow;
import de.studiocode.invgui.window.impl.HopperWindow; import de.studiocode.invgui.window.impl.HopperWindow;
@ -80,6 +81,15 @@ public interface Window {
*/ */
void handleItemBuilderUpdate(Item item); 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. * Removes the {@link Window} from the {@link WindowManager} list.
* If this method is called, the {@link Window} can't be shown again. * If this method is called, the {@link Window} can't be shown again.

@ -6,9 +6,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.*;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
@ -34,7 +32,7 @@ public class WindowManager implements Listener {
* @return The {@link WindowManager} instance * @return The {@link WindowManager} instance
*/ */
public static WindowManager getInstance() { 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 @EventHandler
public void handleInventoryClick(InventoryClickEvent event) { public void handleInventoryClick(InventoryClickEvent event) {
findWindow(event.getClickedInventory()).ifPresent(window -> window.handleClick(event)); Optional<Window> w = findWindow(event.getClickedInventory());
if (w.isPresent()) { // player clicked window
w.ifPresent(window -> window.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);
}
}
}
@EventHandler
public void handleInventoryDrag(InventoryDragEvent event) {
// currently, dragging items is not supported
findWindow(event.getInventory()).ifPresent(w -> event.setCancelled(true));
} }
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)

@ -3,8 +3,12 @@ package de.studiocode.invgui.window.impl;
import de.studiocode.invgui.InvGui; import de.studiocode.invgui.InvGui;
import de.studiocode.invgui.animation.Animation; import de.studiocode.invgui.animation.Animation;
import de.studiocode.invgui.gui.GUI; 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.item.Item;
import de.studiocode.invgui.util.ArrayUtils; import de.studiocode.invgui.util.ArrayUtils;
import de.studiocode.invgui.virtualinventory.VirtualInventory;
import de.studiocode.invgui.window.Window; import de.studiocode.invgui.window.Window;
import de.studiocode.invgui.window.WindowManager; import de.studiocode.invgui.window.WindowManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -13,11 +17,9 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -28,7 +30,7 @@ public abstract class BaseWindow implements Window {
private final UUID viewerUUID; private final UUID viewerUUID;
private final Inventory inventory; private final Inventory inventory;
private final boolean closeOnEvent; private final boolean closeOnEvent;
private final Item[] itemsDisplayed; private final ItemStackHolder[] itemsDisplayed;
private Animation animation; private Animation animation;
private boolean closeable; private boolean closeable;
@ -41,7 +43,7 @@ public abstract class BaseWindow implements Window {
this.inventory = inventory; this.inventory = inventory;
this.closeable = closeable; this.closeable = closeable;
this.closeOnEvent = closeOnEvent; this.closeOnEvent = closeOnEvent;
this.itemsDisplayed = new Item[size]; this.itemsDisplayed = new ItemStackHolder[size];
initItems(); initItems();
WindowManager.getInstance().addWindow(this); WindowManager.getInstance().addWindow(this);
@ -49,39 +51,61 @@ public abstract class BaseWindow implements Window {
private void initItems() { private void initItems() {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Item item = gui.getItem(i); ItemStackHolder holder = gui.getItemStackHolder(i);
if (item != null) redrawItem(i, item, true); if (holder != null) redrawItem(i, holder, true);
} }
} }
private void redrawItem(int index, Item item, boolean setItem) { private void redrawItem(int index, ItemStackHolder holder, boolean setItem) {
inventory.setItem(index, item == null ? null : item.getItemBuilder().buildFor(viewerUUID)); // put ItemStack in inventory
ItemStack itemStack = holder == null ? null : holder.getItemStack(viewerUUID);
inventory.setItem(index, itemStack);
if (setItem) { 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 // tell the previous item (if there is one) that this is no longer its window
Item previousItem = itemsDisplayed[index]; ItemStackHolder previousHolder = itemsDisplayed[index];
if (previousItem != null) previousItem.removeWindow(this); 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 @Override
public void handleTick() { public void handleTick() {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Item item = gui.getItem(i); ItemStackHolder holder = gui.getItemStackHolder(i);
if (itemsDisplayed[i] != item) redrawItem(i, item, true); if (itemsDisplayed[i] != holder) redrawItem(i, holder, true);
} }
} }
@Override @Override
public void handleClick(InventoryClickEvent event) { public void handleClick(InventoryClickEvent event) {
event.setCancelled(true);
if (animation == null) { // if not in animation, let the gui handle the click if (animation == null) { // if not in animation, let the gui handle the click
gui.handleClick(event.getSlot(), (Player) event.getWhoClicked(), event.getClick(), event); gui.handleClick(event.getSlot(), (Player) event.getWhoClicked(), event.getClick(), event);
} } else event.setCancelled(true);
} }
@Override @Override
@ -104,9 +128,24 @@ public abstract class BaseWindow implements Window {
@Override @Override
public void handleItemBuilderUpdate(Item item) { public void handleItemBuilderUpdate(Item item) {
for (int i : ArrayUtils.findAllOccurrences(itemsDisplayed, item)) { getItemSlotElements(item).forEach((index, slotElement) ->
redrawItem(i, item, false); redrawItem(index, slotElement, false));
} }
@Override
public void handleVirtualInventoryUpdate(VirtualInventory virtualInventory) {
getVISlotElements(virtualInventory).forEach((index, slotElement) ->
redrawItem(index, slotElement, false));
}
private Map<Integer, ItemStackHolder> getItemSlotElements(Item item) {
return ArrayUtils.findAllOccurrences(itemsDisplayed, holder -> holder instanceof ItemSlotElement
&& ((ItemSlotElement) holder).getItem() == item);
}
private Map<Integer, ItemStackHolder> getVISlotElements(VirtualInventory virtualInventory) {
return ArrayUtils.findAllOccurrences(itemsDisplayed, holder -> holder instanceof VISlotElement
&& ((VISlotElement) holder).getVirtualInventory() == virtualInventory);
} }
@Override @Override
@ -117,6 +156,8 @@ public abstract class BaseWindow implements Window {
Arrays.stream(itemsDisplayed) Arrays.stream(itemsDisplayed)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(holder -> holder instanceof ItemSlotElement)
.map(holder -> ((ItemSlotElement) holder).getItem())
.forEach(item -> item.removeWindow(this)); .forEach(item -> item.removeWindow(this));
if (closeForViewer) closeForViewer(); if (closeForViewer) closeForViewer();
@ -148,7 +189,11 @@ public abstract class BaseWindow implements Window {
animation.addShowHandler((frame, index) -> redrawItem(index, itemsDisplayed[index], false)); animation.addShowHandler((frame, index) -> redrawItem(index, itemsDisplayed[index], false));
animation.addFinishHandler(() -> this.animation = null); animation.addFinishHandler(() -> this.animation = null);
animation.setSlots(IntStream.range(0, size) 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() .boxed()
.collect(Collectors.toCollection(ArrayList::new))); .collect(Collectors.toCollection(ArrayList::new)));