package com.songoda.epicenchants.utils.objects; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A fast API to easily create advanced GUI. * The project is on GitHub * * @author MrMicky * @version 2.0.3 - Now supports async operations */ public class FastInv implements InventoryHolder { private static Plugin plugin = null; private boolean cancelTasksOnClose = true; private final Set clickListeners = new HashSet<>(); private final Set closeListeners = new HashSet<>(); private Inventory inventory; private final Map itemListeners = new HashMap<>(); private final Set tasks = new HashSet<>(); /** * Create a new FastInv with a custom size and title. * * @param size The size of the menus. * @param title The title (name) of the menus. */ public FastInv(int size, String title) { this(size, InventoryType.CHEST, title); } private FastInv(int size, InventoryType type, String title) { if (plugin == null) { throw new IllegalStateException("FastInv is not initialised"); } runSync(() -> { if (type == InventoryType.CHEST && size > 0) { inventory = Bukkit.createInventory(this, size, title); } else { inventory = Bukkit.createInventory(this, type, title); } }); } /** * Register your FastInv instance. * * @param plugin The plugin that uses FastInv. */ public static void init(Plugin plugin) { if (FastInv.plugin == null) { FastInv.plugin = plugin; Bukkit.getPluginManager().registerEvents(getListener(), plugin); } } public static class FastInvClickEvent extends FastInvEvent { private final InventoryClickEvent event; private final InventoryAction action; private final ClickType clickType; private final ItemStack item; private final int slot; private FastInvClickEvent(Player player, FastInv inventory, InventoryClickEvent event, int slot, ItemStack item, boolean cancelled, InventoryAction action, ClickType clickType) { super(player, inventory, cancelled); this.event = event; this.slot = slot; this.item = item; this.action = action; this.clickType = clickType; } /** * @return The action of the event */ public InventoryAction getAction() { return this.action; } /** * @return The click type */ public ClickType getClickType() { return this.clickType; } /** * Get the clicked {@link ItemStack} * * @return The clicked item */ public ItemStack getItem() { return this.item; } /** * Get the number of the clicked slot * * @return The slot number */ public int getSlot() { return this.slot; } public InventoryClickEvent getEvent() { return event; } } /** * Add an {@link ItemStack} to the menus. * * @param item The item to add * * @return This FastInv instance, for chaining. */ public FastInv addItem(ItemStack item) { return addItem(item, null); } /** * Add an {@link ItemStack} to the menus with a {@link FastInvClickListener} to handle clicks. * * @param item The item to add. * @param listener The {@link FastInvClickListener} for the item. * * @return This FastInv instance, for chaining. */ public FastInv addItem(ItemStack item, FastInvClickListener listener) { runSync(() -> { int slot = inventory.firstEmpty(); if (slot >= 0) { addItem(slot, item, listener); } }); return this; } /** * Add an {@link ItemStack} to the menus on a specific slot. * * @param slot The slot of the item. * @param item The item to add. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int slot, ItemStack item) { return addItem(slot, item, null); } /** * Add an {@link ItemStack} to the menus on specific slot with a {@link FastInvClickListener} to handle clicks. * * @param slot The slot of the item. * @param item The item to add. * @param listener The FastInvClickListener for the item. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int slot, ItemStack item, FastInvClickListener listener) { runSync(() -> { inventory.setItem(slot, item); if (listener != null) { itemListeners.put(slot, listener); } else { itemListeners.remove(slot); } }); return this; } /** * Add an {@link ItemStack} to the menus on a range of slots. * * @param slotFrom Starting slot to put the item in. * @param slotTo Ending slot to put the item in. * @param item The item to add. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int slotFrom, int slotTo, ItemStack item) { return addItem(slotFrom, slotTo, item, null); } /** * Add an {@link ItemStack} to the menus on a range of slots with a {@link FastInvClickListener} to handle clicks. * * @param slotFrom Starting slot to put the item in. * @param slotTo Ending slot to put the item in. * @param item The item to add. * @param listener The FastInvClickListener for the item. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int slotFrom, int slotTo, ItemStack item, FastInvClickListener listener) { for (int i = slotFrom; i <= slotTo; i++) { addItem(i, item, listener); } return this; } /** * Add an {@link ItemStack} to the menus on multiple slots. * * @param slots The slot of the item. * @param item The item to add. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int[] slots, ItemStack item) { return addItem(slots, item, null); } /** * Clear a spot in the inventory. * * @param slot The slot to clear. * * @return This FastInv instance, for chaining. */ public FastInv clear(int slot) { return addItem(slot, null); } /** * Add an {@link ItemStack} to the menus on the edges. * * @param item The item to add. * * @return This FastInv instance, for chaining. */ public FastInv edge(ItemStack item) { int height = inventory.getSize() / 9; addItem(0, 9, item); addItem(inventory.getSize() - 9, inventory.getSize() - 1, item); for (int i = 0; i < height; i++) { addItem(i * 9, item); addItem(i * 9 + 8, item); } return this; } /** * Add an {@link ItemStack} to the menus on multiples slots with a {@link FastInvClickListener} to handle click. * * @param slots The slots to place the item. * @param item The item to add. * @param listener The FastInvClickListener for the item. * * @return This FastInv instance, for chaining. */ public FastInv addItem(int[] slots, ItemStack item, FastInvClickListener listener) { for (int slot : slots) { addItem(slot, item, listener); } return this; } public FastInv fill(ItemStack itemStack) { runSync(() -> { for (int i = 0; i < inventory.getSize(); i++) { if (inventory.getItem(i) == null) { addItem(i, itemStack); } } }); return this; } /** * Add a {@link FastInvCloseListener} to listen on menus close. * * @param listener The {@link FastInvCloseListener} to add. * * @return This FastInv instance, for chaining. */ public FastInv onClose(FastInvCloseListener listener) { closeListeners.add(listener); return this; } /** * Add a {@link FastInvClickListener} to listen on menus click. * * @param listener The {@link FastInvClickListener} to add. * * @return This FastInv instance, for chaining. */ public FastInv onClick(FastInvClickListener listener) { clickListeners.add(listener); return this; } /** * Schedule a task to run. * * @param period Delay between each run. * @param runnable The {@link Runnable} task to run. * * @return This FastInv instance, for chaining. */ public FastInv onUpdate(long period, Runnable runnable) { return onUpdate(period, period, runnable); } /** * Schedule a task to run with a delay before starting. * * @param delay Ticks to wait before starting the task. * @param period Delay between each run. * @param runnable The {@link Runnable} task to run. * * @return This FastInv instance, for chaining */ public FastInv onUpdate(long delay, long period, Runnable runnable) { tasks.add(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period)); return this; } /** * Open the menus to a player. * * @param player The player to open the menu. */ public void open(Player player) { Bukkit.getScheduler().runTask(plugin, () -> player.openInventory(inventory)); } /** * Cancel all tasks. */ public void cancelTasks() { tasks.forEach(BukkitTask::cancel); tasks.clear(); } /** * Run a task on the server primary thread. * * @param runnable The runnable to run on the main thread */ public void runSync(Runnable runnable) { if (Bukkit.isPrimaryThread()) { runnable.run(); } else { Bukkit.getScheduler().runTask(plugin, runnable); } } /** * Get the Bukkit menus associated with this FastInv instance. * * @return The Bukkit {@link Inventory}. */ @Override public Inventory getInventory() { return inventory; } /** * Set if the tasks will be cancel on menus close. * * @param cancelTasksOnClose Set if the tasks will be cancel * * @return This FastInv instance, for chaining. */ public FastInv setCancelTasksOnClose(boolean cancelTasksOnClose) { this.cancelTasksOnClose = cancelTasksOnClose; return this; } public interface FastInvClickListener { void onClick(FastInvClickEvent event); } public interface FastInvCloseListener { void onClose(FastInvCloseEvent event); } public static abstract class FastInvEvent { private boolean cancelled; private final FastInv inventory; private final Player player; FastInvEvent(Player player, FastInv inventory, boolean cancelled) { this.player = player; this.inventory = inventory; this.cancelled = cancelled; } /** * Get the FastInv menus. * * @return This associated FastInv instance. */ public FastInv getInventory() { return inventory; } /** * Get the {@link Player} who clicked. * * @return the player who clicked. */ public Player getPlayer() { return player; } /** * Get if the event is cancelled or not. * * @return Whether the event was cancelled. */ public boolean isCancelled() { return cancelled; } /** * Set if the event will be cancel or not. * * @param cancel Whether the event should be cancelled. */ public void setCancelled(boolean cancel) { this.cancelled = cancel; } } private static Listener getListener() { return new Listener() { @EventHandler public void onClick(InventoryClickEvent event) { if (event.getInventory().getHolder() instanceof FastInv && event.getWhoClicked() instanceof Player) { int slot = event.getRawSlot(); FastInv inv = (FastInv) event.getInventory().getHolder(); FastInvClickEvent clickEvent = new FastInvClickEvent((Player) event.getWhoClicked(), inv, event, slot, event.getCurrentItem(), true, event.getAction(), event.getClick()); if (inv.itemListeners.containsKey(slot)) { inv.itemListeners.get(slot).onClick(clickEvent); } inv.clickListeners.forEach(listener -> listener.onClick(clickEvent)); if (clickEvent.isCancelled()) { event.setCancelled(true); } } } @EventHandler public void onClose(InventoryCloseEvent event) { if (event.getInventory().getHolder() instanceof FastInv && event.getPlayer() instanceof Player) { Player player = (Player) event.getPlayer(); FastInv inv = (FastInv) event.getInventory().getHolder(); FastInvCloseEvent closeEvent = new FastInvCloseEvent(player, inv, false); inv.closeListeners.forEach(listener -> listener.onClose(closeEvent)); Bukkit.getScheduler().runTask(plugin, () -> { // Tiny delay to prevent errors. if (closeEvent.isCancelled() && player.isOnline()) { player.openInventory(inv.getInventory()); } else if (inv.getInventory().getViewers().isEmpty() && inv.cancelTasksOnClose) { inv.cancelTasks(); } }); } } @EventHandler public void onDisable(PluginDisableEvent event) { if (event.getPlugin().equals(plugin)) { for (Player player : Bukkit.getOnlinePlayers()) { if (player.getOpenInventory().getTopInventory().getHolder() instanceof FastInv) { player.closeInventory(); } } } } }; } public static class FastInvCloseEvent extends FastInvEvent { private FastInvCloseEvent(Player player, FastInv inventory, boolean cancelled) { super(player, inventory, cancelled); } } }