diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java index fd7273e2af..38c21349ac 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java @@ -1,914 +1,581 @@ -/* Obtained from https://www.spigotmc.org/threads/libish-inventory-api-kinda.49339/ - * and modified - */ - -package com.bencodez.advancedcore.api.inventory; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -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 com.bencodez.advancedcore.AdvancedCorePlugin; -import com.bencodez.advancedcore.api.item.ItemBuilder; -import com.bencodez.advancedcore.api.messages.PlaceholderUtils; -import com.bencodez.simpleapi.messages.MessageAPI; -import com.bencodez.simpleapi.player.PlayerUtils; - -import lombok.Getter; -import lombok.Setter; - -/** - * The Class BInventory. - */ -public class BInventory { - - /** - * The Class ClickEvent. - */ - public class ClickEvent { - - /** - * The button that was clicked. - * - * @return the button - */ - @Getter - private BInventoryButton button; - - /** - * The click type. - * - * @return the click type - */ - @Getter - private ClickType click; - - /** - * The clicked item. - * - * @return the clicked item - */ - @Getter - private ItemStack clickedItem; - - /** - * The inventory click event. - * - * @return the inventory click event - */ - @Getter - private InventoryClickEvent event; - - /** - * The inventory. - * - * @return the inventory - */ - @Getter - private Inventory inventory; - - /** - * The player who clicked. - * - * @return the player - */ - @Getter - private Player player; - - /** - * The slot that was clicked. - * - * @return the slot number - */ - @Getter - private int slot; - - /** - * Instantiates a new click event. - * - * @param event the inventory click event - * @param b the button - */ - public ClickEvent(InventoryClickEvent event, BInventoryButton b) { - this.event = event; - player = (Player) event.getWhoClicked(); - click = event.getClick(); - inventory = event.getInventory(); - clickedItem = event.getCurrentItem(); - slot = event.getSlot(); - button = b; - } - - /** - * Close the inventory for the player. - */ - public void closeInventory() { - runSync(new Runnable() { - - @Override - public void run() { - if (player != null) { - player.closeInventory(); - } - } - }); - } - - /** - * Gets the current item. - * - * @return the current item - */ - public ItemStack getCurrentItem() { - return clickedItem; - } - - /** - * Gets the meta. - * - * @param player the player - * @param str the str - * @return the meta - */ - public Object getMeta(Player player, String str) { - return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); - } - - /** - * Gets the meta. - * - * @param str the str - * @return the meta - */ - public Object getMeta(String str) { - return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); - } - - /** - * Gets the who clicked. - * - * @return the who clicked - */ - public Player getWhoClicked() { - return player; - } - - /** - * Run a task synchronously. - * - * @param run the runnable to execute - */ - public void runSync(Runnable run) { - AdvancedCorePlugin.getInstance().getBukkitScheduler().runTask(AdvancedCorePlugin.getInstance(), run); - } - } - - /** - * Open inventory. - * - * @param player the player - * @param inventory the inventory - */ - public static void openInventory(Player player, BInventory inventory) { - inventory.openInventory(player); - } - - private Map buttons = new HashMap<>(); - - /** - * Whether the inventory should close after interaction. - * - * @return whether the inventory should close - */ - @Getter - private boolean closeInv = true; - - private HashMap data = new HashMap<>(); - - private Inventory inv; - - private String inventoryName; - - /** - * The last time a button was pressed in this inventory. - * - * @return the last press time in milliseconds - * @param lastPressTime the last press time to set - */ - @Getter - @Setter - private long lastPressTime = 0; - - private int maxInvSize = 54; - - /** - * The maximum page number for paginated inventories. - * - * @return the maximum page number - */ - @Getter - private int maxPage = 1; - - private ItemStack nextItem; - - /** - * The current page number for paginated inventories. - * - * @return the current page number - */ - @Getter - private int page = 1; - - private ArrayList pageButtons = new ArrayList<>(); - - private boolean pages = false; - - private String perm; - - /** - * Placeholders to be replaced in the inventory. - * - * @return the placeholder map - */ - @Getter - private HashMap placeholders = new HashMap<>(); - - /** - * Whether to play sound on inventory interaction. - * - * @return whether sound should play - * @param playerSound whether sound should play - */ - @Getter - @Setter - private boolean playerSound = true; - - private ItemStack prevItem; - - @SuppressWarnings("rawtypes") - ArrayList futures; - - private ArrayList fillItems = new ArrayList<>(); - - /** - * Instantiates a new b inventory. - * - * @param name the name - */ - public BInventory(String name) { - setInventoryName(name); - } - - /** - * Adds the button. - * - * Slot of -2 will add item to end of the GUI (last available slot) - * - * @param button the button - */ - public void addButton(BInventoryButton button) { - int slot = button.getSlot(); - if (slot == -1) { - slot = getNextSlot(); - } - if (slot == -2) { - slot = getProperSize(getNextSlot()) - 1; - } - if (!button.isFillEmptySlots()) { - if (button.getFillSlots() != null && button.getFillSlots().size() > 0) { - for (Integer fill : button.getFillSlots()) { - slot = fill.intValue(); - BInventoryButton button1 = new BInventoryButton(button) { - - @Override - public void onClick(ClickEvent clickEvent) { - button.onClick(clickEvent); - } - }; - button1.setSlot(slot); - getButtons().put(slot, button1); - } - } else { - // no fill slots set - button.setSlot(slot); - getButtons().put(slot, button); - } - } else { - fillItems.add(button); - // fill empty slots - - } - } - - /** - * Check if a slot is taken by a button. - * - * @param slot the slot to check - * @return true if the slot is taken - */ - public boolean isSlotTaken(int slot) { - return buttons.containsKey(slot); - } - - /** - * Adds the button. - * - * @param position the position - * @param button the button - */ - public void addButton(int position, BInventoryButton button) { - getButtons().put(position, button); - } - - /** - * Add data to this inventory. - * - * @param key the key - * @param object the value - * @return this inventory - */ - public BInventory addData(String key, Object object) { - getData().put(key, object); - return this; - } - - private void addFillSlots() { - for (BInventoryButton button : fillItems) { - for (int i = 0; i < getInventorySize(); i++) { - boolean slotExist = false; - for (Integer exist : getButtons().keySet()) { - if (exist.intValue() == i) { - slotExist = true; - } - } - if (!slotExist) { - BInventoryButton button1 = new BInventoryButton(button) { - - @Override - public void onClick(ClickEvent clickEvent) { - button.onClick(clickEvent); - } - }; - button1.setSlot(i); - getButtons().put(i, button1); - } - } - } - fillItems.clear(); - } - - /** - * Add a placeholder to this inventory. - * - * @param toReplace the string to replace - * @param replaceWith the replacement string - * @return this inventory - */ - public BInventory addPlaceholder(String toReplace, String replaceWith) { - placeholders.put(toReplace, replaceWith); - return this; - } - - /** - * Add an updating button with scheduled updates. - * - * @param plugin the plugin instance - * @param delay the initial delay - * @param interval the update interval - * @param runnable the runnable to execute - */ - public void addUpdatingButton(AdvancedCorePlugin plugin, long delay, long interval, Runnable runnable) { - if (futures == null) { - futures = new ArrayList<>(); - } - futures.add(plugin.getInventoryTimer().scheduleWithFixedDelay(runnable, delay, interval, TimeUnit.MILLISECONDS)); - } - - /** - * Cancel all scheduled timers for this inventory. - */ - @SuppressWarnings("rawtypes") - public void cancelTimer() { - if (futures != null) { - for (ScheduledFuture f : futures) { - f.cancel(true); - } - futures = null; - } - } - - /** - * Close the inventory for a player after button click. - * - * @param p the player - * @param b the button that was clicked - */ - public void closeInv(Player p, BInventoryButton b) { - if (!PlayerUtils.getTopInventory(p).equals(inv)) { - return; - } - - if (pages || (closeInv && (b == null || !b.isCloseInvSet()))) { - forceClose(p); - return; - } - - if (b != null && b.isCloseInvSet() && b.isCloseInv()) { - forceClose(p); - return; - } - } - - private void closeUpdatingBInv() { - cancelTimer(); - } - - /** - * Set this inventory to not close after interaction. - * - * @return this inventory - */ - public BInventory dontClose() { - closeInv = false; - return this; - } - - /** - * Force close the inventory for a player. - * - * @param p the player - */ - public void forceClose(Player p) { - if (Bukkit.isPrimaryThread()) { - p.closeInventory(); - - AdvancedCorePlugin.getInstance().getBukkitScheduler() - .runTaskAsynchronously(AdvancedCorePlugin.getInstance(), new Runnable() { - - @Override - public void run() { - closeUpdatingBInv(); - } - }); - } else { - closeUpdatingBInv(); - AdvancedCorePlugin.getInstance().getBukkitScheduler().runTask(AdvancedCorePlugin.getInstance(), - new Runnable() { - - @Override - public void run() { - p.closeInventory(); - } - }, p); - } - } - - /** - * Gets the buttons. - * - * @return the buttons - */ - public Map getButtons() { - return buttons; - } - - /** - * @return the data - */ - public HashMap getData() { - return data; - } - - /** - * Get data by key. - * - * @param key the key - * @return the data value - */ - public Object getData(String key) { - return data.get(key); - } - - /** - * Get data by key with default value. - * - * @param key the key - * @param defaultValue the default value if key is not found - * @return the data value or default value - */ - public Object getData(String key, Object defaultValue) { - if (data.containsKey(key)) { - return data.get(key); - } - return defaultValue; - } - - /** - * Get the first empty slot in the inventory. - * - * @return the first empty slot number - */ - public int getFirstEmptySlot() { - if (buttons.keySet().size() == 0) { - return 0; - } - - for (int i = 0; i < getInventorySize(); i++) { - if (!buttons.containsKey(i)) { - return i; - } - } - return getHighestSlot() + 1; - - } - - /** - * Gets the highest slot. - * - * @return the highest slot - */ - public int getHighestSlot() { - int highestNum = 0; - for (int num : buttons.keySet()) { - if (num > highestNum) { - highestNum = num; - } - } - return highestNum; - } - - /** - * Gets the inventory name. - * - * @return the inventory name - */ - public String getInventoryName() { - return inventoryName; - } - - /** - * Gets the inventory size. - * - * @return the inventory size - */ - public int getInventorySize() { - return getProperSize(getHighestSlot()); - } - - /** - * @return the maxInvSize - */ - public int getMaxInvSize() { - return maxInvSize; - } - - /** - * Get meta from player. - * - * @param player the player - * @param str the meta key - * @return the meta value - */ - public Object getMeta(Player player, String str) { - return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); - } - - /** - * @return the nextItem - */ - public ItemStack getNextItem() { - return nextItem; - } - - /** - * Gets the next slot. - * - * @return the next slot - */ - public int getNextSlot() { - if (buttons.keySet().size() == 0) { - return 0; - } - return getHighestSlot() + 1; - } - - /** - * @return the pageButtons - */ - public ArrayList getPageButtons() { - return pageButtons; - } - - /** - * @return the prevItem - */ - public ItemStack getPrevItem() { - return prevItem; - } - - private int getProperSize(int size) { - if (size < 9) { - return 9; - } - if (size < 18) { - return 18; - } else if (size < 27) { - return 27; - } else if (size < 36) { - return 36; - } else if (size < 45) { - return 45; - } else { - return 54; - } - } - - /** - * Check if the inventory is open for a player. - * - * @param p the player - * @return true if the inventory is open - */ - public boolean isOpen(Player p) { - GUISession session = GUISession.extractSession(p); - if (session != null && session.getInventoryGUI() == this) { - return true; - } - return false; - } - - /** - * @return the pages - */ - public boolean isPages() { - return pages; - } - - /** - * Disable sound for this inventory. - * - * @return this inventory - */ - public BInventory noSound() { - playerSound = false; - return this; - } - - /** - * Handle button click. - * - * @param event the inventory click event - * @param b the button that was clicked - */ - public void onClick(InventoryClickEvent event, BInventoryButton b) { - playSound((Player) event.getWhoClicked()); - b.onClick(new ClickEvent(event, b), this); - } - - private void openInv(Player player, Inventory inv) { - AdvancedCorePlugin.getInstance().getBukkitScheduler().runTask(AdvancedCorePlugin.getInstance(), new Runnable() { - - @Override - public void run() { - player.openInventory(inv); - } - }, player); - } - - /** - * Open inventory. - * - * @param player the player - */ - @SuppressWarnings({}) - public void openInventory(Player player) { - if (player.isSleeping()) { - AdvancedCorePlugin.getInstance().debug(player.getName() + " is sleeping, not opening gui!"); - return; - } - if (perm != null) { - if (!perm.contains("|")) { - if (!player.hasPermission(perm)) { - player.sendMessage( - MessageAPI.colorize(AdvancedCorePlugin.getInstance().getOptions().getFormatNoPerms())); - return; - } - } else { - boolean hasPerm = false; - for (String permStr : perm.split(Pattern.quote("|"))) { - if (player.hasPermission(permStr)) { - hasPerm = true; - } - } - if (!hasPerm) { - player.sendMessage( - MessageAPI.colorize(AdvancedCorePlugin.getInstance().getOptions().getFormatNoPerms())); - return; - } - } - } - addFillSlots(); - BInventory inventory = this; - - if (inventory.getHighestSlot() >= maxInvSize) { - pages = true; - } - if (!pages) { - inv = Bukkit.createInventory(new GUISession(this, 1), inventory.getInventorySize(), - PlaceholderUtils.replaceJavascript(player, - PlaceholderUtils.replacePlaceHolder(inventory.getInventoryName(), getPlaceholders()))); - for (Entry pair : inventory.getButtons().entrySet()) { - ItemStack item = pair.getValue().getItem(player, getPlaceholders()); - inv.setItem(pair.getKey(), item); - - BInventoryButton b = pair.getValue(); - b.setInv(this); - b.setSlot(pair.getKey()); - b.load(player); - - } - - openInv(player, inv); - - } else { - maxPage = getHighestSlot() / (maxInvSize - 9); - if (getHighestSlot() % (maxInvSize - 9) != 0) { - maxPage++; - } - addPlaceholder("totalpages", "" + maxPage); - openInventory(player, 1); - } - - } - - /** - * Open inventory. - * - * @param player the player - * @param page the page - */ - public void openInventory(Player player, int page) { - BInventory inventory = this; - addPlaceholder("currentpage", "" + page); - inv = Bukkit.createInventory(new GUISession(this, page), maxInvSize, PlaceholderUtils.replaceJavascript(player, - PlaceholderUtils.replacePlaceHolder(inventory.getInventoryName(), getPlaceholders()))); - this.page = page; - int startSlot = (page - 1) * (maxInvSize - 9); - for (Entry pair : inventory.getButtons().entrySet()) { - int slot = pair.getKey(); - if (slot >= startSlot) { - slot -= startSlot; - if (slot < (maxInvSize - 9)) { - ItemStack item = pair.getValue().getItem(player, getPlaceholders()); - inv.setItem(slot, item); - - BInventoryButton b = pair.getValue(); - b.setInv(this); - b.setSlot(pair.getKey()); - b.load(player); - } - } - - } - - for (BInventoryButton b : pageButtons) { - inv.setItem((maxInvSize - 9) + b.getSlot(), b.getItem(player, getPlaceholders())); - } - if (prevItem == null) { - if (AdvancedCorePlugin.getInstance().getOptions().getPrevItem() != null) { - prevItem = new ItemBuilder(AdvancedCorePlugin.getInstance().getOptions().getPrevItem()) - .addPlaceholder(getPlaceholders()).toItemStack(player); - } else { - prevItem = new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE, 1).setName("&aPrevious Page") - .addPlaceholder(getPlaceholders()).toItemStack(player); - } - } - if (nextItem == null) { - if (AdvancedCorePlugin.getInstance().getOptions().getNextItem() != null) { - nextItem = new ItemBuilder(AdvancedCorePlugin.getInstance().getOptions().getNextItem()) - .addPlaceholder(getPlaceholders()).toItemStack(player); - } else { - nextItem = new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE, 1).setName("&aNext Page") - .addPlaceholder(getPlaceholders()).toItemStack(player); - } - } - - inv.setItem(maxInvSize - 9, prevItem); - - inv.setItem(maxInvSize - 1, nextItem); - - openInv(player, inv); - } - - /** - * Play sound for a player. - * - * @param player the player - */ - public void playSound(Player player) { - if (playerSound) { - Sound sound = AdvancedCorePlugin.getInstance().getOptions().getClickSoundSound(); - if (sound != null) { - player.playSound(player.getLocation(), sound, - (float) AdvancedCorePlugin.getInstance().getOptions().getClickSoundVolume(), - (float) AdvancedCorePlugin.getInstance().getOptions().getClickSoundPitch()); - } - } - } - - /** - * Require permission to open this inventory. - * - * @param permission the permission required - */ - public void requirePermission(String permission) { - this.perm = permission; - } - - /** - * @param buttons the buttons to set - */ - public void setButtons(Map buttons) { - this.buttons = buttons; - } - - /** - * Set whether the inventory should close after interaction. - * - * @param value whether to close - * @return this inventory - */ - public BInventory setCloseInv(boolean value) { - closeInv = value; - return this; - } - - /** - * Sets the inventory name. - * - * @param inventoryName the new inventory name - */ - public void setInventoryName(String inventoryName) { - this.inventoryName = MessageAPI.colorize(inventoryName); - } - - /** - * @param maxInvSize the maxInvSize to set - */ - public void setMaxInvSize(int maxInvSize) { - this.maxInvSize = getProperSize(maxInvSize); - } - - /** - * Sets the meta. - * - * @param player the player - * @param str the str - * @param ob the ob - */ - public void setMeta(Player player, String str, Object ob) { - PlayerUtils.setPlayerMeta(AdvancedCorePlugin.getInstance(), player, str, ob); - } - - /** - * @param nextItem the nextItem to set - */ - public void setNextItem(ItemStack nextItem) { - this.nextItem = nextItem; - } - - /** - * @param pageButtons the pageButtons to set - */ - public void setPageButtons(ArrayList pageButtons) { - this.pageButtons = pageButtons; - } - - /** - * @param pages the pages to set - */ - public void setPages(boolean pages) { - this.pages = pages; - } - - /** - * @param prevItem the prevItem to set - */ - public void setPrevItem(ItemStack prevItem) { - this.prevItem = prevItem; - } - -} \ No newline at end of file +/* Obtained from https://www.spigotmc.org/threads/libish-inventory-api-kinda.49339/ + * and modified + */ + +package com.bencodez.advancedcore.api.inventory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +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 com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.item.ItemBuilder; +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.simpleapi.player.PlayerUtils; + +import lombok.Getter; +import lombok.Setter; + +/** + * Inventory GUI with button, pagination and update-task lifecycle management. + */ +public class BInventory { + + public class ClickEvent { + @Getter + private final BInventoryButton button; + @Getter + private final ClickType click; + @Getter + private final ItemStack clickedItem; + @Getter + private final InventoryClickEvent event; + @Getter + private final Inventory inventory; + @Getter + private final Player player; + @Getter + private final int slot; + + public ClickEvent(InventoryClickEvent event, BInventoryButton button) { + this.event = event; + this.player = (Player) event.getWhoClicked(); + this.click = event.getClick(); + this.inventory = event.getInventory(); + this.clickedItem = event.getCurrentItem(); + this.slot = event.getSlot(); + this.button = button; + } + + public void closeInventory() { + runSync(() -> { + if (player != null) { + player.closeInventory(); + } + }); + } + + public ItemStack getCurrentItem() { + return clickedItem; + } + + public Object getMeta(Player player, String str) { + return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); + } + + public Object getMeta(String str) { + return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); + } + + public Player getWhoClicked() { + return player; + } + + public void runSync(Runnable runnable) { + AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance(); + plugin.getBukkitScheduler().runTask(plugin, runnable); + } + } + + public static void openInventory(Player player, BInventory inventory) { + inventory.openInventory(player); + } + + private Map buttons = new HashMap<>(); + @Getter + private boolean closeInv = true; + @Getter + private boolean clickAsync = true; + private final HashMap data = new HashMap<>(); + private final ArrayList fillItems = new ArrayList<>(); + private final List> futures = new CopyOnWriteArrayList<>(); + private final Map>> playerFutures = new ConcurrentHashMap<>(); + private Inventory inv; + private String inventoryName; + @Getter + @Setter + private long lastPressTime = 0; + private int maxInvSize = 54; + @Getter + private int maxPage = 1; + private ItemStack nextItem; + @Getter + private int page = 1; + private ArrayList pageButtons = new ArrayList<>(); + private boolean pages = false; + private String perm; + @Getter + private final HashMap placeholders = new HashMap<>(); + @Getter + @Setter + private boolean playerSound = true; + private ItemStack prevItem; + + public BInventory(String name) { + setInventoryName(name); + } + + public void addButton(BInventoryButton button) { + int slot = button.getSlot(); + if (slot == -1) { + slot = getNextSlot(); + } else if (slot == -2) { + slot = getProperSize(getNextSlot()) - 1; + } + if (button.isFillEmptySlots()) { + fillItems.add(button); + return; + } + List fillSlots = button.getFillSlots(); + if (fillSlots != null && !fillSlots.isEmpty()) { + for (Integer fillSlot : fillSlots) { + if (fillSlot != null) { + buttons.put(fillSlot, copyButton(button, fillSlot)); + } + } + return; + } + button.setSlot(slot); + buttons.put(slot, button); + } + + public boolean isSlotTaken(int slot) { + return buttons.containsKey(slot); + } + + public void addButton(int position, BInventoryButton button) { + buttons.put(position, button); + } + + public BInventory addData(String key, Object object) { + data.put(key, object); + return this; + } + + public BInventory addPlaceholder(String toReplace, String replaceWith) { + placeholders.put(toReplace, replaceWith); + return this; + } + + public void addUpdatingButton(AdvancedCorePlugin plugin, long delay, long interval, Runnable runnable) { + futures.add(plugin.getInventoryTimer().scheduleWithFixedDelay(runnable, delay, interval, TimeUnit.MILLISECONDS)); + } + + public void addUpdatingButton(Player player, AdvancedCorePlugin plugin, long delay, long interval, Runnable runnable) { + ScheduledFuture future = plugin.getInventoryTimer().scheduleWithFixedDelay(runnable, delay, interval, + TimeUnit.MILLISECONDS); + trackFuture(player, future); + } + + void addDelayedTask(Player player, AdvancedCorePlugin plugin, long delay, Runnable runnable) { + ScheduledFuture future = plugin.getInventoryTimer().schedule(runnable, delay, TimeUnit.MILLISECONDS); + trackFuture(player, future); + } + + public void cancelTimer() { + cancelLegacyTimers(); + for (List> viewerFutures : playerFutures.values()) { + cancelFutures(viewerFutures); + } + playerFutures.clear(); + } + + public void cancelTimer(Player player) { + if (player == null) { + return; + } + List> viewerFutures = playerFutures.remove(player.getUniqueId()); + cancelFutures(viewerFutures); + } + + public void closeInv(Player player, BInventoryButton button) { + Inventory topInventory = PlayerUtils.getTopInventory(player); + if (topInventory == null || inv == null || !topInventory.equals(inv)) { + return; + } + if (pages || (closeInv && (button == null || !button.isCloseInvSet()))) { + forceClose(player); + return; + } + if (button != null && button.isCloseInvSet() && button.isCloseInv()) { + forceClose(player); + } + } + + public BInventory dontClose() { + closeInv = false; + return this; + } + + public void forceClose(Player player) { + cancelLegacyTimers(); + cancelTimer(player); + if (player == null) { + return; + } + if (Bukkit.isPrimaryThread()) { + player.closeInventory(); + return; + } + AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance(); + plugin.getBukkitScheduler().runTask(plugin, player::closeInventory, player); + } + + public Map getButtons() { + return buttons; + } + + public HashMap getData() { + return data; + } + + public Object getData(String key) { + return data.get(key); + } + + public Object getData(String key, Object defaultValue) { + return data.containsKey(key) ? data.get(key) : defaultValue; + } + + public int getFirstEmptySlot() { + if (buttons.isEmpty()) { + return 0; + } + for (int i = 0; i < getInventorySize(); i++) { + if (!buttons.containsKey(i)) { + return i; + } + } + return getHighestSlot() + 1; + } + + public int getHighestSlot() { + int highest = 0; + for (int slot : buttons.keySet()) { + if (slot > highest) { + highest = slot; + } + } + return highest; + } + + public String getInventoryName() { + return inventoryName; + } + + public int getInventorySize() { + return getProperSize(getHighestSlot()); + } + + public int getMaxInvSize() { + return maxInvSize; + } + + public Object getMeta(Player player, String str) { + return PlayerUtils.getPlayerMeta(AdvancedCorePlugin.getInstance(), player, str); + } + + public ItemStack getNextItem() { + return nextItem; + } + + public int getNextSlot() { + return buttons.isEmpty() ? 0 : getHighestSlot() + 1; + } + + public ArrayList getPageButtons() { + return pageButtons; + } + + public ItemStack getPrevItem() { + return prevItem; + } + + public boolean isOpen(Player player) { + GUISession session = GUISession.extractSession(player); + return session != null && session.getInventoryGUI() == this; + } + + public boolean isPages() { + return pages; + } + + public BInventory noSound() { + playerSound = false; + return this; + } + + public void onClick(InventoryClickEvent event, BInventoryButton button) { + playSound((Player) event.getWhoClicked()); + button.onClick(new ClickEvent(event, button), this); + } + + public void openInventory(Player player) { + if (player.isSleeping()) { + AdvancedCorePlugin.getInstance().debug(player.getName() + " is sleeping, not opening gui!"); + return; + } + if (!hasPermission(player)) { + return; + } + addFillSlots(); + if (getHighestSlot() >= maxInvSize) { + pages = true; + } + if (pages) { + maxPage = InventoryPagination.getPageCount(getHighestSlot(), maxInvSize); + addPlaceholder("totalpages", String.valueOf(maxPage)); + openInventory(player, 1); + return; + } + cancelTimer(player); + inv = Bukkit.createInventory(new GUISession(this, 1), getInventorySize(), getDisplayName(player)); + for (Entry entry : buttons.entrySet()) { + loadButton(player, entry.getValue(), entry.getKey(), entry.getKey()); + } + openInv(player, inv); + } + + public void openInventory(Player player, int page) { + if (page < 1) { + throw new IllegalArgumentException("Page must be >= 1"); + } + maxPage = InventoryPagination.getPageCount(getHighestSlot(), maxInvSize); + int targetPage = Math.min(page, maxPage); + cancelTimer(player); + addPlaceholder("currentpage", String.valueOf(targetPage)); + addPlaceholder("totalpages", String.valueOf(maxPage)); + inv = Bukkit.createInventory(new GUISession(this, targetPage), maxInvSize, getDisplayName(player)); + this.page = targetPage; + int contentSize = InventoryPagination.getContentSize(maxInvSize); + int startSlot = (targetPage - 1) * contentSize; + for (Entry entry : buttons.entrySet()) { + int sourceSlot = entry.getKey(); + if (sourceSlot < startSlot || sourceSlot >= startSlot + contentSize) { + continue; + } + loadButton(player, entry.getValue(), sourceSlot, sourceSlot - startSlot); + } + for (BInventoryButton button : pageButtons) { + int displayedSlot = contentSize + button.getSlot(); + inv.setItem(displayedSlot, button.getItem(player, placeholders)); + button.setInv(this); + } + loadNavigationItems(player); + inv.setItem(contentSize, prevItem); + inv.setItem(maxInvSize - 1, nextItem); + openInv(player, inv); + } + + public void playSound(Player player) { + if (!playerSound) { + return; + } + AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance(); + Sound sound = plugin.getOptions().getClickSoundSound(); + if (sound != null) { + player.playSound(player.getLocation(), sound, (float) plugin.getOptions().getClickSoundVolume(), + (float) plugin.getOptions().getClickSoundPitch()); + } + } + + public void requirePermission(String permission) { + this.perm = permission; + } + + public void setButtons(Map buttons) { + this.buttons = buttons; + } + + public BInventory setClickAsync(boolean value) { + clickAsync = value; + return this; + } + + public BInventory runClicksSync() { + return setClickAsync(false); + } + + public BInventory runClicksAsync() { + return setClickAsync(true); + } + + public BInventory setCloseInv(boolean value) { + closeInv = value; + return this; + } + + public void setInventoryName(String inventoryName) { + this.inventoryName = MessageAPI.colorize(inventoryName); + } + + public void setMaxInvSize(int maxInvSize) { + this.maxInvSize = getProperSize(maxInvSize); + } + + public void setMeta(Player player, String str, Object ob) { + PlayerUtils.setPlayerMeta(AdvancedCorePlugin.getInstance(), player, str, ob); + } + + public void setNextItem(ItemStack nextItem) { + this.nextItem = nextItem; + } + + public void setPageButtons(ArrayList pageButtons) { + this.pageButtons = pageButtons; + } + + public void setPages(boolean pages) { + this.pages = pages; + if (!pages) { + maxPage = 1; + } + } + + public void setPrevItem(ItemStack prevItem) { + this.prevItem = prevItem; + } + + private void addFillSlots() { + if (fillItems.isEmpty()) { + return; + } + int inventorySize = getInventorySize(); + for (BInventoryButton button : fillItems) { + for (int slot = 0; slot < inventorySize; slot++) { + if (!buttons.containsKey(slot)) { + buttons.put(slot, copyButton(button, slot)); + } + } + } + fillItems.clear(); + } + + private BInventoryButton copyButton(BInventoryButton button, int slot) { + BInventoryButton copy = new BInventoryButton(button) { + @Override + public void onClick(ClickEvent clickEvent) { + button.onClick(clickEvent); + } + }; + copy.setSlot(slot); + return copy; + } + + private void cancelFutures(List> scheduledFutures) { + if (scheduledFutures == null) { + return; + } + for (ScheduledFuture future : scheduledFutures) { + if (future != null) { + future.cancel(true); + } + } + } + + private void cancelLegacyTimers() { + cancelFutures(futures); + futures.clear(); + } + + private String getDisplayName(Player player) { + return PlaceholderUtils.replaceJavascript(player, + PlaceholderUtils.replacePlaceHolder(inventoryName, placeholders)); + } + + private int getProperSize(int size) { + if (size < 9) { + return 9; + } + if (size < 18) { + return 18; + } + if (size < 27) { + return 27; + } + if (size < 36) { + return 36; + } + if (size < 45) { + return 45; + } + return 54; + } + + private boolean hasPermission(Player player) { + if (perm == null) { + return true; + } + if (!perm.contains("|")) { + if (player.hasPermission(perm)) { + return true; + } + } else { + for (String permission : perm.split(Pattern.quote("|"))) { + if (player.hasPermission(permission)) { + return true; + } + } + } + player.sendMessage(MessageAPI.colorize(AdvancedCorePlugin.getInstance().getOptions().getFormatNoPerms())); + return false; + } + + private void loadButton(Player player, BInventoryButton button, int sourceSlot, int displayedSlot) { + inv.setItem(displayedSlot, button.getItem(player, placeholders)); + button.setInv(this); + button.setSlot(sourceSlot); + button.load(player); + } + + private void loadNavigationItems(Player player) { + AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance(); + if (prevItem == null) { + if (plugin.getOptions().getPrevItem() != null) { + prevItem = new ItemBuilder(plugin.getOptions().getPrevItem()).addPlaceholder(placeholders).toItemStack(player); + } else { + prevItem = new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE, 1).setName("&aPrevious Page") + .addPlaceholder(placeholders).toItemStack(player); + } + } + if (nextItem == null) { + if (plugin.getOptions().getNextItem() != null) { + nextItem = new ItemBuilder(plugin.getOptions().getNextItem()).addPlaceholder(placeholders).toItemStack(player); + } else { + nextItem = new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE, 1).setName("&aNext Page") + .addPlaceholder(placeholders).toItemStack(player); + } + } + } + + private void openInv(Player player, Inventory inventory) { + AdvancedCorePlugin plugin = AdvancedCorePlugin.getInstance(); + plugin.getBukkitScheduler().runTask(plugin, () -> player.openInventory(inventory), player); + } + + private void trackFuture(Player player, ScheduledFuture future) { + if (player == null) { + futures.add(future); + return; + } + playerFutures.computeIfAbsent(player.getUniqueId(), ignored -> new CopyOnWriteArrayList<>()).add(future); + } +} diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventoryListener.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventoryListener.java index 6e6dc705c5..f62bcd5464 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventoryListener.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventoryListener.java @@ -1,182 +1,161 @@ -package com.bencodez.advancedcore.api.inventory; - -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.Event.Result; -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.InventoryType; -import org.bukkit.inventory.ItemStack; - -import com.bencodez.advancedcore.AdvancedCorePlugin; -import com.bencodez.simpleapi.messages.MessageAPI; - -/** - * Listener for inventory interactions. - */ -public class BInventoryListener implements Listener { - private AdvancedCorePlugin plugin; - - /** - * Instantiates a new BInventory listener. - * - * @param plugin the plugin instance - */ - public BInventoryListener(AdvancedCorePlugin plugin) { - this.plugin = plugin; - } - - /** - * Handle inventory click events. - * - * @param event the inventory click event - */ - @EventHandler(priority = EventPriority.LOWEST) - public void onInventoryClick(InventoryClickEvent event) { - if (!(event.getWhoClicked() instanceof Player)) { - return; - } - - final Player player = (Player) event.getWhoClicked(); - - plugin.getBukkitScheduler().runTaskAsynchronously(plugin, new Runnable() { - - @Override - public void run() { - if (player.getInventory().firstEmpty() != -1) { - plugin.getFullInventoryHandler().check(player); - } - } - }); - - final GUISession session = GUISession.extractSession(player); - if (session == null) { - return; - } - final BInventory gui = session.getInventoryGUI(); - event.setCancelled(true); - event.setResult(Result.DENY); - if (event.getClickedInventory() != null && event.getClickedInventory().getType() == InventoryType.CHEST) { - - if (event.isShiftClick() && event.getClickedInventory() != null - && event.getRawSlot() < event.getInventory().getSize()) { - event.setCurrentItem(new ItemStack(Material.AIR)); - } - player.setItemOnCursor(new ItemStack(Material.AIR)); - player.updateInventory(); - - if (plugin.getOptions().isCloseGUIOnShiftClick()) { - gui.forceClose(player); - } - if (gui.isCloseInv()) { - gui.closeInv(player, null); - } - - // prevent spam clicking, to avoid dupe issues on large servers - long cTime = System.currentTimeMillis(); - if (cTime - gui.getLastPressTime() < plugin.getOptions().getSpamClickTimeMs()) { - plugin.debug(player.getName() + " spam clicking GUI, preventing exploits"); - player.updateInventory(); - event.setCurrentItem(new ItemStack(Material.AIR)); - gui.forceClose(player); - - // spam click message - String msg = plugin.getOptions().getSpamClickMessage(); - if (!msg.isEmpty()) { - player.sendMessage(MessageAPI.colorize(msg)); - } - - return; - } - - gui.setLastPressTime(cTime); - - plugin.getBukkitScheduler().runTaskAsynchronously(plugin, new Runnable() { - - @Override - public void run() { - int slot = event.getSlot(); - if (!gui.isPages()) { - for (int buttonSlot : gui.getButtons().keySet()) { - BInventoryButton button = gui.getButtons().get(buttonSlot); - if (slot == buttonSlot) { - - gui.closeInv(player, button); - - try { - gui.onClick(event, button); - } catch (Exception e) { - e.printStackTrace(); - } - - } - - } - } else { - final int maxInvSize = gui.getMaxInvSize(); - final int page = session.getPage(); - final int maxPage = gui.getMaxPage(); - - if (slot < maxInvSize - 9) { - int buttonSlot = (page - 1) * (maxInvSize - 9) + event.getSlot(); - BInventoryButton button = gui.getButtons().get(buttonSlot); - if (button != null) { - gui.closeInv(player, button); - - try { - gui.onClick(event, button); - } catch (Exception e) { - e.printStackTrace(); - } - - return; - } - - } else if (slot == maxInvSize - 9) { - if (page > 1) { - - final int nextPage = page - 1; - - // gui.forceClose(player); - gui.playSound(player); - gui.openInventory(player, nextPage); - - } - } else if (slot == maxInvSize - 1) { - // AdvancedCorePlugin.getInstance().debug(maxPage + " " + - // page); - if (maxPage > page) { - - final int nextPage = page + 1; - - gui.playSound(player); - // gui.forceClose(player); - gui.openInventory(player, nextPage); - - } - - } - - for (BInventoryButton b : gui.getPageButtons()) { - if (slot == b.getSlot() + (gui.getMaxInvSize() - 9)) { - gui.closeInv(player, b); - - try { - gui.onClick(event, b); - } catch (Exception e) { - e.printStackTrace(); - } - return; - - } - - } - } - } - }); - } - - } -} +package com.bencodez.advancedcore.api.inventory; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.simpleapi.player.PlayerUtils; + +/** + * Listener for inventory interactions. + */ +public class BInventoryListener implements Listener { + private final AdvancedCorePlugin plugin; + + public BInventoryListener(AdvancedCorePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player)) { + return; + } + + Player player = (Player) event.getWhoClicked(); + scheduleFullInventoryCheck(player); + + GUISession session = GUISession.extractSession(player); + if (session == null) { + return; + } + + BInventory gui = session.getInventoryGUI(); + event.setCancelled(true); + event.setResult(Result.DENY); + + if (event.getClickedInventory() == null || event.getClickedInventory() != PlayerUtils.getTopInventory(player)) { + return; + } + + prepareClick(event, player, gui); + if (isSpamClick(player, gui, event)) { + return; + } + + gui.setLastPressTime(System.currentTimeMillis()); + if (gui.isPages()) { + handlePagedClick(event, player, gui, session); + } else { + handleButtonClick(event, player, gui, gui.getButtons().get(event.getSlot())); + } + } + + private void scheduleFullInventoryCheck(Player player) { + plugin.getBukkitScheduler().runTask(plugin, () -> { + if (player.getInventory().firstEmpty() != -1) { + plugin.getFullInventoryHandler().check(player); + } + }, player); + } + + private void prepareClick(InventoryClickEvent event, Player player, BInventory gui) { + if (event.isShiftClick() && event.getRawSlot() < event.getInventory().getSize()) { + event.setCurrentItem(new ItemStack(Material.AIR)); + } + player.setItemOnCursor(new ItemStack(Material.AIR)); + player.updateInventory(); + + if (plugin.getOptions().isCloseGUIOnShiftClick()) { + gui.forceClose(player); + } + if (gui.isCloseInv()) { + gui.closeInv(player, null); + } + } + + private boolean isSpamClick(Player player, BInventory gui, InventoryClickEvent event) { + long currentTime = System.currentTimeMillis(); + if (currentTime - gui.getLastPressTime() >= plugin.getOptions().getSpamClickTimeMs()) { + return false; + } + + plugin.debug(player.getName() + " spam clicking GUI, preventing exploits"); + player.updateInventory(); + event.setCurrentItem(new ItemStack(Material.AIR)); + gui.forceClose(player); + + String message = plugin.getOptions().getSpamClickMessage(); + if (!message.isEmpty()) { + player.sendMessage(MessageAPI.colorize(message)); + } + return true; + } + + private void handlePagedClick(InventoryClickEvent event, Player player, BInventory gui, GUISession session) { + int slot = event.getSlot(); + int maxInventorySize = gui.getMaxInvSize(); + int contentSize = InventoryPagination.getContentSize(maxInventorySize); + int page = session.getPage(); + + if (InventoryPagination.isContentSlot(slot, maxInventorySize)) { + int buttonSlot = InventoryPagination.getButtonSlot(page, slot, maxInventorySize); + BInventoryButton button = gui.getButtons().get(buttonSlot); + if (button != null) { + handleButtonClick(event, player, gui, button); + } + return; + } + + if (slot == contentSize) { + if (page > 1) { + gui.playSound(player); + gui.openInventory(player, page - 1); + } + return; + } + + if (slot == maxInventorySize - 1) { + if (page < gui.getMaxPage()) { + gui.playSound(player); + gui.openInventory(player, page + 1); + } + return; + } + + for (BInventoryButton button : gui.getPageButtons()) { + if (slot == button.getSlot() + contentSize) { + handleButtonClick(event, player, gui, button); + return; + } + } + } + + private void handleButtonClick(InventoryClickEvent event, Player player, BInventory gui, BInventoryButton button) { + if (button == null) { + return; + } + + gui.closeInv(player, button); + Runnable clickAction = () -> { + try { + gui.onClick(event, button); + } catch (Exception exception) { + plugin.debug(exception); + } + }; + + if (gui.isClickAsync()) { + plugin.getBukkitScheduler().runTaskAsynchronously(plugin, clickAction); + } else { + clickAction.run(); + } + } +} diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/GUISession.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/GUISession.java index 9bd8033218..ea81d3e430 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/GUISession.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/GUISession.java @@ -1,112 +1,95 @@ -package com.bencodez.advancedcore.api.inventory; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.InventoryView; - -import com.bencodez.simpleapi.player.PlayerUtils; - -/** - * Session holder for managing GUI state across inventory interactions. - */ -public class GUISession implements InventoryHolder { - /** - * Get the GUISession for a given inventory, or null if none exists for this - * inventory - * - * @param inventory The inventory to get the GUISession from - * @return The GUISession or null if none exists - */ - public static GUISession extractSession(Inventory inventory) { - if (inventory == null) { - return null; - } - InventoryHolder ih = inventory.getHolder(); - if (ih != null && ih instanceof GUISession) { - return (GUISession) ih; - } - return null; - } - - /** - * Extract the GUISession from the inventory currently being viewed by a player, - * or null if none exists - * - * @param player The player who's open inventory to extract the GUISession from - * @return The GUISession or null if none exists - */ - public static GUISession extractSession(Player player) { - if (player == null) { - return null; - } - InventoryView oInv = player.getOpenInventory(); - if (oInv == null) { - return null; - } - Inventory inv = PlayerUtils.getTopInventory(player); - if (inv != null) { - return extractSession(inv); - } - return extractSession(oInv.getTopInventory()); - - } - - private BInventory inventoryGUI; // GUI Being viewed - private int page = 1; // Currently displayed page number - - /** - * Construct a new GUISession - * - * @param inventoryGUI The inventory that this a session to view - * @param page The page currently being viewed - */ - public GUISession(BInventory inventoryGUI, int page) { - if (inventoryGUI == null) { - throw new IllegalArgumentException("InventoryGUI must not be null"); - } - - this.inventoryGUI = inventoryGUI; - this.page = page; - } - - /** - * Method inherited from Bukkit's InventoryHolder. Will always return null - * - * @return Null - */ - @Override - public Inventory getInventory() { // Part of InventoryHolder from bukkit - return null; // doesn't matter at all if null returned - } - - /** - * Get the InventoryGUI being viewed - * - * @return The InventoryGUI being viewed - */ - public BInventory getInventoryGUI() { - return inventoryGUI; - } - - /** - * Get the page currently being viewed - * - * @return The page - */ - public int getPage() { - return page; - } - - /** - * Set the page currently being viewed - * - * @param page The page - */ - public void setPage(int page) { - if (page < 1) { - throw new IllegalArgumentException("Page must be >= 1"); - } - this.page = page; - } -} +package com.bencodez.advancedcore.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.InventoryView; + +import com.bencodez.simpleapi.player.PlayerUtils; + +/** + * Session holder for managing GUI state across inventory interactions. + */ +public class GUISession implements InventoryHolder { + + /** + * Get the GUISession for a given inventory, or null if none exists for this + * inventory. + * + * @param inventory the inventory to inspect + * @return the GUISession or null if none exists + */ + public static GUISession extractSession(Inventory inventory) { + if (inventory == null) { + return null; + } + InventoryHolder holder = inventory.getHolder(); + return holder instanceof GUISession ? (GUISession) holder : null; + } + + /** + * Extract the GUISession from the inventory currently being viewed by a player. + * + * @param player the player whose open inventory should be inspected + * @return the GUISession or null if none exists + */ + public static GUISession extractSession(Player player) { + if (player == null) { + return null; + } + + InventoryView openInventory = player.getOpenInventory(); + if (openInventory == null) { + return null; + } + + Inventory topInventory = PlayerUtils.getTopInventory(player); + if (topInventory == null) { + topInventory = openInventory.getTopInventory(); + } + return extractSession(topInventory); + } + + private final BInventory inventoryGUI; + private int page = 1; + + /** + * Construct a new GUISession. + * + * @param inventoryGUI the GUI represented by this session + * @param page the page currently being viewed + */ + public GUISession(BInventory inventoryGUI, int page) { + if (inventoryGUI == null) { + throw new IllegalArgumentException("InventoryGUI must not be null"); + } + this.inventoryGUI = inventoryGUI; + setPage(page); + } + + /** + * InventoryHolder implementation. The concrete inventory owns this holder, so + * no secondary inventory needs to be returned here. + * + * @return null + */ + @Override + public Inventory getInventory() { + return null; + } + + public BInventory getInventoryGUI() { + return inventoryGUI; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + if (page < 1) { + throw new IllegalArgumentException("Page must be >= 1"); + } + this.page = page; + } +} diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/InventoryPagination.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/InventoryPagination.java new file mode 100644 index 0000000000..e1bcf7cefd --- /dev/null +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/InventoryPagination.java @@ -0,0 +1,32 @@ +package com.bencodez.advancedcore.api.inventory; + +/** + * Shared pagination calculations for {@link BInventory} and its listener. + */ +public final class InventoryPagination { + private static final int NAVIGATION_ROW_SIZE = 9; + + private InventoryPagination() { + } + + public static int getContentSize(int inventorySize) { + return Math.max(1, inventorySize - NAVIGATION_ROW_SIZE); + } + + public static int getPageCount(int highestSlot, int inventorySize) { + int contentSize = getContentSize(inventorySize); + int requiredSlots = Math.max(1, highestSlot + 1); + return Math.max(1, (requiredSlots + contentSize - 1) / contentSize); + } + + public static int getButtonSlot(int page, int displayedSlot, int inventorySize) { + if (page < 1) { + throw new IllegalArgumentException("Page must be >= 1"); + } + return (page - 1) * getContentSize(inventorySize) + displayedSlot; + } + + public static boolean isContentSlot(int slot, int inventorySize) { + return slot >= 0 && slot < getContentSize(inventorySize); + } +} diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/UpdatingBInventoryButton.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/UpdatingBInventoryButton.java index 71ef7bcb46..9f326e25e3 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/UpdatingBInventoryButton.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/UpdatingBInventoryButton.java @@ -1,156 +1,156 @@ -package com.bencodez.advancedcore.api.inventory; - -import java.util.concurrent.TimeUnit; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import com.bencodez.advancedcore.AdvancedCorePlugin; -import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent; -import com.bencodez.advancedcore.api.item.ItemBuilder; -import com.bencodez.simpleapi.player.PlayerUtils; - -import lombok.Getter; - -public abstract class UpdatingBInventoryButton extends BInventoryButton { - @Getter - private long delay; - @Getter - private long updateInterval; - private AdvancedCorePlugin plugin; - @Getter - private boolean updateOnClick = false; - @Getter - private long clickUpdateDelay = 0; - - public UpdatingBInventoryButton(AdvancedCorePlugin plugin, ItemBuilder item, long delay, long updateInterval) { - super(item); - this.plugin = plugin; - this.updateInterval = updateInterval; - this.delay = delay; - } - - public UpdatingBInventoryButton(AdvancedCorePlugin plugin, ItemStack item, long delay, long updateInterval) { - super(item); - this.plugin = plugin; - this.updateInterval = updateInterval; - this.delay = delay; - } - - public UpdatingBInventoryButton(AdvancedCorePlugin plugin, String name, String[] lore, ItemStack item, long delay, - long updateInterval) { - super(name, lore, item); - this.plugin = plugin; - this.updateInterval = updateInterval; - this.delay = delay; - } - - private void checkUpdate(Player p) { - if (!plugin.isLoadUserData() || plugin.getUserManager().getDataManager().isCached(p.getUniqueId())) { - final ItemStack item = onUpdate(p).toItemStack(p); - if (item != null) { - if (plugin.isEnabled()) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - - @Override - public void run() { - try { - if (p != null && getInv().isOpen(p)) { - if (getFillSlots() != null && getFillSlots().size() > 0) { - for (Integer slot : getFillSlots()) { - PlayerUtils.getTopInventory(p).setItem(slot.intValue(), item); - } - } else { - PlayerUtils.getTopInventory(p).setItem(getSlot(), item); - } - } else { - getInv().cancelTimer(); - } - } catch (Exception e) { - plugin.debug(e); - getInv().cancelTimer(); - } - - } - }, p); - } else { - getInv().cancelTimer(); - } - } else { - getInv().cancelTimer(); - } - } - } - - public UpdatingBInventoryButton delay(long mileseconds) { - this.clickUpdateDelay = mileseconds; - return this; - } - - @Override - public void load(Player p) { - getInv().addUpdatingButton(plugin, delay, updateInterval, new Runnable() { - - @Override - public void run() { - checkUpdate(p); - } - }); - } - - @Override - public void onClick(ClickEvent event, BInventory inv) { - super.onClick(event, inv); - if (updateOnClick) { - if (clickUpdateDelay > 0) { - plugin.getInventoryTimer().schedule(new Runnable() { - - @Override - public void run() { - update(event.getPlayer()); - } - }, clickUpdateDelay, TimeUnit.MILLISECONDS); - } else { - update(event.getPlayer()); - } - } - } - - public abstract ItemBuilder onUpdate(Player player); - - public void update(Player p) { - if (!plugin.isLoadUserData() || plugin.getUserManager().getDataManager().isCached(p.getUniqueId())) { - final ItemStack item = onUpdate(p).toItemStack(p); - if (item != null) { - if (plugin.isEnabled()) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - - @Override - public void run() { - try { - if (p != null && getInv().isOpen(p)) { - if (getFillSlots() != null && getFillSlots().size() > 0) { - for (Integer slot : getFillSlots()) { - p.getOpenInventory().getTopInventory().setItem(slot.intValue(), item); - } - } else { - p.getOpenInventory().getTopInventory().setItem(getSlot(), item); - } - } - } catch (Exception e) { - plugin.debug(e); - } - - } - }, p); - } - } - } - } - - public UpdatingBInventoryButton updateOnClick() { - updateOnClick = true; - return this; - } - -} +package com.bencodez.advancedcore.api.inventory; + +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent; +import com.bencodez.advancedcore.api.item.ItemBuilder; +import com.bencodez.simpleapi.player.PlayerUtils; + +import lombok.Getter; + +public abstract class UpdatingBInventoryButton extends BInventoryButton { + @Getter + private final long delay; + @Getter + private final long updateInterval; + private final AdvancedCorePlugin plugin; + @Getter + private boolean updateOnClick = false; + @Getter + private long clickUpdateDelay = 0; + + public UpdatingBInventoryButton(AdvancedCorePlugin plugin, ItemBuilder item, long delay, long updateInterval) { + super(item); + this.plugin = plugin; + this.updateInterval = updateInterval; + this.delay = delay; + } + + public UpdatingBInventoryButton(AdvancedCorePlugin plugin, ItemStack item, long delay, long updateInterval) { + super(item); + this.plugin = plugin; + this.updateInterval = updateInterval; + this.delay = delay; + } + + public UpdatingBInventoryButton(AdvancedCorePlugin plugin, String name, String[] lore, ItemStack item, long delay, + long updateInterval) { + super(name, lore, item); + this.plugin = plugin; + this.updateInterval = updateInterval; + this.delay = delay; + } + + public UpdatingBInventoryButton delay(long milliseconds) { + this.clickUpdateDelay = milliseconds; + return this; + } + + @Override + public void load(Player player) { + BInventory inventory = getInv(); + if (inventory == null) { + return; + } + inventory.addUpdatingButton(player, plugin, delay, updateInterval, () -> scheduleUpdate(player, true)); + } + + @Override + public void onClick(ClickEvent event, BInventory inventory) { + super.onClick(event, inventory); + if (!updateOnClick) { + return; + } + + if (clickUpdateDelay > 0) { + inventory.addDelayedTask(event.getPlayer(), plugin, clickUpdateDelay, () -> update(event.getPlayer())); + } else { + update(event.getPlayer()); + } + } + + public abstract ItemBuilder onUpdate(Player player); + + public void update(Player player) { + scheduleUpdate(player, false); + } + + public UpdatingBInventoryButton updateOnClick() { + updateOnClick = true; + return this; + } + + private void scheduleUpdate(Player player, boolean cancelWhenUnavailable) { + BInventory inventory = getInv(); + if (inventory == null) { + return; + } + if (!plugin.isEnabled()) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + plugin.getBukkitScheduler().runTask(plugin, () -> applyUpdate(player, cancelWhenUnavailable), player); + } + + private void applyUpdate(Player player, boolean cancelWhenUnavailable) { + BInventory inventory = getInv(); + if (inventory == null || player == null || !plugin.isEnabled()) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + if (plugin.isLoadUserData() && !plugin.getUserManager().getDataManager().isCached(player.getUniqueId())) { + return; + } + if (!inventory.isOpen(player)) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + try { + ItemBuilder builder = onUpdate(player); + if (builder == null) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + ItemStack item = builder.toItemStack(player); + if (item == null) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + Inventory topInventory = PlayerUtils.getTopInventory(player); + if (topInventory == null) { + cancelIfRequested(inventory, player, cancelWhenUnavailable); + return; + } + + List fillSlots = getFillSlots(); + if (fillSlots != null && !fillSlots.isEmpty()) { + for (Integer slot : fillSlots) { + if (slot != null) { + topInventory.setItem(slot.intValue(), item); + } + } + } else { + topInventory.setItem(getSlot(), item); + } + } catch (Exception exception) { + plugin.debug(exception); + cancelIfRequested(inventory, player, cancelWhenUnavailable); + } + } + + private void cancelIfRequested(BInventory inventory, Player player, boolean cancelWhenUnavailable) { + if (cancelWhenUnavailable && inventory != null) { + inventory.cancelTimer(player); + } + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryListenerTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryListenerTest.java new file mode 100644 index 0000000000..d84242cc52 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryListenerTest.java @@ -0,0 +1,106 @@ +package com.bencodez.advancedcore.tests.inventory; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import com.bencodez.advancedcore.AdvancedCoreConfigOptions; +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.inventory.BInventory; +import com.bencodez.advancedcore.api.inventory.BInventoryButton; +import com.bencodez.advancedcore.api.inventory.BInventoryListener; +import com.bencodez.advancedcore.api.inventory.GUISession; +import com.bencodez.simpleapi.player.PlayerUtils; +import com.bencodez.simpleapi.scheduler.BukkitScheduler; + +public class BInventoryListenerTest { + + @Test + public void guiButtonClickDefaultsToAsyncCallback() { + TestContext context = createContext(true); + ArgumentCaptor asyncTask = ArgumentCaptor.forClass(Runnable.class); + + runListener(context); + + verify(context.scheduler).runTask(eq(context.plugin), any(Runnable.class), eq(context.player)); + verify(context.scheduler).runTaskAsynchronously(eq(context.plugin), asyncTask.capture()); + verify(context.gui, never()).onClick(context.event, context.button); + + asyncTask.getValue().run(); + verify(context.gui).onClick(context.event, context.button); + } + + @Test + public void guiButtonClickCanOptIntoSyncCallback() { + TestContext context = createContext(false); + + runListener(context); + + verify(context.scheduler).runTask(eq(context.plugin), any(Runnable.class), eq(context.player)); + verify(context.scheduler, never()).runTaskAsynchronously(eq(context.plugin), any(Runnable.class)); + verify(context.gui).onClick(context.event, context.button); + } + + private TestContext createContext(boolean clickAsync) { + TestContext context = new TestContext(); + context.plugin = mock(AdvancedCorePlugin.class); + context.options = mock(AdvancedCoreConfigOptions.class); + context.scheduler = mock(BukkitScheduler.class); + context.event = mock(InventoryClickEvent.class); + context.player = mock(Player.class); + context.view = mock(InventoryView.class); + context.topInventory = mock(Inventory.class); + context.gui = mock(BInventory.class); + context.button = mock(BInventoryButton.class); + context.session = new GUISession(context.gui, 1); + + when(context.plugin.getOptions()).thenReturn(context.options); + when(context.plugin.getBukkitScheduler()).thenReturn(context.scheduler); + when(context.options.getSpamClickTimeMs()).thenReturn(0L); + when(context.event.getWhoClicked()).thenReturn(context.player); + when(context.player.getOpenInventory()).thenReturn(context.view); + when(context.topInventory.getHolder()).thenReturn(context.session); + when(context.event.getClickedInventory()).thenReturn(context.topInventory); + when(context.event.getInventory()).thenReturn(context.topInventory); + when(context.event.getSlot()).thenReturn(3); + when(context.gui.getButtons()).thenReturn(Map.of(3, context.button)); + when(context.gui.isPages()).thenReturn(false); + when(context.gui.isCloseInv()).thenReturn(false); + when(context.gui.isClickAsync()).thenReturn(clickAsync); + return context; + } + + private void runListener(TestContext context) { + try (MockedStatic playerUtils = mockStatic(PlayerUtils.class)) { + playerUtils.when(() -> PlayerUtils.getTopInventory(context.player)).thenReturn(context.topInventory); + new BInventoryListener(context.plugin).onInventoryClick(context.event); + } + } + + private static class TestContext { + private AdvancedCorePlugin plugin; + private AdvancedCoreConfigOptions options; + private BukkitScheduler scheduler; + private InventoryClickEvent event; + private Player player; + private InventoryView view; + private Inventory topInventory; + private BInventory gui; + private BInventoryButton button; + private GUISession session; + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java index 2943b530c5..2bd0aba461 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java @@ -1,15 +1,21 @@ package com.bencodez.advancedcore.tests.inventory; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.bukkit.entity.Player; import org.junit.jupiter.api.Test; import com.bencodez.advancedcore.AdvancedCorePlugin; @@ -17,6 +23,19 @@ public class BInventoryTest { + @Test + public void clickCallbacksDefaultToAsyncAndCanOptIntoSync() { + BInventory inventory = new BInventory("Test"); + + assertTrue(inventory.isClickAsync()); + assertSame(inventory, inventory.runClicksSync()); + assertFalse(inventory.isClickAsync()); + assertSame(inventory, inventory.runClicksAsync()); + assertTrue(inventory.isClickAsync()); + assertSame(inventory, inventory.setClickAsync(false)); + assertFalse(inventory.isClickAsync()); + } + @Test @SuppressWarnings("rawtypes") public void updatingButtonUsesConfiguredIntervalAndCancelsFuture() { @@ -35,4 +54,54 @@ public void updatingButtonUsesConfiguredIntervalAndCancelsFuture() { verify(timer).scheduleWithFixedDelay(runnable, 100L, 250L, TimeUnit.MILLISECONDS); verify(future).cancel(true); } + + @Test + @SuppressWarnings("rawtypes") + public void legacyUpdatingButtonIsCancelledWhenGuiIsForceClosed() { + AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); + ScheduledExecutorService timer = mock(ScheduledExecutorService.class); + ScheduledFuture future = mock(ScheduledFuture.class); + Runnable runnable = mock(Runnable.class); + when(plugin.getInventoryTimer()).thenReturn(timer); + doReturn(future).when(timer).scheduleWithFixedDelay(eq(runnable), eq(100L), eq(250L), + eq(TimeUnit.MILLISECONDS)); + + BInventory inventory = new BInventory("Test"); + inventory.addUpdatingButton(plugin, 100L, 250L, runnable); + inventory.forceClose(null); + + verify(future).cancel(true); + } + + @Test + @SuppressWarnings("rawtypes") + public void viewerCancellationDoesNotCancelOtherViewerTasks() { + AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); + ScheduledExecutorService timer = mock(ScheduledExecutorService.class); + ScheduledFuture firstFuture = mock(ScheduledFuture.class); + ScheduledFuture secondFuture = mock(ScheduledFuture.class); + Player firstPlayer = mock(Player.class); + Player secondPlayer = mock(Player.class); + Runnable firstRunnable = mock(Runnable.class); + Runnable secondRunnable = mock(Runnable.class); + when(plugin.getInventoryTimer()).thenReturn(timer); + when(firstPlayer.getUniqueId()).thenReturn(UUID.fromString("00000000-0000-0000-0000-000000000001")); + when(secondPlayer.getUniqueId()).thenReturn(UUID.fromString("00000000-0000-0000-0000-000000000002")); + doReturn(firstFuture).when(timer).scheduleWithFixedDelay(eq(firstRunnable), eq(10L), eq(20L), + eq(TimeUnit.MILLISECONDS)); + doReturn(secondFuture).when(timer).scheduleWithFixedDelay(eq(secondRunnable), eq(30L), eq(40L), + eq(TimeUnit.MILLISECONDS)); + + BInventory inventory = new BInventory("Test"); + inventory.addUpdatingButton(firstPlayer, plugin, 10L, 20L, firstRunnable); + inventory.addUpdatingButton(secondPlayer, plugin, 30L, 40L, secondRunnable); + + inventory.cancelTimer(firstPlayer); + + verify(firstFuture).cancel(true); + verify(secondFuture, never()).cancel(true); + + inventory.cancelTimer(); + verify(secondFuture).cancel(true); + } } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/GUISessionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/GUISessionTest.java new file mode 100644 index 0000000000..d084ab2212 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/GUISessionTest.java @@ -0,0 +1,44 @@ +package com.bencodez.advancedcore.tests.inventory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.junit.jupiter.api.Test; + +import com.bencodez.advancedcore.api.inventory.BInventory; +import com.bencodez.advancedcore.api.inventory.GUISession; + +public class GUISessionTest { + + @Test + public void constructorValidatesPageAndKeepsGuiReference() { + BInventory gui = mock(BInventory.class); + GUISession session = new GUISession(gui, 2); + + assertSame(gui, session.getInventoryGUI()); + assertEquals(2, session.getPage()); + assertThrows(IllegalArgumentException.class, () -> new GUISession(gui, 0)); + assertThrows(IllegalArgumentException.class, () -> new GUISession(null, 1)); + } + + @Test + public void inventoryExtractionOnlyReturnsGuiSessionHolders() { + BInventory gui = mock(BInventory.class); + GUISession session = new GUISession(gui, 1); + Inventory inventory = mock(Inventory.class); + when(inventory.getHolder()).thenReturn(session); + + assertSame(session, GUISession.extractSession(inventory)); + + Inventory unrelated = mock(Inventory.class); + when(unrelated.getHolder()).thenReturn(mock(InventoryHolder.class)); + assertNull(GUISession.extractSession(unrelated)); + assertNull(GUISession.extractSession((Inventory) null)); + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/InventoryPaginationTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/InventoryPaginationTest.java new file mode 100644 index 0000000000..39519a7505 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/InventoryPaginationTest.java @@ -0,0 +1,38 @@ +package com.bencodez.advancedcore.tests.inventory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.bencodez.advancedcore.api.inventory.InventoryPagination; + +public class InventoryPaginationTest { + + @Test + public void pageCountHandlesZeroBasedSlotBoundary() { + assertEquals(45, InventoryPagination.getContentSize(54)); + assertEquals(1, InventoryPagination.getPageCount(44, 54)); + assertEquals(2, InventoryPagination.getPageCount(45, 54)); + assertEquals(2, InventoryPagination.getPageCount(89, 54)); + assertEquals(3, InventoryPagination.getPageCount(90, 54)); + } + + @Test + public void displayedSlotsMapBackToSourceSlots() { + assertEquals(0, InventoryPagination.getButtonSlot(1, 0, 54)); + assertEquals(44, InventoryPagination.getButtonSlot(1, 44, 54)); + assertEquals(45, InventoryPagination.getButtonSlot(2, 0, 54)); + assertEquals(89, InventoryPagination.getButtonSlot(2, 44, 54)); + assertThrows(IllegalArgumentException.class, () -> InventoryPagination.getButtonSlot(0, 0, 54)); + } + + @Test + public void contentSlotCheckExcludesNavigationRow() { + assertTrue(InventoryPagination.isContentSlot(44, 54)); + assertFalse(InventoryPagination.isContentSlot(45, 54)); + assertFalse(InventoryPagination.isContentSlot(53, 54)); + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/UpdatingBInventoryButtonTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/UpdatingBInventoryButtonTest.java new file mode 100644 index 0000000000..f110ad1a2d --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/UpdatingBInventoryButtonTest.java @@ -0,0 +1,40 @@ +package com.bencodez.advancedcore.tests.inventory; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.Test; + +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.inventory.BInventory; +import com.bencodez.advancedcore.api.inventory.UpdatingBInventoryButton; +import com.bencodez.advancedcore.api.item.ItemBuilder; + +public class UpdatingBInventoryButtonTest { + + @Test + public void loadRegistersPeriodicUpdateForSpecificViewer() { + AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); + BInventory inventory = mock(BInventory.class); + Player player = mock(Player.class); + ItemBuilder item = mock(ItemBuilder.class); + UpdatingBInventoryButton button = new UpdatingBInventoryButton(plugin, item, 100L, 250L) { + @Override + public ItemBuilder onUpdate(Player player) { + return item; + } + + @Override + public void onClick(BInventory.ClickEvent clickEvent) { + } + }; + button.setInv(inventory); + + button.load(player); + + verify(inventory).addUpdatingButton(eq(player), eq(plugin), eq(100L), eq(250L), any(Runnable.class)); + } +}