From 4829504492a97f893abaeeef772369e2c4881f7f Mon Sep 17 00:00:00 2001 From: Eric <60104846+ericlmao@users.noreply.github.com> Date: Fri, 15 May 2026 02:00:54 -0300 Subject: [PATCH 1/5] Revamp menu holders and remove cache --- README.md | 2 +- .../moonrise/engine/paper/gui/ChestMenu.java | 38 +++++++++++--- .../moonrise/engine/paper/gui/HopperMenu.java | 42 ++++++++++++---- .../engine/paper/gui/PaginatedMenu.java | 43 ++++++++++++---- .../engine/paper/gui/UserInterface.java | 32 ++++++------ .../engine/paper/gui/button/Button.java | 26 ++++++++-- .../controller/PlayerInventoryController.java | 49 ++++++------------- .../paper/gui/holder/ChestMenuHolder.java | 46 +++++++++++++++++ .../paper/gui/holder/HopperMenuHolder.java | 46 +++++++++++++++++ .../gui/holder/InteractiveMenuHolder.java | 19 +++++++ .../paper/gui/holder/PaginatedMenuHolder.java | 46 +++++++++++++++++ .../paper/gui/util/MenuInteractionUtil.java | 20 +++----- .../engine/paper/gui/util/SafeUtil.java | 14 +++--- 13 files changed, 322 insertions(+), 101 deletions(-) create mode 100644 paper/src/main/java/gg/moonrise/engine/paper/gui/holder/ChestMenuHolder.java create mode 100644 paper/src/main/java/gg/moonrise/engine/paper/gui/holder/HopperMenuHolder.java create mode 100644 paper/src/main/java/gg/moonrise/engine/paper/gui/holder/InteractiveMenuHolder.java create mode 100644 paper/src/main/java/gg/moonrise/engine/paper/gui/holder/PaginatedMenuHolder.java diff --git a/README.md b/README.md index adfacde..76bef3f 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ public final class ExampleMenu extends ChestMenu { } ``` -Menus are cached per-player and handled by `PlayerInventoryController`. +Menus are backed by Bukkit `InventoryHolder` instances and handled by `PlayerInventoryController`. ## Messages and placeholders diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/ChestMenu.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/ChestMenu.java index 0f8cc1c..606ebf3 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/ChestMenu.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/ChestMenu.java @@ -3,13 +3,16 @@ import com.google.common.base.Preconditions; import gg.moonrise.engine.message.util.MiniMessageUtil; import gg.moonrise.engine.paper.gui.button.Button; +import gg.moonrise.engine.paper.gui.holder.ChestMenuHolder; import gg.moonrise.engine.paper.gui.util.MenuInteractionUtil; import gg.moonrise.engine.paper.gui.util.SafeUtil; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; @@ -20,8 +23,7 @@ /** * Represents a chest-based GUI menu for players. * This class manages button placement, click handling, and automatic refreshing - * of dynamic buttons. Menus are cached per-player and automatically cleaned up - * when closed. + * of dynamic buttons. Menus are routed through their inventory holder. */ public abstract class ChestMenu implements ChestInterface { @@ -32,7 +34,7 @@ public abstract class ChestMenu implements ChestInterface { private final Map buttonById = new HashMap<>(); private final Map refreshingButtons = new HashMap<>(); - protected InventoryView inventory; + protected Inventory inventory; private boolean cancelClicks = true; private final Player player; @@ -97,7 +99,7 @@ public void onOpen(Player player, InventoryOpenEvent event) { */ @Override public void onClose(Player player, InventoryCloseEvent event) { - UserInterface.invalidateFromCache(player.getUniqueId()); + invalidate(); } /** @@ -116,7 +118,7 @@ public void onClick(Player player, InventoryClickEvent event) { */ @Override public void open() { - MenuInteractionUtil.openMenu(player, this, this::refresh, () -> inventory); + MenuInteractionUtil.openMenu(player, this::refresh, () -> inventory); } /** @@ -135,7 +137,7 @@ public void invalidate() { @Override public void refresh() { if (inventory == null) - inventory = typeFromRows(rows).create(player, title); + inventory = createInventory(); clearInventory(inventory); @@ -252,13 +254,28 @@ public int getButtonCount() { return buttons.size(); } + /** + * Get the Inventory associated with this ChestMenu + * @return The Inventory + */ + @Override + public Inventory getInventory() { + return inventory; + } + /** * Get the InventoryView associated with this ChestMenu * @return The InventoryView */ @Override + @Deprecated(forRemoval = false) public InventoryView getView() { - return inventory; + if (inventory == null) return null; + + InventoryView view = player.getOpenInventory(); + if (!view.getTopInventory().equals(inventory)) return null; + + return view; } /** @@ -286,4 +303,11 @@ public boolean checkCancelClick(int slot) { return MenuInteractionUtil.checkCancelClick(inventory, buttonById, slot); } + private Inventory createInventory() { + ChestMenuHolder holder = new ChestMenuHolder(this); + Inventory created = Bukkit.createInventory(holder, rows * 9, title); + holder.setInventory(created); + return created; + } + } diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/HopperMenu.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/HopperMenu.java index 8176a1a..3d5b128 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/HopperMenu.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/HopperMenu.java @@ -2,16 +2,19 @@ import gg.moonrise.engine.message.util.MiniMessageUtil; import gg.moonrise.engine.paper.gui.button.Button; +import gg.moonrise.engine.paper.gui.holder.HopperMenuHolder; import gg.moonrise.engine.paper.gui.util.MenuInteractionUtil; import gg.moonrise.engine.paper.gui.util.SafeUtil; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; +import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.MenuType; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.Contract; @@ -20,8 +23,7 @@ /** * Represents a chest-based GUI menu for players. * This class manages button placement, click handling, and automatic refreshing - * of dynamic buttons. Menus are cached per-player and automatically cleaned up - * when closed. + * of dynamic buttons. Menus are routed through their inventory holder. */ public abstract class HopperMenu implements UserInterface { @@ -31,7 +33,7 @@ public abstract class HopperMenu implements UserInterface { private final Map buttonById = new HashMap<>(); private final Map refreshingButtons = new HashMap<>(); - protected InventoryView inventory; + protected Inventory inventory; private boolean cancelClicks = true; private final Player player; @@ -81,7 +83,7 @@ public void onOpen(Player player, InventoryOpenEvent event) { */ @Override public void onClose(Player player, InventoryCloseEvent event) { - UserInterface.invalidateFromCache(player.getUniqueId()); + invalidate(); } /** @@ -100,7 +102,7 @@ public void onClick(Player player, InventoryClickEvent event) { */ @Override public void open() { - MenuInteractionUtil.openMenu(player, this, this::refresh, () -> inventory); + MenuInteractionUtil.openMenu(player, this::refresh, () -> inventory); } /** @@ -119,7 +121,7 @@ public void invalidate() { @Override public void refresh() { if (inventory == null) - inventory = MenuType.HOPPER.create(player, title); + inventory = createInventory(); clearInventory(inventory); @@ -169,7 +171,7 @@ public void addButton(int slot, Button button) { * @param button The button to add */ public void addButton(Button button) { - for (int i = 0; i < inventory.getTopInventory().getSize(); i++) { + for (int i = 0; i < InventoryType.HOPPER.getDefaultSize(); i++) { if (buttons.containsKey(i)) continue; addButton(i, button); @@ -231,13 +233,28 @@ public int getButtonCount() { return buttons.size(); } + /** + * Get the Inventory associated with this menu + * @return The Inventory + */ + @Override + public Inventory getInventory() { + return inventory; + } + /** * Get the InventoryView associated with this menu * @return The InventoryView */ @Override + @Deprecated(forRemoval = false) public InventoryView getView() { - return inventory; + if (inventory == null) return null; + + InventoryView view = player.getOpenInventory(); + if (!view.getTopInventory().equals(inventory)) return null; + + return view; } /** @@ -265,4 +282,11 @@ public boolean checkCancelClick(int slot) { return MenuInteractionUtil.checkCancelClick(inventory, buttonById, slot); } + private Inventory createInventory() { + HopperMenuHolder holder = new HopperMenuHolder(this); + Inventory created = Bukkit.createInventory(holder, InventoryType.HOPPER, title); + holder.setInventory(created); + return created; + } + } diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/PaginatedMenu.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/PaginatedMenu.java index 9936335..a63324d 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/PaginatedMenu.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/PaginatedMenu.java @@ -3,14 +3,17 @@ import com.google.common.base.Preconditions; import gg.moonrise.engine.message.util.MiniMessageUtil; import gg.moonrise.engine.paper.gui.button.Button; +import gg.moonrise.engine.paper.gui.holder.PaginatedMenuHolder; import gg.moonrise.engine.paper.gui.util.MenuInteractionUtil; import gg.moonrise.engine.paper.gui.util.SafeUtil; import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; @@ -22,8 +25,7 @@ /** * Represents a chest-based GUI menu for players. * This class manages button placement, click handling, and automatic refreshing - * of dynamic buttons. Menus are cached per-player and automatically cleaned up - * when closed. + * of dynamic buttons. Menus are routed through their inventory holder. */ @Slf4j public abstract class PaginatedMenu implements ChestInterface { @@ -45,7 +47,7 @@ public abstract class PaginatedMenu implements ChestInterface { private final List contentSlots = new ArrayList<>(); private final Map refreshingContentButtons = new HashMap<>(); - protected InventoryView inventory; + protected Inventory inventory; private boolean cancelClicks = true; /** @@ -183,13 +185,13 @@ public void onOpen(Player player, InventoryOpenEvent event) { /** * Handle the event when a player closes the inventory. - * This method invalidates the menu from the cache to free up resources. + * This method invalidates the menu to free up resources. * @param player The player who closed the inventory. * @param event The InventoryCloseEvent triggered by the player closing the inventory. */ @Override public void onClose(Player player, InventoryCloseEvent event) { - UserInterface.invalidateFromCache(player.getUniqueId()); + invalidate(); } /** @@ -205,12 +207,11 @@ public void onClick(Player player, InventoryClickEvent event) { /** * Open the chest menu for the player. - * This method refreshes the menu, invalidates any existing cached menu for the player, - * caches the current menu, and opens the inventory for the player. + * This method refreshes the menu and opens the inventory for the player. */ @Override public void open() { - MenuInteractionUtil.openMenu(player, this, this::refresh, () -> inventory); + MenuInteractionUtil.openMenu(player, this::refresh, () -> inventory); } /** @@ -231,7 +232,7 @@ public void invalidate() { @Override public void refresh() { if (inventory == null) - inventory = typeFromRows(rows).create(player, title); + inventory = createInventory(); clearInventory(inventory); @@ -451,13 +452,28 @@ public void changePage(int page) { refresh(); } + /** + * Get the Inventory of this menu + * @return The Inventory + */ + @Override + public Inventory getInventory() { + return inventory; + } + /** * Get the InventoryView of this menu * @return The InventoryView */ @Override + @Deprecated(forRemoval = false) public InventoryView getView() { - return inventory; + if (inventory == null) return null; + + InventoryView view = player.getOpenInventory(); + if (!view.getTopInventory().equals(inventory)) return null; + + return view; } /** @@ -502,4 +518,11 @@ private void renderButtonToSlot(int slot, Button button) { button.onAddToInventory(inventory); } + private Inventory createInventory() { + PaginatedMenuHolder holder = new PaginatedMenuHolder(this); + Inventory created = Bukkit.createInventory(holder, rows * 9, title); + holder.setInventory(created); + return created; + } + } diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/UserInterface.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/UserInterface.java index 932e520..77c6ec4 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/UserInterface.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/UserInterface.java @@ -4,20 +4,15 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - /** * Represents the UserInterface interface. */ public interface UserInterface { - Map CACHE = new HashMap<>(); - /** * Called when a player opens an inventory. * @@ -52,20 +47,28 @@ public interface UserInterface { */ void refresh(); + /** + * Gets the Inventory associated with this UserInterface. + * + * @return The Inventory of this UserInterface. + */ + Inventory getInventory(); + /** * Gets the InventoryView associated with this UserInterface. * * @return The InventoryView of this UserInterface. */ + @Deprecated(forRemoval = false) InventoryView getView(); /** - * Clears the inventory view by setting all items in the top inventory to null. - * @param view The InventoryView to clear. + * Clears the inventory by setting all items to null. + * @param inventory The Inventory to clear. */ - default void clearInventory(InventoryView view) { - for (int i = 0; i < view.getTopInventory().getSize(); i++) { - view.getTopInventory().setItem(i, null); + default void clearInventory(Inventory inventory) { + for (int i = 0; i < inventory.getSize(); i++) { + inventory.setItem(i, null); } } @@ -86,11 +89,4 @@ default void clearInventory(InventoryView view) { * Invalidate the current state of the UserInterface. */ void invalidate(); - - static void invalidateFromCache(UUID uuid) { - UserInterface ui = CACHE.remove(uuid); - if (ui == null) return; - - ui.invalidate(); - } } diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/button/Button.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/button/Button.java index e872e18..9a9f7ba 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/button/Button.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/button/Button.java @@ -2,8 +2,10 @@ import lombok.RequiredArgsConstructor; import org.bukkit.NamespacedKey; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; @@ -26,15 +28,15 @@ public final class Button { private final long refreshIntervalTicks; private final boolean cancelClicks; private long lastRefreshTime = 0L; - private InventoryView boundingInventory; + private Inventory inventory; /** * Executes onAddToInventory. * @param inventory the inventory */ - public void onAddToInventory(InventoryView inventory) { - this.boundingInventory = inventory; + public void onAddToInventory(Inventory inventory) { + this.inventory = inventory; } /** @@ -75,10 +77,26 @@ public ItemStack item(Player player) { /** * Gets the inventory that this button is bound to. + * @return the inventory + */ + public Inventory inventory() { + return inventory; + } + + /** + * Gets the inventory view that this button is bound to. * @return the bounding inventory */ + @Deprecated(forRemoval = false) public InventoryView boundingInventory() { - return boundingInventory; + if (inventory == null) return null; + + for (HumanEntity viewer : inventory.getViewers()) { + InventoryView view = viewer.getOpenInventory(); + if (view.getTopInventory().equals(inventory)) return view; + } + + return null; } /** diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/controller/PlayerInventoryController.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/controller/PlayerInventoryController.java index 9cee8f6..14b19ff 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/controller/PlayerInventoryController.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/controller/PlayerInventoryController.java @@ -1,6 +1,7 @@ package gg.moonrise.engine.paper.gui.controller; import gg.moonrise.engine.paper.gui.UserInterface; +import gg.moonrise.engine.paper.gui.holder.InteractiveMenuHolder; import gg.moonrise.moss.spring.SpringComponent; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -9,9 +10,8 @@ import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryEvent; import org.bukkit.event.inventory.InventoryOpenEvent; -import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.InventoryHolder; /** * Represents the PlayerInventoryController class. @@ -29,10 +29,10 @@ public class PlayerInventoryController implements Listener { public void onOpen(InventoryOpenEvent event) { HumanEntity client = event.getPlayer(); - UserInterface ui = getUserInterface(client, event); - if (ui == null) return; + InteractiveMenuHolder holder = getMenuHolder(event.getInventory().getHolder()); + if (holder == null) return; - ui.onOpen((Player) client, event); + holder.onOpen((Player) client, event); } @@ -45,10 +45,10 @@ public void onOpen(InventoryOpenEvent event) { public void onClose(InventoryCloseEvent event) { HumanEntity client = event.getPlayer(); - UserInterface ui = getUserInterface(client, event); - if (ui == null) return; + InteractiveMenuHolder holder = getMenuHolder(event.getInventory().getHolder()); + if (holder == null) return; - ui.onClose((Player) client, event); + holder.onClose((Player) client, event); } @@ -61,39 +61,22 @@ public void onClose(InventoryCloseEvent event) { public void onClick(InventoryClickEvent event) { HumanEntity client = event.getWhoClicked(); - UserInterface ui = getUserInterface(client, event); - if (ui == null) return; + InteractiveMenuHolder holder = getMenuHolder(event.getInventory().getHolder()); + if (holder == null) return; + + UserInterface ui = holder.getMenu(); if (ui.cancelClicks() || ui.checkCancelClick(event.getSlot())) { event.setCancelled(true); event.setResult(Event.Result.DENY); } - ui.onClick((Player) client, event); + holder.onClick((Player) client, event); } - /** - * Retrieves the UserInterface associated with the given HumanEntity and InventoryEvent. - * - * @param client The HumanEntity (player) involved in the event. - * @param event The InventoryEvent to check against. - * @return The UserInterface if it exists and matches the event's inventory view; otherwise, null. - */ - private UserInterface getUserInterface(HumanEntity client, InventoryEvent event) { - UserInterface ui = UserInterface.CACHE.get(client.getUniqueId()); - if (ui == null) return null; - if (ui.getView() == null) { - UserInterface.invalidateFromCache(client.getUniqueId()); - return null; - } - - InventoryView inventory = event.getView(); - if (!ui.getView().equals(inventory)) { - UserInterface.invalidateFromCache(client.getUniqueId()); - return null; - } - - return ui; + private InteractiveMenuHolder getMenuHolder(InventoryHolder holder) { + if (!(holder instanceof InteractiveMenuHolder menuHolder)) return null; + return menuHolder; } } diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/ChestMenuHolder.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/ChestMenuHolder.java new file mode 100644 index 0000000..5c67686 --- /dev/null +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/ChestMenuHolder.java @@ -0,0 +1,46 @@ +package gg.moonrise.engine.paper.gui.holder; + +import gg.moonrise.engine.paper.gui.ChestMenu; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; + +@RequiredArgsConstructor +public final class ChestMenuHolder implements InteractiveMenuHolder { + + private final ChestMenu menu; + private Inventory inventory; + + @Override + public void onOpen(Player player, InventoryOpenEvent event) { + menu.onOpen(player, event); + } + + @Override + public void onClose(Player player, InventoryCloseEvent event) { + menu.onClose(player, event); + } + + @Override + public void onClick(Player player, InventoryClickEvent event) { + menu.onClick(player, event); + } + + @Override + public ChestMenu getMenu() { + return menu; + } + + @Override + public @NotNull Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } +} diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/HopperMenuHolder.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/HopperMenuHolder.java new file mode 100644 index 0000000..f78fc18 --- /dev/null +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/HopperMenuHolder.java @@ -0,0 +1,46 @@ +package gg.moonrise.engine.paper.gui.holder; + +import gg.moonrise.engine.paper.gui.HopperMenu; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; + +@RequiredArgsConstructor +public final class HopperMenuHolder implements InteractiveMenuHolder { + + private final HopperMenu menu; + private Inventory inventory; + + @Override + public void onOpen(Player player, InventoryOpenEvent event) { + menu.onOpen(player, event); + } + + @Override + public void onClose(Player player, InventoryCloseEvent event) { + menu.onClose(player, event); + } + + @Override + public void onClick(Player player, InventoryClickEvent event) { + menu.onClick(player, event); + } + + @Override + public HopperMenu getMenu() { + return menu; + } + + @Override + public @NotNull Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } +} diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/InteractiveMenuHolder.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/InteractiveMenuHolder.java new file mode 100644 index 0000000..2c2d3dc --- /dev/null +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/InteractiveMenuHolder.java @@ -0,0 +1,19 @@ +package gg.moonrise.engine.paper.gui.holder; + +import gg.moonrise.engine.paper.gui.UserInterface; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.InventoryHolder; + +public interface InteractiveMenuHolder extends InventoryHolder { + + void onOpen(Player player, InventoryOpenEvent event); + + void onClose(Player player, InventoryCloseEvent event); + + void onClick(Player player, InventoryClickEvent event); + + T getMenu(); +} diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/PaginatedMenuHolder.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/PaginatedMenuHolder.java new file mode 100644 index 0000000..bc266da --- /dev/null +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/holder/PaginatedMenuHolder.java @@ -0,0 +1,46 @@ +package gg.moonrise.engine.paper.gui.holder; + +import gg.moonrise.engine.paper.gui.PaginatedMenu; +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; + +@RequiredArgsConstructor +public final class PaginatedMenuHolder implements InteractiveMenuHolder { + + private final PaginatedMenu menu; + private Inventory inventory; + + @Override + public void onOpen(Player player, InventoryOpenEvent event) { + menu.onOpen(player, event); + } + + @Override + public void onClose(Player player, InventoryCloseEvent event) { + menu.onClose(player, event); + } + + @Override + public void onClick(Player player, InventoryClickEvent event) { + menu.onClick(player, event); + } + + @Override + public PaginatedMenu getMenu() { + return menu; + } + + @Override + public @NotNull Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } +} diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/util/MenuInteractionUtil.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/util/MenuInteractionUtil.java index 5eeddc5..dcda2d5 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/util/MenuInteractionUtil.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/util/MenuInteractionUtil.java @@ -1,12 +1,11 @@ package gg.moonrise.engine.paper.gui.util; -import gg.moonrise.engine.paper.gui.UserInterface; import gg.moonrise.engine.paper.gui.button.Button; import gg.moonrise.engine.paper.scheduler.Scheduler; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; @@ -45,25 +44,22 @@ public static void processClick( public static void openMenu( Player player, - UserInterface ui, Runnable refreshAction, - Supplier inventorySupplier + Supplier inventorySupplier ) { refreshAction.run(); - UserInterface.invalidateFromCache(player.getUniqueId()); - UserInterface.CACHE.put(player.getUniqueId(), ui); - Scheduler.entity(player).execute(() -> { - InventoryView view = inventorySupplier.get(); - if (view != null) view.open(); + Inventory inventory = inventorySupplier.get(); + if (inventory != null) player.openInventory(inventory); }, 1); } - public static void refreshButton(InventoryView inventory, int slot, Button button) { + public static void refreshButton(Inventory inventory, int slot, Button button) { if (inventory == null) return; + if (inventory.getViewers().isEmpty()) return; - Player player = (Player) inventory.getPlayer(); + Player player = (Player) inventory.getViewers().getFirst(); ItemStack stack = button.item(player); if (stack == null || stack.getType().isAir()) return; @@ -88,7 +84,7 @@ public static void addButton( refreshingButtons.put(slot, button); } - public static boolean checkCancelClick(InventoryView inventory, Map buttonById, int slot) { + public static boolean checkCancelClick(Inventory inventory, Map buttonById, int slot) { if (inventory == null) return false; ItemStack item = inventory.getItem(slot); diff --git a/paper/src/main/java/gg/moonrise/engine/paper/gui/util/SafeUtil.java b/paper/src/main/java/gg/moonrise/engine/paper/gui/util/SafeUtil.java index abfe706..456291d 100644 --- a/paper/src/main/java/gg/moonrise/engine/paper/gui/util/SafeUtil.java +++ b/paper/src/main/java/gg/moonrise/engine/paper/gui/util/SafeUtil.java @@ -1,7 +1,7 @@ package gg.moonrise.engine.paper.gui.util; import lombok.extern.slf4j.Slf4j; -import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; /** @@ -12,18 +12,18 @@ public class SafeUtil { /** * Sets an item in the inventory at the specified slot safely. - * @param view the inventory to set the item in + * @param inventory the inventory to set the item in * @param slot the slot to set the item in * @param item the item to set */ - public static void setInventoryItem(InventoryView view, int slot, ItemStack item) { - if (view == null) return; - if (slot < 0 || slot >= view.getTopInventory().getSize()) { - log.warn("Invalid inventory slot {} for inventory size {}", slot, view.getTopInventory().getSize()); + public static void setInventoryItem(Inventory inventory, int slot, ItemStack item) { + if (inventory == null) return; + if (slot < 0 || slot >= inventory.getSize()) { + log.warn("Invalid inventory slot {} for inventory size {}", slot, inventory.getSize()); return; } - view.getTopInventory().setItem(slot, item); + inventory.setItem(slot, item); } } From b6912d27793ac7136cdc37251bfaca5beedbd052 Mon Sep 17 00:00:00 2001 From: Eric <60104846+ericlmao@users.noreply.github.com> Date: Fri, 15 May 2026 02:03:20 -0300 Subject: [PATCH 2/5] chore: bump plugin engine version to 1.1.2 --- README.md | 6 +++--- common/build.gradle.kts | 2 +- paper/build.gradle.kts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 76bef3f..85d2330 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ Then add dependencies: ```kotlin dependencies { - implementation("gg.moonrise.engine:plugin-engine-paper:1.1.1") - // or: implementation("gg.moonrise.engine:plugin-engine-common:1.1.1") + implementation("gg.moonrise.engine:plugin-engine-paper:1.1.2") + // or: implementation("gg.moonrise.engine:plugin-engine-common:1.1.2") } ``` @@ -61,7 +61,7 @@ dependencies { gg.moonrise.engine plugin-engine-paper - 1.1.1 + 1.1.2 ``` diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 9dca211..60ad0fa 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -6,7 +6,7 @@ plugins { var id = "plugin-engine-common" var domain = "gg.moonrise.engine" -var apiVersion = "1.1.1" +var apiVersion = "1.1.2" repositories { mavenCentral() diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index dccac76..f1fb7e8 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -6,7 +6,7 @@ plugins { var id = "plugin-engine-paper" var domain = "gg.moonrise.engine" -var apiVersion = "1.1.1" +var apiVersion = "1.1.2" repositories { mavenCentral() From 1814b8beaba45cb938d761ba191fa413b7400b0d Mon Sep 17 00:00:00 2001 From: Eric <60104846+ericlmao@users.noreply.github.com> Date: Fri, 15 May 2026 02:18:12 -0300 Subject: [PATCH 3/5] chore: add paper test dependencies and junit platform --- paper/build.gradle.kts | 12 ++++ .../engine/paper/cooldown/CooldownsTest.java | 39 ++++++++++ .../moonrise/engine/paper/data/AABBTest.java | 46 ++++++++++++ .../engine/paper/gui/ChestMenuTest.java | 72 +++++++++++++++++++ .../engine/paper/gui/HopperMenuTest.java | 55 ++++++++++++++ .../engine/paper/gui/button/ButtonTest.java | 56 +++++++++++++++ .../engine/paper/item/ItemBuilderTest.java | 66 +++++++++++++++++ .../engine/paper/support/MockBukkitTest.java | 21 ++++++ 8 files changed, 367 insertions(+) create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/cooldown/CooldownsTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/data/AABBTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/gui/ChestMenuTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/gui/HopperMenuTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/gui/button/ButtonTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/item/ItemBuilderTest.java create mode 100644 paper/src/test/java/gg/moonrise/engine/paper/support/MockBukkitTest.java diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index dccac76..11f4bcd 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -39,6 +39,14 @@ dependencies { // Lombok compileOnly("org.projectlombok:lombok:1.18.32") annotationProcessor("org.projectlombok:lombok:1.18.32") + + // Testing + testImplementation(platform("org.junit:junit-bom:5.12.2")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.76.1") + testImplementation("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT") + testImplementation("net.kyori:adventure-text-serializer-plain:4.26.1") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } java { @@ -57,6 +65,10 @@ tasks.shadowJar { archiveClassifier.set("") } +tasks.test { + useJUnitPlatform() +} + publishing { publications { create("mavenJava") { diff --git a/paper/src/test/java/gg/moonrise/engine/paper/cooldown/CooldownsTest.java b/paper/src/test/java/gg/moonrise/engine/paper/cooldown/CooldownsTest.java new file mode 100644 index 0000000..79dcd1d --- /dev/null +++ b/paper/src/test/java/gg/moonrise/engine/paper/cooldown/CooldownsTest.java @@ -0,0 +1,39 @@ +package gg.moonrise.engine.paper.cooldown; + +import gg.moonrise.engine.paper.support.MockBukkitTest; +import net.kyori.adventure.text.Component; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerQuitEvent.QuitReason; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; + +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CooldownsTest extends MockBukkitTest { + + @Test + void cooldownsExpireByTime() { + PlayerMock player = server.addPlayer(); + String key = "action"; + + Cooldowns.addCooldown(player.getUniqueId(), key, Duration.ofSeconds(1)); + assertTrue(Cooldowns.isOnCooldown(player.getUniqueId(), key)); + + Cooldowns.addCooldown(player.getUniqueId(), key, -1L); + assertFalse(Cooldowns.isOnCooldown(player.getUniqueId(), key)); + } + + @Test + void quitClearsPlayerCooldowns() { + PlayerMock player = server.addPlayer(); + String key = "quit-action"; + Cooldowns.addCooldown(player.getUniqueId(), key, Duration.ofMinutes(1)); + + new Cooldowns().onQuit(new PlayerQuitEvent(player, Component.empty(), QuitReason.DISCONNECTED)); + + assertFalse(Cooldowns.isOnCooldown(player.getUniqueId(), key)); + } +} diff --git a/paper/src/test/java/gg/moonrise/engine/paper/data/AABBTest.java b/paper/src/test/java/gg/moonrise/engine/paper/data/AABBTest.java new file mode 100644 index 0000000..06056c0 --- /dev/null +++ b/paper/src/test/java/gg/moonrise/engine/paper/data/AABBTest.java @@ -0,0 +1,46 @@ +package gg.moonrise.engine.paper.data; + +import org.bukkit.util.Vector; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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; + +class AABBTest { + + @Test + void convertsToVectorsAndArrays() { + AABB box = new AABB(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + + assertEquals(new Vector(1.0, 2.0, 3.0), box.minVector()); + assertEquals(new Vector(4.0, 5.0, 6.0), box.maxVector()); + assertEquals(box.minVector(), box.toVectors().first()); + assertEquals(box.maxVector(), box.toVectors().second()); + assertArrayEquals(new double[]{1.0, 2.0, 3.0}, box.minArray()); + assertArrayEquals(new double[]{4.0, 5.0, 6.0}, box.maxArray()); + assertArrayEquals(new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}, box.toArrayPair()); + } + + @Test + void insideCheckIncludesBoundaries() { + AABB box = new AABB(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + + assertTrue(box.isInside(1.0, 2.0, 3.0)); + assertTrue(box.isInside(new Vector(4.0, 5.0, 6.0))); + assertTrue(box.isInside(2.5, 3.5, 4.5)); + assertFalse(box.isInside(0.99, 3.5, 4.5)); + assertFalse(box.isInside(2.5, 5.01, 4.5)); + } + + @Test + void createsFromVectorsAndArrayPair() { + AABB fromVectors = AABB.fromVectors(new Vector(1.0, 2.0, 3.0), new Vector(4.0, 5.0, 6.0)); + AABB fromArray = AABB.fromArrayPair(new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}); + + assertEquals(fromVectors, fromArray); + assertThrows(IllegalArgumentException.class, () -> AABB.fromArrayPair(new double[]{1.0, 2.0})); + } +} diff --git a/paper/src/test/java/gg/moonrise/engine/paper/gui/ChestMenuTest.java b/paper/src/test/java/gg/moonrise/engine/paper/gui/ChestMenuTest.java new file mode 100644 index 0000000..646138c --- /dev/null +++ b/paper/src/test/java/gg/moonrise/engine/paper/gui/ChestMenuTest.java @@ -0,0 +1,72 @@ +package gg.moonrise.engine.paper.gui; + +import gg.moonrise.engine.paper.gui.button.Button; +import gg.moonrise.engine.paper.support.MockBukkitTest; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ChestMenuTest extends MockBukkitTest { + + @Test + void addRemoveAndSlotBoundsWork() { + PlayerMock player = server.addPlayer(); + TestChestMenu menu = new TestChestMenu(player, 1); + Button button = button(Material.DIAMOND); + + menu.addButton(0, button); + + assertTrue(menu.hasButton(0)); + assertEquals(1, menu.getButtonCount()); + assertEquals(button, menu.removeButton(0)); + assertFalse(menu.hasButton(0)); + assertNull(button.boundingInventory()); + assertThrows(IllegalArgumentException.class, () -> menu.addButton(9, button)); + } + + @Test + void refreshWritesButtonItemsAndPersistentIds() { + PlayerMock player = server.addPlayer(); + TestChestMenu menu = new TestChestMenu(player, 1); + Button button = Button.builder() + .item(viewer -> new ItemStack(Material.EMERALD)) + .refresh(10L) + .build(); + + menu.addButton(4, button); + menu.refresh(); + + ItemStack rendered = menu.getView().getTopInventory().getItem(4); + assertNotNull(rendered); + assertEquals(Material.EMERALD, rendered.getType()); + assertEquals(button.uuid().toString(), rendered.getPersistentDataContainer().get(Button.KEY, PersistentDataType.STRING)); + assertEquals(menu.getView(), button.boundingInventory()); + assertEquals(1, menu.getRefreshingButtons().size()); + assertTrue(menu.checkCancelClick(4)); + } + + private static Button button(Material material) { + return Button.builder() + .item(player -> new ItemStack(material)) + .build(); + } + + private static final class TestChestMenu extends ChestMenu { + + private TestChestMenu(Player player, int rows) { + super(player, "Test", rows); + } + } +} diff --git a/paper/src/test/java/gg/moonrise/engine/paper/gui/HopperMenuTest.java b/paper/src/test/java/gg/moonrise/engine/paper/gui/HopperMenuTest.java new file mode 100644 index 0000000..d93eddc --- /dev/null +++ b/paper/src/test/java/gg/moonrise/engine/paper/gui/HopperMenuTest.java @@ -0,0 +1,55 @@ +package gg.moonrise.engine.paper.gui; + +import gg.moonrise.engine.paper.gui.button.Button; +import gg.moonrise.engine.paper.support.MockBukkitTest; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HopperMenuTest extends MockBukkitTest { + + @Test + void refreshWritesButtonItemsAndPersistentIds() { + PlayerMock player = server.addPlayer(); + TestHopperMenu menu = new TestHopperMenu(player); + Button button = Button.builder() + .item(viewer -> new ItemStack(Material.HOPPER)) + .refresh(5L) + .build(); + + menu.addButton(2, button); + menu.refresh(); + + ItemStack rendered = menu.getView().getTopInventory().getItem(2); + assertNotNull(rendered); + assertEquals(Material.HOPPER, rendered.getType()); + assertEquals(button.uuid().toString(), rendered.getPersistentDataContainer().get(Button.KEY, PersistentDataType.STRING)); + assertEquals(menu.getView(), button.boundingInventory()); + assertEquals(1, menu.getRefreshingButtons().size()); + assertTrue(menu.checkCancelClick(2)); + } + + @Test + void cancelClicksCanBeDisabled() { + TestHopperMenu menu = new TestHopperMenu(server.addPlayer()); + + assertTrue(menu.cancelClicks()); + menu.cancelClicks(false); + assertFalse(menu.cancelClicks()); + } + + private static final class TestHopperMenu extends HopperMenu { + + private TestHopperMenu(Player player) { + super(player, "Test"); + } + } +} diff --git a/paper/src/test/java/gg/moonrise/engine/paper/gui/button/ButtonTest.java b/paper/src/test/java/gg/moonrise/engine/paper/gui/button/ButtonTest.java new file mode 100644 index 0000000..4fd4ea1 --- /dev/null +++ b/paper/src/test/java/gg/moonrise/engine/paper/gui/button/ButtonTest.java @@ -0,0 +1,56 @@ +package gg.moonrise.engine.paper.gui.button; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ButtonTest { + + @Test + void builderRequiresDisplayItem() { + assertThrows(IllegalStateException.class, () -> Button.builder().build()); + } + + @Test + void builderStoresDefaultsAndRefreshInterval() { + Button button = Button.builder() + .item(player -> new ItemStack(Material.DIAMOND)) + .refresh(20L) + .build(); + + assertNotNull(button.uuid()); + assertTrue(button.cancelClick()); + assertEquals(20L, button.refreshIntervalTicks()); + button.setLastRefreshTime(40L); + assertEquals(40L, button.lastRefreshTime()); + } + + @Test + void customCancelAndClickActionAreApplied() { + AtomicBoolean clicked = new AtomicBoolean(false); + AtomicReference