From 1d08e06d5cec410448530f754903d36b321183dc Mon Sep 17 00:00:00 2001 From: NichtStudioCode <51272202+NichtStudioCode@users.noreply.github.com> Date: Wed, 20 Jan 2021 22:27:13 +0100 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 60 +++++ pom.xml | 62 +++++ .../java/de/studiocode/invgui/InvGui.java | 32 +++ .../invgui/animation/Animation.java | 25 ++ .../invgui/animation/impl/BaseAnimation.java | 103 ++++++++ .../invgui/animation/impl/IndexAnimation.java | 29 +++ .../animation/impl/RandomAnimation.java | 34 +++ .../java/de/studiocode/invgui/gui/GUI.java | 230 ++++++++++++++++++ .../de/studiocode/invgui/gui/SlotElement.java | 47 ++++ .../invgui/gui/builder/GUIBuilder.java | 40 +++ .../gui/builder/impl/BaseGUIBuilder.java | 124 ++++++++++ .../gui/builder/impl/PagedGUIBuilder.java | 71 ++++++ .../gui/builder/impl/SimpleGUIBuilder.java | 18 ++ .../builder/impl/SimplePagedGUIsBuilder.java | 34 +++ .../impl/SimplePagedItemsGUIBuilder.java | 34 +++ .../studiocode/invgui/gui/impl/BaseGUI.java | 176 ++++++++++++++ .../studiocode/invgui/gui/impl/PagedGUI.java | 151 ++++++++++++ .../studiocode/invgui/gui/impl/SimpleGUI.java | 9 + .../invgui/gui/impl/SimplePagedGUIs.java | 35 +++ .../invgui/gui/impl/SimplePagedItemsGUI.java | 38 +++ .../java/de/studiocode/invgui/item/Item.java | 64 +++++ .../studiocode/invgui/item/impl/BaseItem.java | 36 +++ .../invgui/item/impl/CommandItem.java | 25 ++ .../invgui/item/impl/SimpleItem.java | 30 +++ .../invgui/item/itembuilder/ItemBuilder.java | 212 ++++++++++++++++ .../de/studiocode/invgui/util/ArrayUtils.java | 29 +++ .../invgui/util/MojangApiUtils.java | 70 ++++++ .../java/de/studiocode/invgui/util/Pair.java | 28 +++ .../invgui/util/ReflectionUtils.java | 20 ++ .../de/studiocode/invgui/util/SlotUtils.java | 44 ++++ .../de/studiocode/invgui/util/WebUtils.java | 23 ++ .../de/studiocode/invgui/window/Window.java | 134 ++++++++++ .../invgui/window/WindowManager.java | 100 ++++++++ .../invgui/window/impl/BaseWindow.java | 211 ++++++++++++++++ .../invgui/window/impl/DropperWindow.java | 27 ++ .../invgui/window/impl/HopperWindow.java | 27 ++ .../window/impl/NormalInventoryWindow.java | 25 ++ 38 files changed, 2459 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/de/studiocode/invgui/InvGui.java create mode 100644 src/main/java/de/studiocode/invgui/animation/Animation.java create mode 100644 src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java create mode 100644 src/main/java/de/studiocode/invgui/animation/impl/IndexAnimation.java create mode 100644 src/main/java/de/studiocode/invgui/animation/impl/RandomAnimation.java create mode 100644 src/main/java/de/studiocode/invgui/gui/GUI.java create mode 100644 src/main/java/de/studiocode/invgui/gui/SlotElement.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/impl/PagedGUIBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java create mode 100644 src/main/java/de/studiocode/invgui/gui/impl/PagedGUI.java create mode 100644 src/main/java/de/studiocode/invgui/gui/impl/SimpleGUI.java create mode 100644 src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java create mode 100644 src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java create mode 100644 src/main/java/de/studiocode/invgui/item/Item.java create mode 100644 src/main/java/de/studiocode/invgui/item/impl/BaseItem.java create mode 100644 src/main/java/de/studiocode/invgui/item/impl/CommandItem.java create mode 100644 src/main/java/de/studiocode/invgui/item/impl/SimpleItem.java create mode 100644 src/main/java/de/studiocode/invgui/item/itembuilder/ItemBuilder.java create mode 100644 src/main/java/de/studiocode/invgui/util/ArrayUtils.java create mode 100644 src/main/java/de/studiocode/invgui/util/MojangApiUtils.java create mode 100644 src/main/java/de/studiocode/invgui/util/Pair.java create mode 100644 src/main/java/de/studiocode/invgui/util/ReflectionUtils.java create mode 100644 src/main/java/de/studiocode/invgui/util/SlotUtils.java create mode 100644 src/main/java/de/studiocode/invgui/util/WebUtils.java create mode 100644 src/main/java/de/studiocode/invgui/window/Window.java create mode 100644 src/main/java/de/studiocode/invgui/window/WindowManager.java create mode 100644 src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java create mode 100644 src/main/java/de/studiocode/invgui/window/impl/DropperWindow.java create mode 100644 src/main/java/de/studiocode/invgui/window/impl/HopperWindow.java create mode 100644 src/main/java/de/studiocode/invgui/window/impl/NormalInventoryWindow.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bc51b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Local pydev project properties file +*.pydevproject + +# Eclipse Metadata +.metadata + +# Gradle temporarily build directory +.gradle + +# Eclipse Build Directory +bin/ + +# Temp files should never be added to git +tmp/ +*.tmp +*.swp + +# Backup files also shouldn't be saved +*.bak + +# Interface Builders shouldn't be added to git +*~.nib + +# As the name says, it's only local +local.properties + +# IDE Settings shouldn't be added to git +.settings/ +.loadpath +*.iml + +## Main Jetbrains directory with lots of local settings +.idea/ + +## File-based project format: +*.ipr +*.iws + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Target directories shouldn't be added to git +target/ + +# Eclipse Recommenders are really unnecessary for git +.recommenders/ + +# Local Libraries shouldn't be added to git +.libs/ + +# Testing +src/test/ +src/main/java/de/studiocode/invgui/InvGuiPlugin.java +src/main/resources/plugin.yml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..97ab308 --- /dev/null +++ b/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + de.studiocode + InvGUI + 0.1-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + minecraft-repo + https://libraries.minecraft.net/ + + + + + + org.spigotmc + spigot-api + 1.16.4-R0.1-SNAPSHOT + provided + + + com.mojang + authlib + 1.5.21 + provided + + + * + * + + + + + org.jetbrains + annotations-java5 + RELEASE + compile + + + + \ No newline at end of file diff --git a/src/main/java/de/studiocode/invgui/InvGui.java b/src/main/java/de/studiocode/invgui/InvGui.java new file mode 100644 index 0000000..6ca3309 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/InvGui.java @@ -0,0 +1,32 @@ +package de.studiocode.invgui; + +import de.studiocode.invgui.window.WindowManager; +import org.bukkit.plugin.Plugin; + +public class InvGui { + + private static InvGui instance; + + private Plugin plugin; + + public static InvGui getInstance() { + return instance == null ? instance = new InvGui() : instance; + } + + public Plugin getPlugin() { + if (plugin == null) + throw new IllegalStateException("Please set your plugin using InvGui.getInstance().setPlugin"); + return plugin; + } + + public void setPlugin(Plugin plugin) { + this.plugin = plugin; + } + + public void onDisable() { + if (WindowManager.hasInstance()) + WindowManager.getInstance().getWindows() + .forEach(w -> w.close(true)); + } + +} diff --git a/src/main/java/de/studiocode/invgui/animation/Animation.java b/src/main/java/de/studiocode/invgui/animation/Animation.java new file mode 100644 index 0000000..a2b369f --- /dev/null +++ b/src/main/java/de/studiocode/invgui/animation/Animation.java @@ -0,0 +1,25 @@ +package de.studiocode.invgui.animation; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.function.BiConsumer; + +public interface Animation { + + void setPlayer(@NotNull Player player); + + void setBounds(int width, int height); + + void setSlots(List slots); + + void addShowHandler(@NotNull BiConsumer show); + + void setFinishHandler(@NotNull Runnable finish); + + void start(); + + void cancel(); + +} diff --git a/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java b/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java new file mode 100644 index 0000000..d1a7cf7 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/animation/impl/BaseAnimation.java @@ -0,0 +1,103 @@ +package de.studiocode.invgui.animation.impl; + +import de.studiocode.invgui.InvGui; +import de.studiocode.invgui.animation.Animation; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiConsumer; + +public abstract class BaseAnimation implements Animation { + + private final int tickDelay; + + private int width; + private int height; + private int size; + private Player player; + private CopyOnWriteArrayList slots; + private BiConsumer show; + private Runnable finish; + + private BukkitTask task; + private int frame; + + public BaseAnimation(int tickDelay) { + this.tickDelay = tickDelay; + } + + protected abstract void handleFrame(int frame); + + @Override + public void setBounds(int width, int height) { + this.width = width; + this.height = height; + this.size = width * height; + } + + @Override + public void addShowHandler(@NotNull BiConsumer show) { + if (this.show != null) this.show = this.show.andThen(show); + else this.show = show; + } + + protected void show(int i) { + show.accept(frame, i); + } + + @Override + public void setFinishHandler(@NotNull Runnable finish) { + this.finish = finish; + } + + public CopyOnWriteArrayList getSlots() { + return slots; + } + + @Override + public void setSlots(List slots) { + this.slots = new CopyOnWriteArrayList<>(slots); + } + + protected void finished() { + task.cancel(); + finish.run(); + } + + public void start() { + task = Bukkit.getScheduler().runTaskTimer(InvGui.getInstance().getPlugin(), () -> { + handleFrame(frame); + frame++; + }, 0, tickDelay); + } + + public void cancel() { + System.out.println("ani cancel"); + task.cancel(); + } + + protected int getWidth() { + return width; + } + + protected int getHeight() { + return height; + } + + protected int getSize() { + return size; + } + + protected Player getPlayer() { + return player; + } + + public void setPlayer(@NotNull Player player) { + this.player = player; + } + +} diff --git a/src/main/java/de/studiocode/invgui/animation/impl/IndexAnimation.java b/src/main/java/de/studiocode/invgui/animation/impl/IndexAnimation.java new file mode 100644 index 0000000..1d2d07d --- /dev/null +++ b/src/main/java/de/studiocode/invgui/animation/impl/IndexAnimation.java @@ -0,0 +1,29 @@ +package de.studiocode.invgui.animation.impl; + +import de.studiocode.invgui.item.Item; +import org.bukkit.Sound; + +import java.util.List; + +/** + * Lets the {@link Item}s pop up index after index. + */ +public class IndexAnimation extends BaseAnimation { + + public IndexAnimation(int tickDelay, boolean sound) { + super(tickDelay); + + if (sound) addShowHandler((frame, index) -> getPlayer().playSound(getPlayer().getLocation(), + Sound.ENTITY_ITEM_PICKUP, 1, 1)); + } + + @Override + protected void handleFrame(int frame) { + List slots = getSlots(); + if (!slots.isEmpty()) { + show(slots.get(0)); + slots.remove(0); + } else finished(); + } + +} diff --git a/src/main/java/de/studiocode/invgui/animation/impl/RandomAnimation.java b/src/main/java/de/studiocode/invgui/animation/impl/RandomAnimation.java new file mode 100644 index 0000000..53e03b5 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/animation/impl/RandomAnimation.java @@ -0,0 +1,34 @@ +package de.studiocode.invgui.animation.impl; + +import de.studiocode.invgui.item.Item; +import org.bukkit.Sound; + +import java.util.List; +import java.util.Random; + +/** + * Lets the {@link Item}s pop up randomly. + */ +public class RandomAnimation extends BaseAnimation { + + private final Random random = new Random(); + + public RandomAnimation(int tickDelay, boolean sound) { + super(tickDelay); + + if (sound) addShowHandler((frame, index) -> getPlayer().playSound(getPlayer().getLocation(), + Sound.ENTITY_ITEM_PICKUP, 1, 1)); + } + + @Override + protected void handleFrame(int frame) { + List slots = getSlots(); + + if (!slots.isEmpty()) { + int slot = slots.get(random.nextInt(slots.size())); + slots.remove(Integer.valueOf(slot)); + show(slot); + } else finished(); + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/GUI.java b/src/main/java/de/studiocode/invgui/gui/GUI.java new file mode 100644 index 0000000..5fa1cdc --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/GUI.java @@ -0,0 +1,230 @@ +package de.studiocode.invgui.gui; + +import de.studiocode.invgui.gui.impl.*; +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.window.Window; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +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.
+ * 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 + * to players, you will need to use a {@link Window}. + * + * @see BaseGUI + * @see PagedGUI + * @see SimpleGUI + * @see SimplePagedItemsGUI + * @see SimplePagedGUIs + */ +public interface GUI { + + /** + * Gets the size of the {@link GUI}. + * + * @return The size of the gui. + */ + int getSize(); + + /** + * Gets the width of the {@link GUI} + * + * @return The width of the {@link GUI} + */ + int getWidth(); + + /** + * Gets the height of the {@link GUI} + * + * @return The height of the {@link GUI} + */ + int getHeight(); + + /** + * Sets the {@link SlotElement} on these coordinates. + * If you need to set an {@link Item}, please use {@link #setItem(int, int, Item)} instead. + * + * @param x The x coordinate + * @param y The y coordinate + * @param slotElement The {@link SlotElement} to be placed there. + */ + void setSlotElement(int x, int y, @NotNull SlotElement slotElement); + + /** + * Sets the {@link SlotElement} on these coordinates. + * If you need to set an {@link Item}, please use {@link #setItem(int, Item)} instead. + * + * @param index The slot index + * @param slotElement The {@link SlotElement} to be placed there. + */ + void setSlotElement(int index, @NotNull SlotElement slotElement); + + /** + * Gets the {@link SlotElement} on these coordinates + * + * @param x The x coordinate + * @param y The y coordinate + * @return The {@link SlotElement} placed there + */ + SlotElement getSlotElement(int x, int y); + + /** + * Gets the {@link SlotElement} placed on that slot + * + * @param index The slot index + * @return The {@link SlotElement} placed on that slot + */ + SlotElement getSlotElement(int index); + + /** + * Gets a all {@link SlotElement}s of this {@link GUI} in an Array. + * + * @return All {@link SlotElement}s of this {@link GUI} + */ + SlotElement[] getSlotElements(); + + /** + * Sets the {@link Item} on these coordinates. + * + * @param x The x coordinate + * @param y The y coordinate + * @param item The {@link Item} that should be placed on these coordinates + * or null to remove the {@link Item} that is currently there. + */ + void setItem(int x, int y, Item item); + + /** + * Sets the {@link Item} on that slot + * + * @param index The slot index + * @param item The {@link Item} that should be placed on that slot or null + * to remove the {@link Item} that is currently there. + */ + void setItem(int index, Item item); + + /** + * Gets the {@link Item} on these coordinates. + * + * @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 + */ + Item getItem(int x, int y); + + /** + * 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 + */ + Item getItem(int index); + + /** + * Adds {@link Item}s to the gui. + * + * @param items The {@link Item}s that should be added to the gui + */ + void addItems(@NotNull Item... items); + + /** + * Removes an {@link Item} by its coordinates. + * + * @param x The x coordinate + * @param y The y coordinate + */ + void remove(int x, int y); + + /** + * Remove the {@link Item} which are placed on these slots. + * + * @param index The slot index of the {@link Item}s that should be removed + */ + void remove(int index); + + /** + * Fills the slots of this {@link GUI} with the content of another one, + * allowing for nested {@link GUI}s + * + * @param offset Defines the index where the nested {@link GUI} should start (inclusive) + * @param gui The {@link GUI} which should be put inside this {@link GUI} + */ + void nest(int offset, @NotNull GUI gui); + + /** + * A method called if a slot in the {@link Inventory} has been clicked. + * + * @param slot The slot that has been clicked + * @param player The {@link Player} that clicked + * @param clickType The {@link ClickType} + * @param event The {@link InventoryClickEvent} + */ + void handleClick(int slot, Player player, ClickType clickType, InventoryClickEvent event); + + // ---- fill methods ---- + + /** + * Fills the {@link GUI} with {@link Item}s. + * + * @param start The start index of the fill (inclusive) + * @param end The end index of the fill (exclusive) + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + void fill(int start, int end, Item item, boolean replaceExisting); + + /** + * Fills the entire {@link GUI} with {@link Item}s. + * + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + void fill(Item item, boolean replaceExisting); + + /** + * Fills one row with an specific {@link Item} + * + * @param row The row + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + void fillRow(int row, Item item, boolean replaceExisting); + + /** + * Fills one column with an specific {@link Item} + * + * @param column The column + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + void fillColumn(int column, Item item, boolean replaceExisting); + + /** + * Fills the borders of this {@link GUI} with an specific {@link Item} + * + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + void fillBorders(Item item, boolean replaceExisting); + + /** + * Fills a rectangle in this {@link GUI} with an specific {@link Item} + * + * @param x The x coordinate where the rectangle should start. + * @param y The y coordinate where the rectangle should start. + * @param width The width of the rectangle. + * @param height The height of the rectangle + * @param item The {@link Item} that should be used or null to remove an existing item. + * @param replaceExisting If existing {@link Item}s should be replaced. + */ + 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 new file mode 100644 index 0000000..2e9e0d3 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/SlotElement.java @@ -0,0 +1,47 @@ +package de.studiocode.invgui.gui; + +import de.studiocode.invgui.item.Item; + +public class 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; + } + + public SlotElement(GUI gui, int slotNumber) { + this.gui = gui; + this.slotNumber = slotNumber; + this.item = null; + } + + public boolean isItem() { + return item != null; + } + + 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); + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java new file mode 100644 index 0000000..5b430c1 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/GUIBuilder.java @@ -0,0 +1,40 @@ +package de.studiocode.invgui.gui.builder; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.item.Item; +import org.bukkit.inventory.ShapedRecipe; +import org.jetbrains.annotations.NotNull; + +/** + * A utility class to easily construct {@link GUI}s.
+ * It provides similar functionality to Bukkit's {@link ShapedRecipe}, as it + * allows for a structure String which defines the layout of the {@link GUI}. + */ +public interface GUIBuilder { + + /** + * Sets the structure of the {@link GUI}. + * The structure is a {@link String} of characters, like used in {@link ShapedRecipe} to + * define where which {@link Item} should be. + * + * @param structure The structure {@link String} + */ + void setStructure(@NotNull String structure); + + /** + * Sets an ingredient for the structure String, which will later be + * used to set up the inventory correctly. + * + * @param c The ingredient key + * @param item The {@link Item} + */ + void setIngredient(char c, @NotNull Item item); + + /** + * Builds the {@link GUI}. + * + * @return The built {@link GUI} + */ + GUI build(); + +} 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 new file mode 100644 index 0000000..86fda06 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/BaseGUIBuilder.java @@ -0,0 +1,124 @@ +package de.studiocode.invgui.gui.builder.impl; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.builder.GUIBuilder; +import de.studiocode.invgui.item.Item; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class BaseGUIBuilder implements GUIBuilder { + + private final int width; + private final int height; + private final int size; + + private final Map ingredientMap = new HashMap<>(); + + private String structure; + + public BaseGUIBuilder(int width, int height) { + this.width = width; + this.height = height; + this.size = width * height; + } + + @Override + public void setStructure(@NotNull String structure) { + structure = structure.replace(" ", ""); + if (structure.length() != size) throw new IllegalArgumentException("Structure length does not match size"); + this.structure = structure; + } + + @Override + public void setIngredient(char key, @NotNull Item item) { + ingredientMap.put(key, new Ingredient(item)); + } + + public void setIngredient(char key, int special) { + ingredientMap.put(key, new Ingredient(special)); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + private Ingredient[] parseStructure() { + if (structure == null) throw new IllegalStateException("Structure has not been set yet."); + + Ingredient[] ingredients = new Ingredient[size]; + int i = 0; + for (char c : structure.toCharArray()) { + if (c != '.') ingredients[i] = this.ingredientMap.get(c); + i++; + } + + return ingredients; + } + + protected void setItems(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()); + } + } + + public List findIndicesOf(int special) { + List indices = new ArrayList<>(); + + Ingredient[] ingredients = parseStructure(); + for (int i = 0; i < size; i++) { + Ingredient ingredient = ingredients[i]; + if (ingredient != null && ingredient.isSpecial() && ingredient.getSpecial() == special) + indices.add(i); + } + + return indices; + } + + public abstract GUI build(); + + static class Ingredient { + + private final Item item; + private final int special; + + public Ingredient(Item item) { + this.item = item; + this.special = -1; + } + + public Ingredient(int special) { + this.special = special; + this.item = null; + } + + public Item getItem() { + if (isSpecial()) throw new IllegalStateException("Ingredient is special"); + return item; + } + + public int getSpecial() { + if (isItem()) throw new IllegalStateException("Ingredient is item"); + return special; + } + + public boolean isItem() { + return item != null; + } + + public boolean isSpecial() { + return item == null; + } + + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/builder/impl/PagedGUIBuilder.java b/src/main/java/de/studiocode/invgui/gui/builder/impl/PagedGUIBuilder.java new file mode 100644 index 0000000..2fe7622 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/PagedGUIBuilder.java @@ -0,0 +1,71 @@ +package de.studiocode.invgui.gui.builder.impl; + +import de.studiocode.invgui.item.itembuilder.ItemBuilder; + +import java.util.List; + +public abstract class PagedGUIBuilder extends BaseGUIBuilder { + + private ItemBuilder backBuilder; + private ItemBuilder forwardBuilder; + private int[] listSlots; + + public PagedGUIBuilder(int width, int height) { + super(width, height); + } + + protected int getBackItemIndex() { + List indices = findIndicesOf(0); + if (indices.isEmpty()) + throw new IllegalStateException("BackItem index is not set"); + + return indices.get(0); + } + + protected int getForwardItemIndex() { + List indices = findIndicesOf(1); + if (indices.isEmpty()) + throw new IllegalStateException("ForwardItem index is not set"); + + return indices.get(0); + } + + protected int[] getListSlots() { + if (listSlots == null) + return findIndicesOf(2).stream().mapToInt(Integer::intValue).toArray(); + else return listSlots; + } + + public void setListSlots(int[] listSlots) { + this.listSlots = listSlots; + } + + public void setBackItemIngredient(char c) { + setIngredient(c, 0); + } + + public void setForwardItemIngredient(char c) { + setIngredient(c, 1); + } + + public void setListSlotIngredient(char c) { + setIngredient(c, 2); + } + + protected ItemBuilder getBackBuilder() { + return backBuilder; + } + + public void setBackBuilder(ItemBuilder backBuilder) { + this.backBuilder = backBuilder; + } + + protected ItemBuilder getForwardBuilder() { + return forwardBuilder; + } + + public void setForwardBuilder(ItemBuilder forwardBuilder) { + this.forwardBuilder = forwardBuilder; + } + +} 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 new file mode 100644 index 0000000..d726957 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimpleGUIBuilder.java @@ -0,0 +1,18 @@ +package de.studiocode.invgui.gui.builder.impl; + +import de.studiocode.invgui.gui.impl.SimpleGUI; + +public class SimpleGUIBuilder extends BaseGUIBuilder { + + public SimpleGUIBuilder(int width, int height) { + super(width, height); + } + + @Override + public SimpleGUI build() { + SimpleGUI gui = new SimpleGUI(getWidth(), getHeight()); + setItems(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 new file mode 100644 index 0000000..079a2c8 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedGUIsBuilder.java @@ -0,0 +1,34 @@ +package de.studiocode.invgui.gui.builder.impl; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.impl.SimplePagedGUIs; + +import java.util.List; + +public class SimplePagedGUIsBuilder extends PagedGUIBuilder { + + private List guis; + + public SimplePagedGUIsBuilder(int width, int height) { + super(width, height); + } + + @Override + public SimplePagedGUIs build() { + if (getBackBuilder() == null || getForwardBuilder() == null) + throw new IllegalStateException("BackBuilder or ForwardBuilder haven't been set yet"); + if (guis == null) + throw new IllegalStateException("GUIs haven't been set yet"); + + SimplePagedGUIs gui = new SimplePagedGUIs(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), + getForwardItemIndex(), getForwardBuilder(), guis, getListSlots()); + setItems(gui); + + return gui; + } + + public void setGuis(List guis) { + this.guis = guis; + } + +} 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 new file mode 100644 index 0000000..1ba6bd5 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/builder/impl/SimplePagedItemsGUIBuilder.java @@ -0,0 +1,34 @@ +package de.studiocode.invgui.gui.builder.impl; + +import de.studiocode.invgui.gui.impl.SimplePagedItemsGUI; +import de.studiocode.invgui.item.Item; + +import java.util.List; + +public class SimplePagedItemsGUIBuilder extends PagedGUIBuilder { + + private List items; + + public SimplePagedItemsGUIBuilder(int width, int height) { + super(width, height); + } + + @Override + public SimplePagedItemsGUI build() { + if (getBackBuilder() == null || getForwardBuilder() == null) + throw new IllegalStateException("BackBuilder or ForwardBuilder haven't been set yet"); + if (items == null) + throw new IllegalStateException("Items haven't been set yet"); + + SimplePagedItemsGUI gui = new SimplePagedItemsGUI(getWidth(), getHeight(), getBackItemIndex(), getBackBuilder(), + getForwardItemIndex(), getForwardBuilder(), items, getListSlots()); + setItems(gui); + + return gui; + } + + public void setItems(List items) { + this.items = items; + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java new file mode 100644 index 0000000..649dc1b --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/BaseGUI.java @@ -0,0 +1,176 @@ +package de.studiocode.invgui.gui.impl; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement; +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 { + + protected final int width; + protected final int height; + protected final int size; + protected final SlotElement[] slotElements; + + public BaseGUI(int width, int height) { + this.width = width; + this.height = height; + this.size = width * height; + slotElements = new SlotElement[size]; + } + + @Override + public void setItem(int x, int y, Item 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 + 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"); + } + + @Override + public void nest(int offset, @NotNull GUI gui) { + for (int i = 0; i < gui.getSize(); i++) slotElements[i + offset] = new SlotElement(gui, i); + } + + @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); + } + + public void fill(@NotNull SortedSet slots, Item item, boolean replaceExisting) { + for (int slot : slots) { + if (!replaceExisting && slotElements[slot] != null) continue; + setItem(slot, item); + } + } + + @Override + public void fill(int start, int end, Item item, boolean replaceExisting) { + for (int i = start; i < end; i++) { + if (!replaceExisting && slotElements[i] != null) continue; + setItem(i, item); + } + } + + @Override + public void fill(Item item, boolean replaceExisting) { + fill(0, size, item, replaceExisting); + } + + @Override + public void fillRow(int row, Item item, boolean replaceExisting) { + if (row >= height) throw new IllegalArgumentException("Row out of bounds"); + fill(SlotUtils.getSlotsRow(row, width), item, replaceExisting); + } + + @Override + public void fillColumn(int column, Item item, boolean replaceExisting) { + if (column >= width) throw new IllegalArgumentException("Column out of bounds"); + fill(SlotUtils.getSlotsColumn(column, width, height), item, replaceExisting); + } + + @Override + public void fillBorders(Item item, boolean replaceExisting) { + fill(SlotUtils.getSlotsBorders(width, height), item, replaceExisting); + } + + @Override + public void fillRectangle(int x, int y, int width, int height, Item item, boolean 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); + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/PagedGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/PagedGUI.java new file mode 100644 index 0000000..73629a1 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/PagedGUI.java @@ -0,0 +1,151 @@ +package de.studiocode.invgui.gui.impl; + +import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.item.impl.BaseItem; +import de.studiocode.invgui.item.itembuilder.ItemBuilder; +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.List; + +public abstract class PagedGUI extends BaseGUI { + + private final boolean infinitePages; + private final int[] itemListSlots; + protected int currentPage; + private Item forwardItem; + private Item backItem; + + public PagedGUI(int width, int height, boolean infinitePages, int... itemListSlots) { + super(width, height); + this.infinitePages = infinitePages; + this.itemListSlots = itemListSlots; + } + + public void setControlItems(int backItemSlot, BackItem backItem, int forwardItemSlot, ForwardItem forwardItem) { + setItem(backItemSlot, this.backItem = backItem); + setItem(forwardItemSlot, this.forwardItem = forwardItem); + } + + public void goForward() { + if (hasNextPage()) { + currentPage++; + update(); + } + } + + public boolean hasNextPage() { + return currentPage < getPageAmount() - 1 || infinitePages; + } + + public void goBack() { + if (hasPageBefore()) { + currentPage--; + update(); + } + } + + public boolean hasPageBefore() { + return currentPage > 0; + } + + protected void update() { + updateControlItems(); + updatePageContent(); + } + + private void updateControlItems() { + backItem.notifyWindows(); + forwardItem.notifyWindows(); + } + + private void updatePageContent() { + List slotElements = getPageItems(currentPage); + + for (int i = 0; i < itemListSlots.length; i++) { + if (slotElements.size() > i) setSlotElement(itemListSlots[i], slotElements.get(i)); + else remove(itemListSlots[i]); + } + } + + public int getCurrentPage() { + return currentPage; + } + + public Item getForwardItem() { + return forwardItem; + } + + public Item getBackItem() { + return backItem; + } + + public boolean hasInfinitePages() { + return infinitePages; + } + + public int[] getItemListSlots() { + return itemListSlots; + } + + abstract int getPageAmount(); + + abstract List getPageItems(int page); + + class ForwardItem extends BaseItem { + + private final ItemBuilder itemBuilder; + + public ForwardItem(@NotNull ItemBuilder itemBuilder) { + this.itemBuilder = itemBuilder; + } + + @Override + public ItemBuilder getItemBuilder() { + itemBuilder.clearLore(); + + if (hasNextPage()) + itemBuilder.addLoreLines("§7Go to page §e" + (getCurrentPage() + 1) + + (infinitePages ? "" : "§7/§e" + getPageAmount())); + else itemBuilder.addLoreLines("§7There are no more pages"); + + return itemBuilder; + } + + @Override + public void handleClick(ClickType clickType, Player player, InventoryClickEvent event) { + if (clickType == ClickType.LEFT) goForward(); + } + + } + + class BackItem extends BaseItem { + + private final ItemBuilder itemBuilder; + + public BackItem(@NotNull ItemBuilder itemBuilder) { + this.itemBuilder = itemBuilder; + } + + @Override + public ItemBuilder getItemBuilder() { + itemBuilder.clearLore(); + + if (hasPageBefore()) + itemBuilder.addLoreLines("§7Go to page §e" + getCurrentPage() + "§7/§e" + getPageAmount()); + else itemBuilder.addLoreLines("§7You can't go further back"); + + return itemBuilder; + } + + @Override + public void handleClick(ClickType clickType, Player player, InventoryClickEvent event) { + if (clickType == ClickType.LEFT) goBack(); + } + + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/SimpleGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/SimpleGUI.java new file mode 100644 index 0000000..c9f08bc --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/SimpleGUI.java @@ -0,0 +1,9 @@ +package de.studiocode.invgui.gui.impl; + +public class SimpleGUI extends BaseGUI { + + public SimpleGUI(int width, int height) { + super(width, height); + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java new file mode 100644 index 0000000..7cc628d --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedGUIs.java @@ -0,0 +1,35 @@ +package de.studiocode.invgui.gui.impl; + +import de.studiocode.invgui.gui.GUI; +import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.item.itembuilder.ItemBuilder; + +import java.util.Arrays; +import java.util.List; + +public class SimplePagedGUIs extends PagedGUI { + + private final List guis; + + public SimplePagedGUIs(int width, int height, int backItemSlot, ItemBuilder backBuilder, int forwardItemSlot, + ItemBuilder forwardBuilder, List guis, int... itemListSlots) { + 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(); + } + + @Override + int getPageAmount() { + return guis.size(); + } + + @Override + List getPageItems(int page) { + return Arrays.asList(guis.get(page).getSlotElements()); + } + +} diff --git a/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java new file mode 100644 index 0000000..67cdeae --- /dev/null +++ b/src/main/java/de/studiocode/invgui/gui/impl/SimplePagedItemsGUI.java @@ -0,0 +1,38 @@ +package de.studiocode.invgui.gui.impl; + +import de.studiocode.invgui.gui.SlotElement; +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.item.itembuilder.ItemBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +public class SimplePagedItemsGUI extends PagedGUI { + + private final List items; + + public SimplePagedItemsGUI(int width, int height, int backItemSlot, ItemBuilder backBuilder, int forwardItemSlot, + ItemBuilder forwardBuilder, List items, int... itemListSlots) { + + super(width, height, false, itemListSlots); + this.items = items; + + setControlItems(backItemSlot, new BackItem(backBuilder), forwardItemSlot, new ForwardItem(forwardBuilder)); + update(); + } + + @Override + protected int getPageAmount() { + return (int) Math.ceil((double) items.size() / (double) getItemListSlots().length); + } + + @Override + List getPageItems(int page) { + int length = getItemListSlots().length; + int from = page * length; + int to = Math.min(from + length, items.size()); + + return items.subList(from, to).stream().map(SlotElement::new).collect(Collectors.toList()); + } + +} diff --git a/src/main/java/de/studiocode/invgui/item/Item.java b/src/main/java/de/studiocode/invgui/item/Item.java new file mode 100644 index 0000000..c3a245a --- /dev/null +++ b/src/main/java/de/studiocode/invgui/item/Item.java @@ -0,0 +1,64 @@ +package de.studiocode.invgui.item; + +import de.studiocode.invgui.item.itembuilder.ItemBuilder; +import de.studiocode.invgui.window.Window; +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 java.util.Set; + +public interface Item { + + /** + * Gets the {@link ItemBuilder}. + * This method gets called every time a {@link Window} is notified ({@link #notifyWindows()}). + * + * @return The {@link ItemBuilder} + */ + ItemBuilder getItemBuilder(); + + /** + * Adds a {@link Window} to the window set, telling the {@link Item} that it is + * currently being displayed in that {@link Window}. + * + * @param window The {@link Window} the {@link Item} is currently displayed in. + */ + void addWindow(Window window); + + /** + * Removes an {@link Window} from the window set, telling the {@link Item} that it + * is no longer being displayed in that {@link Window}. + * + * @param window The {@link Window} the {@link Item} is no longer displayed in. + */ + void removeWindow(Window window); + + /** + * Gets all the {@link Window}s the {@link Item} is displayed in. + * + * @return The {@link Set} of {@link Window}s the {@link Item} is displayed in. + */ + Set getWindows(); + + /** + * Calls the {@link Window#handleItemBuilderUpdate(Item)} method on every {@link Window} + * in the set, notifying them that the {@link ItemBuilder} has been updated + * and the {@link ItemStack} inside the {@link Window}'s {@link Inventory} should + * be replaced. + */ + void notifyWindows(); + + /** + * A method called if the the {@link ItemStack} associated to this {@link Item} + * has been clicked by a player. + * + * @param clickType The {@link ClickType} the {@link Player} did. + * @param player The {@link Player} who clicked on the {@link ItemStack} + * @param event The {@link InventoryClickEvent} associated with this click. + */ + void handleClick(ClickType clickType, Player player, InventoryClickEvent event); + +} diff --git a/src/main/java/de/studiocode/invgui/item/impl/BaseItem.java b/src/main/java/de/studiocode/invgui/item/impl/BaseItem.java new file mode 100644 index 0000000..1e9b107 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/item/impl/BaseItem.java @@ -0,0 +1,36 @@ +package de.studiocode.invgui.item.impl; + +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.window.Window; + +import java.util.HashSet; +import java.util.Set; + +/** + * The base for all {@link Item}s. + */ +public abstract class BaseItem implements Item { + + private final Set windows = new HashSet<>(); + + @Override + public void addWindow(Window window) { + windows.add(window); + } + + @Override + public void removeWindow(Window window) { + windows.remove(window); + } + + @Override + public Set getWindows() { + return windows; + } + + @Override + public void notifyWindows() { + windows.forEach(w -> w.handleItemBuilderUpdate(this)); + } + +} diff --git a/src/main/java/de/studiocode/invgui/item/impl/CommandItem.java b/src/main/java/de/studiocode/invgui/item/impl/CommandItem.java new file mode 100644 index 0000000..1b4e09b --- /dev/null +++ b/src/main/java/de/studiocode/invgui/item/impl/CommandItem.java @@ -0,0 +1,25 @@ +package de.studiocode.invgui.item.impl; + +import de.studiocode.invgui.item.itembuilder.ItemBuilder; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; + +/** + * An item that will force a player to run a command or say something in the chat when clicked + */ +public class CommandItem extends SimpleItem { + + private final String command; + + public CommandItem(@NotNull ItemBuilder itemBuilder, @NotNull String command) { + super(itemBuilder); + this.command = command; + } + + @Override + public void handleClick(ClickType clickType, Player player, InventoryClickEvent event) { + player.chat(command); + } +} diff --git a/src/main/java/de/studiocode/invgui/item/impl/SimpleItem.java b/src/main/java/de/studiocode/invgui/item/impl/SimpleItem.java new file mode 100644 index 0000000..2c3a653 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/item/impl/SimpleItem.java @@ -0,0 +1,30 @@ +package de.studiocode.invgui.item.impl; + +import de.studiocode.invgui.item.Item; +import de.studiocode.invgui.item.itembuilder.ItemBuilder; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; + +/** + * A simple {@link Item} that does nothing. + */ +public class SimpleItem extends BaseItem { + + private final ItemBuilder itemBuilder; + + public SimpleItem(@NotNull ItemBuilder itemBuilder) { + this.itemBuilder = itemBuilder; + } + + public ItemBuilder getItemBuilder() { + return itemBuilder; + } + + @Override + public void handleClick(ClickType clickType, Player player, InventoryClickEvent event) { + // empty + } + +} diff --git a/src/main/java/de/studiocode/invgui/item/itembuilder/ItemBuilder.java b/src/main/java/de/studiocode/invgui/item/itembuilder/ItemBuilder.java new file mode 100644 index 0000000..f062b8d --- /dev/null +++ b/src/main/java/de/studiocode/invgui/item/itembuilder/ItemBuilder.java @@ -0,0 +1,212 @@ +package de.studiocode.invgui.item.itembuilder; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import de.studiocode.invgui.util.MojangApiUtils; +import de.studiocode.invgui.util.Pair; +import de.studiocode.invgui.util.ReflectionUtils; +import de.studiocode.invgui.window.impl.BaseWindow; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.*; + +public class ItemBuilder { + + protected Material material; + protected int amount = 1; + protected int damage; + protected int customModelData = -1; + protected String displayName; + protected List lore = new ArrayList<>(); + protected List itemFlags = new ArrayList<>(); + protected HashMap> enchantments = new HashMap<>(); + protected GameProfile gameProfile; + + public ItemBuilder(Material material) { + this.material = material; + } + + public ItemBuilder(@NotNull PlayerHead playerHead) { + material = Material.PLAYER_HEAD; + gameProfile = new GameProfile(UUID.randomUUID(), null); + PropertyMap propertyMap = gameProfile.getProperties(); + propertyMap.put("textures", new Property("textures", playerHead.getTextureValue())); + } + + /** + * Builds the {@link ItemStack} + * + * @return The {@link ItemStack} + */ + public ItemStack build() { + ItemStack itemStack = new ItemStack(material, amount); + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta instanceof Damageable) ((Damageable) itemMeta).setDamage(damage); + if (customModelData != -1) itemMeta.setCustomModelData(customModelData); + if (displayName != null) itemMeta.setDisplayName(displayName); + if (gameProfile != null) ReflectionUtils.setValueOfDeclaredField(itemMeta, "profile", gameProfile); + enchantments.forEach((enchantment, pair) -> itemMeta.addEnchant(enchantment, pair.getFirst(), pair.getSecond())); + itemMeta.addItemFlags(itemFlags.toArray(new ItemFlag[0])); + itemMeta.setLore(lore); + + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + /** + * Builds the {@link ItemStack} for a specific player. + * This is the method called by {@link BaseWindow} which gives you + * the option to (for example) create a subclass of {@link ItemBuilder} that automatically + * translates the item's name into the player's language. + * + * @param playerUUID The {@link UUID} of the {@link Player} + * for who this {@link ItemStack} should be built. + * @return The {@link ItemStack} + */ + public ItemStack buildFor(@NotNull UUID playerUUID) { + return build(); + } + + public ItemBuilder setMaterial(Material material) { + this.material = material; + return this; + } + + public ItemBuilder setAmount(int amount) { + this.amount = amount; + return this; + } + + public ItemBuilder setDamage(int damage) { + this.damage = damage; + return this; + } + + public ItemBuilder setCustomModelData(int customModelData) { + this.customModelData = customModelData; + return this; + } + + public ItemBuilder setDisplayName(String displayName) { + this.displayName = displayName; + return this; + } + + public ItemBuilder setLore(List lore) { + this.lore = lore; + return this; + } + + public ItemBuilder addLoreLines(String... lines) { + lore.addAll(Arrays.asList(lines)); + return this; + } + + public ItemBuilder removeLoreLines(String... lines) { + lore.removeAll(Arrays.asList(lines)); + return this; + } + + public ItemBuilder removeLoreLine(int index) { + lore.remove(index); + return this; + } + + public ItemBuilder clearLore() { + lore.clear(); + return this; + } + + public ItemBuilder setItemFlags(List itemFlags) { + this.itemFlags = itemFlags; + return this; + } + + public ItemBuilder addItemFlags(ItemFlag... itemFlags) { + this.itemFlags.addAll(Arrays.asList(itemFlags)); + return this; + } + + public ItemBuilder removeItemFlags(ItemFlag... itemFlags) { + this.itemFlags.removeAll(Arrays.asList(itemFlags)); + return this; + } + + public ItemBuilder clearItemFlags() { + itemFlags.clear(); + return this; + } + + public ItemBuilder setEnchantments(HashMap> enchantments) { + this.enchantments = enchantments; + return this; + } + + public ItemBuilder addEnchantment(Enchantment enchantment, int level, boolean ignoreLevelRestriction) { + enchantments.put(enchantment, new Pair<>(level, ignoreLevelRestriction)); + return this; + } + + public ItemBuilder removeEnchantment(Enchantment enchantment) { + enchantments.remove(enchantment); + return this; + } + + public ItemBuilder clearEnchantments() { + enchantments.clear(); + return this; + } + + public static class PlayerHead { + + private final String textureValue; + + private PlayerHead(@NotNull String textureValue) { + this.textureValue = textureValue; + } + + public static PlayerHead of(@NotNull Player player) { + return of(player.getUniqueId()); + } + + public static PlayerHead of(@NotNull String playerName) { + try { + return of(MojangApiUtils.getCurrentUUID(playerName)); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + public static PlayerHead of(@NotNull UUID uuid) { + try { + return new PlayerHead(MojangApiUtils.getSkinData(uuid, false)[0]); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + public static PlayerHead fromTextureValue(@NotNull String textureValue) { + return new PlayerHead(textureValue); + } + + public String getTextureValue() { + return textureValue; + } + + } + +} diff --git a/src/main/java/de/studiocode/invgui/util/ArrayUtils.java b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java new file mode 100644 index 0000000..e41b1db --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/ArrayUtils.java @@ -0,0 +1,29 @@ +package de.studiocode.invgui.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class ArrayUtils { + + public static int findFirstEmptyIndex(@NotNull Object[] array) { + for (int index = 0; index < array.length; index++) { + if (array[index] == null) return index; + } + + return -1; + } + + public static List findAllOccurrences(@NotNull Object[] array, @NotNull Object toFind) { + List occurrences = new ArrayList<>(); + + for (int index = 0; index < array.length; index++) { + Object obj = array[index]; + if (obj != null && obj.equals(toFind)) occurrences.add(index); + } + + return occurrences; + } + +} diff --git a/src/main/java/de/studiocode/invgui/util/MojangApiUtils.java b/src/main/java/de/studiocode/invgui/util/MojangApiUtils.java new file mode 100644 index 0000000..266da9f --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/MojangApiUtils.java @@ -0,0 +1,70 @@ +package de.studiocode.invgui.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.util.UUIDTypeAdapter; + +import java.io.IOException; +import java.net.URL; +import java.util.UUID; + +public class MojangApiUtils { + + private static final String SKIN_DATA_URL = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=%s"; + private static final String NAME_AT_TIME_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%s"; + + public static String[] getSkinData(UUID uuid, boolean requestSignature) throws IOException { + String url = String.format(SKIN_DATA_URL, uuid, requestSignature); + String content = WebUtils.readWebsiteContent(new URL(url)); + + JsonObject jsonObject = new JsonParser().parse(content).getAsJsonObject(); + + checkForError(jsonObject); + if (jsonObject.has("properties")) { + JsonElement properties = jsonObject.get("properties"); + JsonObject property = properties.getAsJsonArray().get(0).getAsJsonObject(); + String value = property.get("value").getAsString(); + String signature = requestSignature ? property.get("signature").getAsString() : ""; + return new String[] {value, signature}; + } + + return null; + } + + public static UUID getCurrentUUID(String name) throws IOException { + return getUuidAtTime(name, System.currentTimeMillis() / 1000); + } + + public static UUID getUuidAtTime(String name, long timestamp) throws IOException { + String url = String.format(NAME_AT_TIME_URL, name, timestamp); + String content = WebUtils.readWebsiteContent(new URL(url)); + + JsonObject jsonObject = new JsonParser().parse(content).getAsJsonObject(); + + checkForError(jsonObject); + if (jsonObject.has("id")) { + String id = jsonObject.get("id").getAsString(); + return UUIDTypeAdapter.fromString(id); + } + + return null; + } + + private static void checkForError(JsonObject jsonObject) throws MojangApiException { + if (jsonObject.has("error") && jsonObject.has("errorMessage")) { + if (jsonObject.has("errorMessage")) + throw new MojangApiException(jsonObject.get("errorMessage").getAsString()); + else throw new MojangApiException(""); + } + } + + public static class MojangApiException extends IOException { + + public MojangApiException(String message) { + super(message); + } + + } + +} diff --git a/src/main/java/de/studiocode/invgui/util/Pair.java b/src/main/java/de/studiocode/invgui/util/Pair.java new file mode 100644 index 0000000..5467c08 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/Pair.java @@ -0,0 +1,28 @@ +package de.studiocode.invgui.util; + +public class Pair { + + private A first; + private B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return first; + } + + public void setFirst(A first) { + this.first = first; + } + + public B getSecond() { + return second; + } + + public void setSecond(B second) { + this.second = second; + } +} diff --git a/src/main/java/de/studiocode/invgui/util/ReflectionUtils.java b/src/main/java/de/studiocode/invgui/util/ReflectionUtils.java new file mode 100644 index 0000000..51cad2a --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/ReflectionUtils.java @@ -0,0 +1,20 @@ +package de.studiocode.invgui.util; + +import java.lang.reflect.Field; + +public class ReflectionUtils { + + public static void setValueOfDeclaredField(Object obj, String fieldName, Object value) { + try { + Class clazz = obj.getClass(); + Field field = clazz.getDeclaredField(fieldName); + boolean accessible = field.isAccessible(); + field.setAccessible(true); + field.set(obj, value); + field.setAccessible(accessible); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/de/studiocode/invgui/util/SlotUtils.java b/src/main/java/de/studiocode/invgui/util/SlotUtils.java new file mode 100644 index 0000000..ee5d077 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/SlotUtils.java @@ -0,0 +1,44 @@ +package de.studiocode.invgui.util; + +import java.util.SortedSet; +import java.util.TreeSet; + +public class SlotUtils { + + public static SortedSet getSlotsRow(int row, int width) { + SortedSet slots = new TreeSet<>(); + for (int x = 0; x < width; x++) slots.add(convertToIndex(x, row, width)); + return slots; + } + + public static SortedSet getSlotsColumn(int column, int width, int height) { + SortedSet slots = new TreeSet<>(); + for (int y = 0; y < height; y++) slots.add(convertToIndex(column, y, width)); + return slots; + } + + public static SortedSet getSlotsBorders(int width, int height) { + SortedSet slots = new TreeSet<>(); + if (height > 0) slots.addAll(getSlotsRow(0, width)); + if (height - 1 > 0) slots.addAll(getSlotsRow(height - 1, width)); + if (width > 0) slots.addAll(getSlotsColumn(0, width, height)); + if (width - 1 > 0) slots.addAll(getSlotsColumn(width - 1, width, height)); + return slots; + } + + public static SortedSet getSlotsRect(int x, int y, int width, int height, int frameWidth) { + SortedSet slots = new TreeSet<>(); + for (int y1 = y; y1 <= height; y1++) { + for (int x1 = x; x1 <= width; x1++) { + slots.add(convertToIndex(x1, y1, frameWidth)); + } + } + + return slots; + } + + public static int convertToIndex(int x, int y, int width) { + return y * width + x; + } + +} diff --git a/src/main/java/de/studiocode/invgui/util/WebUtils.java b/src/main/java/de/studiocode/invgui/util/WebUtils.java new file mode 100644 index 0000000..b95112c --- /dev/null +++ b/src/main/java/de/studiocode/invgui/util/WebUtils.java @@ -0,0 +1,23 @@ +package de.studiocode.invgui.util; + +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +public class WebUtils { + + public static String readWebsiteContent(@NotNull URL url) throws IOException { + StringBuilder content = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + + return content.substring(0, content.length() - 1); + } + +} diff --git a/src/main/java/de/studiocode/invgui/window/Window.java b/src/main/java/de/studiocode/invgui/window/Window.java new file mode 100644 index 0000000..2284d9c --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/Window.java @@ -0,0 +1,134 @@ +package de.studiocode.invgui.window; + +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 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.UUID; + +/** + * A window is the way to show a player a GUI. + * Windows can only have one viewer at a time. + */ +public interface Window { + + /** + * Gets the underlying {@link Inventory}. + * + * @return The underlying {@link Inventory}. + */ + Inventory getInventory(); + + /** + * Gets the underlying {@link GUI}. + * + * @return The underlying {@link GUI}. + */ + GUI getGui(); + + /** + * A method called every tick by the {@link WindowManager} + * to re-draw the {@link Item}s. + */ + void handleTick(); + + /** + * A method called by the {@link WindowManager} to notify the Window + * that one of its {@link Item}s has been clicked. + * + * @param event The {@link InventoryClickEvent} associated with this action. + */ + void handleClick(InventoryClickEvent event); + + /** + * A method called by the {@link WindowManager} to notify the Window + * that its underlying {@link Inventory} is being opened. + * + * @param event The {@link InventoryOpenEvent} associated with this action. + */ + void handleOpen(InventoryOpenEvent event); + + /** + * A method called by the {@link WindowManager} to notify the Window + * that its underlying {@link Inventory} is being closed. + * + * @param player The {@link Player} who closed this inventory. + */ + void handleClose(Player player); + + /** + * A method called by the {@link Item} itself to notify the Window + * that its {@link ItemBuilder} has been updated and the {@link ItemStack} + * in the {@link Inventory} should be replaced. + * + * @param item The {@link Item} whose {@link ItemBuilder} has been updated. + */ + void handleItemBuilderUpdate(Item item); + + /** + * Removes the {@link Window} from the {@link WindowManager} list. + * If this method is called, the {@link Window} can't be shown again. + * + * @param closeForViewer If the underlying {@link Inventory} should be closed for the viewer. + */ + void close(boolean closeForViewer); + + /** + * Gets if the {@link Window} is closed and can't be shown again. + * + * @return If the {@link Window} is closed. + */ + boolean isClosed(); + + /** + * Closes the underlying {@link Inventory} for its viewer. + */ + void closeForViewer(); + + /** + * Shows the window to a the player. + */ + void show(); + + /** + * Plays an animation. + * + * @param animation The animation to play. + */ + void playAnimation(Animation animation); + + /** + * Gets if the player is able to close the {@link Inventory}. + * + * @return If the player is able to close the {@link Inventory}. + */ + boolean isCloseable(); + + /** + * Sets if the player should be able to close the {@link Inventory}. + * + * @param closeable If the player should be able to close the {@link Inventory}. + */ + void setCloseable(boolean closeable); + + /** + * Gets the viewer of this {@link Window} + * + * @return The viewer of this window, can be null. + */ + Player getViewer(); + + /** + * Gets the viewer's UUID or null if there is no viewer. + * + * @return The viewer's UUID or null if there is now viewer. + */ + UUID getViewerUUID(); + +} diff --git a/src/main/java/de/studiocode/invgui/window/WindowManager.java b/src/main/java/de/studiocode/invgui/window/WindowManager.java new file mode 100644 index 0000000..fd7e890 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/WindowManager.java @@ -0,0 +1,100 @@ +package de.studiocode.invgui.window; + +import de.studiocode.invgui.InvGui; +import org.bukkit.Bukkit; +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.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +public class WindowManager implements Listener { + + private static WindowManager instance; + + private final List windows = new CopyOnWriteArrayList<>(); + + private WindowManager() { + Bukkit.getPluginManager().registerEvents(this, InvGui.getInstance().getPlugin()); + Bukkit.getScheduler().scheduleSyncRepeatingTask(InvGui.getInstance().getPlugin(), + () -> windows.forEach(Window::handleTick), 0, 1); + } + + public static WindowManager getInstance() { + return instance == null ? instance = new WindowManager() : instance; + } + + public static boolean hasInstance() { + return instance != null; + } + + public void addWindow(Window window) { + windows.add(window); + } + + public void removeWindow(Window window) { + windows.remove(window); + } + + /** + * Find a window to an inventory + * + * @param inventory The inventory + * @return The window that belongs to that inventory + */ + public Optional findWindow(Inventory inventory) { + return windows.stream() + .filter(w -> w.getInventory().equals(inventory)) + .findFirst(); + } + + /** + * Find a window to a player + * + * @param player The player + * @return The window that the player has open + */ + public Optional findWindow(Player player) { + return windows.stream() + .filter(w -> w.getInventory().getViewers().stream().anyMatch(player::equals)) + .findFirst(); + } + + /** + * Get a list of all currently active windows + * + * @return A list of all windows + */ + public List getWindows() { + return windows; + } + + @EventHandler + public void handleInventoryClick(InventoryClickEvent event) { + findWindow(event.getClickedInventory()).ifPresent(window -> window.handleClick(event)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void handleInventoryClose(InventoryCloseEvent event) { + findWindow(event.getInventory()).ifPresent(window -> window.handleClose((Player) event.getPlayer())); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void handleInventoryOpen(InventoryOpenEvent event) { + findWindow(event.getInventory()).ifPresent(window -> window.handleOpen(event)); + } + + @EventHandler + public void handlePlayerQuit(PlayerQuitEvent event) { + findWindow(event.getPlayer().getOpenInventory().getTopInventory()).ifPresent(window -> window.handleClose(event.getPlayer())); + } + +} diff --git a/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java b/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java new file mode 100644 index 0000000..f6dfdcb --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/impl/BaseWindow.java @@ -0,0 +1,211 @@ +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.item.Item; +import de.studiocode.invgui.util.ArrayUtils; +import de.studiocode.invgui.window.Window; +import de.studiocode.invgui.window.WindowManager; +import org.bukkit.Bukkit; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public abstract class BaseWindow implements Window { + + private final GUI gui; + private final int size; + private final UUID viewerUUID; + private final Inventory inventory; + private final boolean closeOnEvent; + private final Item[] itemsDisplayed; + + private Animation animation; + private boolean closeable; + private boolean closed; + + public BaseWindow(UUID viewerUUID, GUI gui, Inventory inventory, boolean closeable, boolean closeOnEvent) { + this.gui = gui; + this.size = gui.getSize(); + this.viewerUUID = viewerUUID; + this.inventory = inventory; + this.closeable = closeable; + this.closeOnEvent = closeOnEvent; + this.itemsDisplayed = new Item[size]; + + initItems(); + WindowManager.getInstance().addWindow(this); + } + + private void initItems() { + for (int i = 0; i < size; i++) { + Item item = gui.getItem(i); + if (item != null) redrawItem(i, item, true); + } + } + + private void redrawItem(int index, Item item, boolean setItem) { + inventory.setItem(index, item == null ? null : item.getItemBuilder().buildFor(viewerUUID)); + 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); + + itemsDisplayed[index] = item; + } + } + + @Override + public void handleTick() { + for (int i = 0; i < size; i++) { + Item item = gui.getItem(i); + if (itemsDisplayed[i] != item) redrawItem(i, item, 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); + } + } + + @Override + public void handleOpen(InventoryOpenEvent event) { + if (!event.getPlayer().equals(getViewer())) + event.setCancelled(true); + } + + @Override + public void handleClose(Player player) { + if (closeable) { + stopAnimation(); + if (closeOnEvent) close(false); + } else { + if (player.equals(getViewer())) + Bukkit.getScheduler().runTaskLater(InvGui.getInstance().getPlugin(), + () -> player.openInventory(inventory), 0); + } + } + + @Override + public void handleItemBuilderUpdate(Item item) { + for (int i : ArrayUtils.findAllOccurrences(itemsDisplayed, item)) { + redrawItem(i, item, false); + } + } + + @Override + public void close(boolean closeForViewer) { + closed = true; + + WindowManager.getInstance().removeWindow(this); + + Arrays.stream(itemsDisplayed) + .filter(Objects::nonNull) + .forEach(item -> item.removeWindow(this)); + + if (closeForViewer) closeForViewer(); + } + + @Override + public void closeForViewer() { + closeable = true; + new ArrayList<>(inventory.getViewers()).forEach(HumanEntity::closeInventory); // clone list to prevent ConcurrentModificationException + } + + @Override + public void show() { + if (closed) throw new IllegalStateException("The Window has already been closed."); + if (inventory.getViewers().size() != 0) throw new IllegalStateException("A Window can only have one viewer."); + + Player viewer = getViewer(); + if (viewer == null) throw new IllegalStateException("The player is not online."); + viewer.openInventory(inventory); + } + + @Override + public void playAnimation(Animation animation) { + if (getViewer() != null) { + this.animation = animation; + + animation.setBounds(getGui().getWidth(), getGui().getHeight()); + animation.setPlayer(getViewer()); + animation.addShowHandler((frame, index) -> redrawItem(index, itemsDisplayed[index], false)); + animation.setFinishHandler(() -> this.animation = null); + animation.setSlots(IntStream.range(0, size) + .filter(i -> itemsDisplayed[i] != null) + .boxed() + .collect(Collectors.toCollection(ArrayList::new))); + + clearItemStacks(); + animation.start(); + } + } + + private void stopAnimation() { + if (this.animation != null) { + // cancel the scheduler task and set animation to null + animation.cancel(); + animation = null; + + // show all items again + for (int i = 0; i < gui.getSize(); i++) + redrawItem(i, itemsDisplayed[i], false); + } + } + + private void clearItemStacks() { + for (int i = 0; i < inventory.getSize(); i++) inventory.setItem(i, null); + } + + @Override + public Player getViewer() { + return Bukkit.getPlayer(viewerUUID); + } + + @Override + public UUID getViewerUUID() { + return viewerUUID; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + @Override + public GUI getGui() { + return gui; + } + + @Override + public boolean isCloseable() { + return closeable; + } + + @Override + public void setCloseable(boolean closeable) { + this.closeable = closeable; + } + + @Override + public boolean isClosed() { + return closed; + } + +} \ No newline at end of file diff --git a/src/main/java/de/studiocode/invgui/window/impl/DropperWindow.java b/src/main/java/de/studiocode/invgui/window/impl/DropperWindow.java new file mode 100644 index 0000000..b2dbcd2 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/impl/DropperWindow.java @@ -0,0 +1,27 @@ +package de.studiocode.invgui.window.impl; + +import de.studiocode.invgui.gui.GUI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; + +import java.util.UUID; + +public class DropperWindow extends BaseWindow { + + public DropperWindow(UUID viewerUUID, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + super(viewerUUID, gui, createInventory(gui, title), closeable, closeOnEvent); + } + + public DropperWindow(Player player, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + this(player.getUniqueId(), gui, title, closeable, closeOnEvent); + } + + private static Inventory createInventory(GUI gui, String title) { + if (gui.getWidth() != 3 || gui.getHeight() != 3) + throw new IllegalArgumentException("GUI width and height have to be 3."); + return Bukkit.createInventory(null, InventoryType.DROPPER, title); + } + +} diff --git a/src/main/java/de/studiocode/invgui/window/impl/HopperWindow.java b/src/main/java/de/studiocode/invgui/window/impl/HopperWindow.java new file mode 100644 index 0000000..a3a693e --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/impl/HopperWindow.java @@ -0,0 +1,27 @@ +package de.studiocode.invgui.window.impl; + +import de.studiocode.invgui.gui.GUI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; + +import java.util.UUID; + +public class HopperWindow extends BaseWindow { + + public HopperWindow(UUID viewerUUID, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + super(viewerUUID, gui, createInventory(gui, title), closeable, closeOnEvent); + } + + public HopperWindow(Player player, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + this(player.getUniqueId(), gui, title, closeable, closeOnEvent); + } + + private static Inventory createInventory(GUI gui, String title) { + if (gui.getWidth() != 5 || gui.getHeight() != 1) + throw new IllegalArgumentException("GUI width has to be 5, height 1."); + return Bukkit.createInventory(null, InventoryType.HOPPER, title); + } + +} diff --git a/src/main/java/de/studiocode/invgui/window/impl/NormalInventoryWindow.java b/src/main/java/de/studiocode/invgui/window/impl/NormalInventoryWindow.java new file mode 100644 index 0000000..e3cf697 --- /dev/null +++ b/src/main/java/de/studiocode/invgui/window/impl/NormalInventoryWindow.java @@ -0,0 +1,25 @@ +package de.studiocode.invgui.window.impl; + +import de.studiocode.invgui.gui.GUI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import java.util.UUID; + +public final class NormalInventoryWindow extends BaseWindow { + + public NormalInventoryWindow(UUID viewerUUID, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + super(viewerUUID, gui, createInventory(gui, title), closeable, closeOnEvent); + } + + public NormalInventoryWindow(Player player, GUI gui, String title, boolean closeable, boolean closeOnEvent) { + this(player.getUniqueId(), gui, title, closeable, closeOnEvent); + } + + private static Inventory createInventory(GUI gui, String title) { + if (gui.getWidth() != 9) throw new IllegalArgumentException("GUI width has to be 9."); + return Bukkit.createInventory(null, gui.getSize(), title); + } + +}