Initial commit

This commit is contained in:
NichtStudioCode 2021-01-20 22:27:13 +01:00
commit 1d08e06d5c
38 changed files with 2459 additions and 0 deletions

2
.gitattributes vendored Normal file

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

60
.gitignore vendored Normal file

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

62
pom.xml Normal file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.studiocode</groupId>
<artifactId>InvGUI</artifactId>
<version>0.1-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.16.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>1.5.21</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations-java5</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

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

@ -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<Integer> slots);
void addShowHandler(@NotNull BiConsumer<Integer, Integer> show);
void setFinishHandler(@NotNull Runnable finish);
void start();
void cancel();
}

@ -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<Integer> slots;
private BiConsumer<Integer, Integer> 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<Integer, Integer> 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<Integer> getSlots() {
return slots;
}
@Override
public void setSlots(List<Integer> 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;
}
}

@ -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<Integer> slots = getSlots();
if (!slots.isEmpty()) {
show(slots.get(0));
slots.remove(0);
} else finished();
}
}

@ -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<Integer> slots = getSlots();
if (!slots.isEmpty()) {
int slot = slots.get(random.nextInt(slots.size()));
slots.remove(Integer.valueOf(slot));
show(slot);
} else finished();
}
}

@ -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.<br>
* Each {@link SlotElement} can either be an {@link Item} or a
* reference to another {@link GUI}s slot index.</br>
* A {@link GUI} is not an {@link Inventory}, nor does
* it access one. It just contains {@link SlotElement}s and their positions.<br>
* 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
}

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

@ -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.<br>
* 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();
}

@ -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<Character, Ingredient> 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<Integer> findIndicesOf(int special) {
List<Integer> 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;
}
}
}

@ -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<Integer> indices = findIndicesOf(0);
if (indices.isEmpty())
throw new IllegalStateException("BackItem index is not set");
return indices.get(0);
}
protected int getForwardItemIndex() {
List<Integer> 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;
}
}

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

@ -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<GUI> 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<GUI> guis) {
this.guis = guis;
}
}

@ -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<Item> 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<Item> items) {
this.items = items;
}
}

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

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

@ -0,0 +1,9 @@
package de.studiocode.invgui.gui.impl;
public class SimpleGUI extends BaseGUI {
public SimpleGUI(int width, int height) {
super(width, height);
}
}

@ -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<GUI> guis;
public SimplePagedGUIs(int width, int height, int backItemSlot, ItemBuilder backBuilder, int forwardItemSlot,
ItemBuilder forwardBuilder, List<GUI> 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<SlotElement> getPageItems(int page) {
return Arrays.asList(guis.get(page).getSlotElements());
}
}

@ -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<Item> items;
public SimplePagedItemsGUI(int width, int height, int backItemSlot, ItemBuilder backBuilder, int forwardItemSlot,
ItemBuilder forwardBuilder, List<Item> 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<SlotElement> 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());
}
}

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

@ -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<Window> windows = new HashSet<>();
@Override
public void addWindow(Window window) {
windows.add(window);
}
@Override
public void removeWindow(Window window) {
windows.remove(window);
}
@Override
public Set<Window> getWindows() {
return windows;
}
@Override
public void notifyWindows() {
windows.forEach(w -> w.handleItemBuilderUpdate(this));
}
}

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

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

@ -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<String> lore = new ArrayList<>();
protected List<ItemFlag> itemFlags = new ArrayList<>();
protected HashMap<Enchantment, Pair<Integer, Boolean>> 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<String> 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<ItemFlag> 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<Enchantment, Pair<Integer, Boolean>> 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;
}
}
}

@ -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<Integer> findAllOccurrences(@NotNull Object[] array, @NotNull Object toFind) {
List<Integer> 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;
}
}

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

@ -0,0 +1,28 @@
package de.studiocode.invgui.util;
public class Pair<A, B> {
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;
}
}

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

@ -0,0 +1,44 @@
package de.studiocode.invgui.util;
import java.util.SortedSet;
import java.util.TreeSet;
public class SlotUtils {
public static SortedSet<Integer> getSlotsRow(int row, int width) {
SortedSet<Integer> slots = new TreeSet<>();
for (int x = 0; x < width; x++) slots.add(convertToIndex(x, row, width));
return slots;
}
public static SortedSet<Integer> getSlotsColumn(int column, int width, int height) {
SortedSet<Integer> slots = new TreeSet<>();
for (int y = 0; y < height; y++) slots.add(convertToIndex(column, y, width));
return slots;
}
public static SortedSet<Integer> getSlotsBorders(int width, int height) {
SortedSet<Integer> 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<Integer> getSlotsRect(int x, int y, int width, int height, int frameWidth) {
SortedSet<Integer> 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;
}
}

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

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

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

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

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

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

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