From 73f2be103f32807000a26529dbff2f9e50ea1dfb Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Fri, 20 Mar 2026 20:46:06 +0100
Subject: [PATCH 1/8] feat: implement pagination management system with
PaginationManager and related button classes to allow a single button to
paginate
---
.../maxlego08/menu/api/InventoryManager.java | 26 ++++
.../api/button/GenericPaginateButton.java | 120 ++++++++++++++++++
.../api/button/GenericPaginationButton.java | 62 +++++++++
.../api/pagination/PaginationManager.java | 102 +++++++++++++++
.../menu/api/pagination/PaginationState.java | 46 +++++++
.../fr/maxlego08/menu/ZInventoryManager.java | 10 ++
.../menu/button/buttons/PaginationButton.java | 42 ++++++
.../button/buttons/PaginationNextButton.java | 37 ++++++
.../buttons/PaginationPreviousButton.java | 33 +++++
.../loader/PaginationNextButtonLoader.java | 28 ++++
.../PaginationPreviousButtonLoader.java | 28 ++++
.../menu/inventory/VInventoryManager.java | 1 +
.../menu/pagination/ZPaginationManager.java | 69 ++++++++++
13 files changed, 604 insertions(+)
create mode 100644 API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
create mode 100644 API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
create mode 100644 API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
create mode 100644 API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
create mode 100644 src/main/java/fr/maxlego08/menu/button/buttons/PaginationButton.java
create mode 100644 src/main/java/fr/maxlego08/menu/button/buttons/PaginationNextButton.java
create mode 100644 src/main/java/fr/maxlego08/menu/button/buttons/PaginationPreviousButton.java
create mode 100644 src/main/java/fr/maxlego08/menu/button/loader/PaginationNextButtonLoader.java
create mode 100644 src/main/java/fr/maxlego08/menu/button/loader/PaginationPreviousButtonLoader.java
create mode 100644 src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
diff --git a/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java b/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java
index 0b536a6d..91b3ea88 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java
@@ -3,6 +3,7 @@
import com.tcoded.folialib.impl.PlatformScheduler;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.ButtonOption;
+import fr.maxlego08.menu.api.button.GenericPaginationButton;
import fr.maxlego08.menu.api.checker.InventoryRequirementType;
import fr.maxlego08.menu.api.enchantment.Enchantments;
import fr.maxlego08.menu.api.engine.InventoryEngine;
@@ -12,6 +13,7 @@
import fr.maxlego08.menu.api.font.FontImage;
import fr.maxlego08.menu.api.itemstack.ItemStackSimilar;
import fr.maxlego08.menu.api.loader.MaterialLoader;
+import fr.maxlego08.menu.api.pagination.PaginationManager;
import fr.maxlego08.menu.api.utils.Message;
import fr.maxlego08.menu.api.utils.MetaUpdater;
import fr.maxlego08.menu.api.utils.Placeholders;
@@ -595,4 +597,28 @@ public interface InventoryManager extends Listener {
*
*/
MenuItemStack loadItemStack(File file, String path, Map map);
+
+ /**
+ * Provides access to the pagination manager for handling paginated content in inventories.
+ *
+ * The PaginationManager is responsible for managing multi-page inventory displays,
+ * allowing buttons to paginate through large collections of items or data. It tracks
+ * the current page for each player and manages navigation between pages.
+ *
+ * This is typically used in conjunction with {@link GenericPaginationButton} or
+ * other paginated button implementations to display collections that exceed a single
+ * inventory page's capacity.
+ *
+ * Example usage:
+ * {@code
+ * PaginationManager manager = inventoryManager.getPaginationManager();
+ * // Use manager to control pagination state
+ * }
+ *
+ * @return An instance of {@link PaginationManager} for managing pagination state in inventories.
+ * @see GenericPaginationButton
+ * @see PaginationManager
+ */
+ PaginationManager getPaginationManager();
+
}
\ No newline at end of file
diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
new file mode 100644
index 00000000..d1569765
--- /dev/null
+++ b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
@@ -0,0 +1,120 @@
+package fr.maxlego08.menu.api.button;
+
+import fr.maxlego08.menu.api.pagination.PaginationManager;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class GenericPaginateButton extends PaginateButton {
+
+ @NotNull
+ public abstract String getContextId(@NotNull Player player);
+
+
+ @NotNull
+ public abstract PaginationManager getPaginationManager();
+
+ /**
+ * Gets the current page for this button (0-based index).
+ *
+ * @param player the player
+ * @return the current page
+ */
+ public final int getCurrentPage(@NotNull Player player) {
+ return getPaginationManager().getPage(player.getUniqueId(), getContextId(player));
+ }
+
+ /**
+ * Gets the current page (1-based index) for UI purposes.
+ *
+ * @param player the player
+ * @return the current page (1-based)
+ */
+ public final int getCurrentPageOneIndexed(@NotNull Player player) {
+ return getCurrentPage(player) + 1;
+ }
+
+ /**
+ * Sets the current page for this button.
+ *
+ * @param player the player
+ * @param page the page to set (0-based index)
+ */
+ public final void setCurrentPage(@NotNull Player player, int page) {
+ getPaginationManager().setPage(player.getUniqueId(), getContextId(player), page);
+ }
+
+ /**
+ * Advances to the next page if available.
+ *
+ * @param player the player
+ * @return true if advanced, false if already at the last page
+ */
+ public final boolean nextPage(@NotNull Player player) {
+ int maxPage = getMaxPage(player);
+ int currentPage = getCurrentPage(player);
+ if (currentPage < maxPage) {
+ getPaginationManager().nextPage(player.getUniqueId(), getContextId(player));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Goes to the previous page if available.
+ *
+ * @param player the player
+ * @return true if went back, false if already at the first page
+ */
+ public final boolean previousPage(@NotNull Player player) {
+ int currentPage = getCurrentPage(player);
+ if (currentPage > 0) {
+ getPaginationManager().previousPage(player.getUniqueId(), getContextId(player));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Resets pagination to the first page.
+ *
+ * @param player the player
+ */
+ public final void resetPagination(@NotNull Player player) {
+ getPaginationManager().reset(player.getUniqueId(), getContextId(player));
+ }
+
+ /**
+ * Calculates the maximum page number (0-based index).
+ *
+ * @param player the player
+ * @return the maximum page
+ */
+ public final int getMaxPage(@NotNull Player player) {
+ int totalSize = getPaginationSize(player);
+ int pageSize = getSlots().size();
+ if (pageSize <= 0) return 0;
+ return Math.max(0, (int) Math.ceil((double) totalSize / pageSize) - 1);
+ }
+
+ /**
+ * Checks if there's a next page available.
+ *
+ * @param player the player
+ * @return true if there's a next page
+ */
+ public final boolean hasNextPage(@NotNull Player player) {
+ return getCurrentPage(player) < getMaxPage(player);
+ }
+
+ /**
+ * Checks if there's a previous page available.
+ *
+ * @param player the player
+ * @return true if there's a previous page
+ */
+ public final boolean hasPreviousPage(@NotNull Player player) {
+ return getCurrentPage(player) > 0;
+ }
+
+}
+
diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
new file mode 100644
index 00000000..1962b5e5
--- /dev/null
+++ b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
@@ -0,0 +1,62 @@
+package fr.maxlego08.menu.api.button;
+
+import fr.maxlego08.menu.api.engine.InventoryEngine;
+import fr.maxlego08.menu.api.engine.Pagination;
+import fr.maxlego08.menu.api.utils.Placeholders;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public abstract class GenericPaginationButton extends GenericPaginateButton {
+
+ /**
+ * Gets the list of elements to paginate.
+ *
+ * @param player the player
+ * @return the list of elements
+ */
+ @NotNull
+ protected abstract List getElements(@NotNull Player player);
+
+ /**
+ * Renders a single element at the given slot.
+ *
+ * @param player the player
+ * @param inventory the inventory engine
+ * @param slot the inventory slot
+ * @param element the element to render
+ * @param placeholders the placeholders
+ */
+ protected abstract void renderElement(
+ @NotNull Player player,
+ @NotNull InventoryEngine inventory,
+ int slot,
+ @NotNull T element,
+ @NotNull Placeholders placeholders);
+
+ @Override
+ public final void onRender(@NotNull Player player, @NotNull InventoryEngine inventory) {
+ List elements = getElements(player);
+ int pageSize = getSlots().size();
+ int currentPage = getCurrentPageOneIndexed(player);
+
+ Pagination pagination = new Pagination<>();
+ List paginatedElements = pagination.paginate(elements, pageSize, currentPage);
+
+ int slotIndex = 0;
+ for (Integer slot : getSlots()) {
+ if (slotIndex >= paginatedElements.size()) break;
+
+ T element = paginatedElements.get(slotIndex);
+ Placeholders placeholders = new Placeholders();
+ placeholders.register("page", String.valueOf(getCurrentPageOneIndexed(player)));
+ placeholders.register("max_page", String.valueOf(getMaxPage(player) + 1));
+
+ renderElement(player, inventory, slot, element, placeholders);
+ slotIndex++;
+ }
+ }
+}
+
+
diff --git a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
new file mode 100644
index 00000000..dfbefc78
--- /dev/null
+++ b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
@@ -0,0 +1,102 @@
+package fr.maxlego08.menu.api.pagination;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.UUID;
+
+public interface PaginationManager {
+
+ /**
+ * Gets or creates a pagination state for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier (e.g., "job:miner", "reward:quest_1")
+ * @return the pagination state
+ */
+ @NotNull PaginationState getOrCreateState(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Gets the current page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @return the current page (0-based index), or 0 if not found
+ */
+ int getPage(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Sets the current page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @param page the page to set (0-based index)
+ */
+ void setPage(@NotNull UUID playerId, @NotNull String contextId, int page);
+
+ /**
+ * Increments the page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ */
+ void nextPage(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Decrements the page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ */
+ void previousPage(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Resets the pagination to page 0 for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ */
+ void reset(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Gets the pagination state without creating it if it doesn't exist.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @return the pagination state, or null if not found
+ */
+ @Nullable PaginationState getState(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Returns whether a pagination state exists for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @return true if a state exists
+ */
+ default boolean hasState(@NotNull UUID playerId, @NotNull String contextId) {
+ return getState(playerId, contextId) != null;
+ }
+
+ /**
+ * Removes a pagination state for a player and context.
+ * Useful when the player logs out or the context is no longer needed.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ */
+ void removeState(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Removes all pagination states for a player.
+ * Useful on player logout.
+ *
+ * @param playerId the player's UUID
+ */
+ void removePlayerStates(@NotNull UUID playerId);
+
+ /**
+ * Clears all pagination states.
+ */
+ void clearAll();
+}
\ No newline at end of file
diff --git a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
new file mode 100644
index 00000000..818609fe
--- /dev/null
+++ b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
@@ -0,0 +1,46 @@
+package fr.maxlego08.menu.api.pagination;
+
+public class PaginationState {
+ private int currentPage;
+
+ public PaginationState() {
+ this(0);
+ }
+
+ public PaginationState(int page) {
+ this.currentPage = Math.max(0, page);
+ }
+
+ /**
+ * @return the current page (0-based index)
+ */
+ public int getCurrentPage() {
+ return currentPage;
+ }
+
+ /**
+ * @return the current page (1-based index for UI purposes)
+ */
+ public int getCurrentPageOneIndexed() {
+ return this.currentPage + 1;
+ }
+
+ public void setCurrentPage(int page) {
+ this.currentPage = Math.max(0, page);
+ }
+
+ public void nextPage() {
+ this.currentPage++;
+ }
+
+ public void previousPage() {
+ if (this.currentPage > 0) {
+ this.currentPage--;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PaginationState{currentPage=%d}", currentPage);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
index 54710f90..30731dd0 100644
--- a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
+++ b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
@@ -18,6 +18,7 @@
import fr.maxlego08.menu.api.itemstack.ItemStackSimilar;
import fr.maxlego08.menu.api.loader.MaterialLoader;
import fr.maxlego08.menu.api.loader.NoneLoader;
+import fr.maxlego08.menu.api.pagination.PaginationManager;
import fr.maxlego08.menu.api.utils.*;
import fr.maxlego08.menu.button.buttons.ZNoneButton;
import fr.maxlego08.menu.button.loader.*;
@@ -43,6 +44,7 @@
import fr.maxlego08.menu.loader.actions.*;
import fr.maxlego08.menu.loader.deluxemenu.InventoryDeluxeMenuLoader;
import fr.maxlego08.menu.loader.permissible.*;
+import fr.maxlego08.menu.pagination.ZPaginationManager;
import fr.maxlego08.menu.requirement.checker.InventoryRequirementChecker;
import fr.maxlego08.menu.zcore.logger.Logger;
import fr.maxlego08.menu.zcore.logger.Logger.LogType;
@@ -75,6 +77,7 @@
import java.util.stream.Stream;
public class ZInventoryManager extends ZUtils implements InventoryManager {
+ private final PaginationManager paginationManager = new ZPaginationManager();
private final Map> inventories = new HashMap<>();
private final Map>> buttonOptions = new HashMap<>();
@@ -134,6 +137,11 @@ public MenuItemStack loadItemStack(File file, String path, Map m
return new MenuItemStackLoader(this).load(configuration, "item", file);
}
+ @Override
+ public PaginationManager getPaginationManager() {
+ return this.paginationManager;
+ }
+
@Override
public Inventory loadInventory(Plugin plugin, File file) throws InventoryException {
return this.loadInventory(plugin, file, ZInventory.class);
@@ -420,6 +428,8 @@ public void loadButtons() {
buttonManager.register(new MainMenuLoader(this.plugin));
buttonManager.register(new JumpLoader(this.plugin));
buttonManager.register(new SwitchLoader(this.plugin));
+ buttonManager.register(new PaginationNextButtonLoader(this.plugin));
+ buttonManager.register(new PaginationPreviousButtonLoader(this.plugin));
// Loading Button Dialog
// Register Button Dialog Body
diff --git a/src/main/java/fr/maxlego08/menu/button/buttons/PaginationButton.java b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationButton.java
new file mode 100644
index 00000000..dc416e2b
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationButton.java
@@ -0,0 +1,42 @@
+package fr.maxlego08.menu.button.buttons;
+
+import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.button.Button;
+import fr.maxlego08.menu.api.button.GenericPaginateButton;
+import fr.maxlego08.menu.api.engine.InventoryEngine;
+import fr.maxlego08.menu.api.pagination.PaginationManager;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class PaginationButton extends Button {
+
+ protected final MenuPlugin plugin;
+ protected final PaginationManager manager;
+ protected final String contextId;
+
+ public PaginationButton(@NotNull MenuPlugin plugin, @NotNull String contextId) {
+ this.plugin = plugin;
+ this.manager = plugin.getInventoryManager().getPaginationManager();
+ this.contextId = contextId;
+ }
+
+ @Nullable
+ protected GenericPaginateButton findPaginateButton(@NotNull InventoryEngine inventory, @NotNull Player player) {
+ for (Button button : inventory.getButtons()) {
+ if (button instanceof GenericPaginateButton paginate && paginate.getContextId(player).equals(this.contextId)) {
+ return paginate;
+ }
+ }
+ return null;
+ }
+
+ protected void refreshInventory(@NotNull Player player) {
+ this.plugin.getInventoryManager().updateInventory(player, this.plugin);
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/button/buttons/PaginationNextButton.java b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationNextButton.java
new file mode 100644
index 00000000..c480f55d
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationNextButton.java
@@ -0,0 +1,37 @@
+package fr.maxlego08.menu.button.buttons;
+
+import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.button.GenericPaginateButton;
+import fr.maxlego08.menu.api.engine.InventoryEngine;
+import fr.maxlego08.menu.api.utils.Placeholders;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.jetbrains.annotations.NotNull;
+
+public class PaginationNextButton extends PaginationButton {
+
+ public PaginationNextButton(@NotNull MenuPlugin plugin, @NotNull String contextId) {
+ super(plugin, contextId);
+ }
+
+ protected void onNextPage(@NotNull Player player, @NotNull InventoryEngine inventory) {
+ refreshInventory(player);
+ }
+
+ protected void onCannotNextPage(@NotNull Player player, @NotNull InventoryEngine inventory) {
+ }
+
+ @Override
+ public void onClick(@NotNull Player player, @NotNull InventoryClickEvent event, @NotNull InventoryEngine inventory, int slot, @NotNull Placeholders placeholders) {
+ GenericPaginateButton paginateButton = findPaginateButton(inventory, player);
+ if (paginateButton == null) return;
+
+ int currentPage = this.manager.getPage(player.getUniqueId(), this.contextId);
+ if (currentPage < paginateButton.getMaxPage(player)) {
+ this.manager.nextPage(player.getUniqueId(), this.contextId);
+ onNextPage(player, inventory);
+ } else {
+ onCannotNextPage(player, inventory);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/button/buttons/PaginationPreviousButton.java b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationPreviousButton.java
new file mode 100644
index 00000000..5762a41c
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/button/buttons/PaginationPreviousButton.java
@@ -0,0 +1,33 @@
+package fr.maxlego08.menu.button.buttons;
+
+import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.engine.InventoryEngine;
+import fr.maxlego08.menu.api.utils.Placeholders;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.jetbrains.annotations.NotNull;
+
+public class PaginationPreviousButton extends PaginationButton {
+
+ public PaginationPreviousButton(@NotNull MenuPlugin plugin, @NotNull String contextId) {
+ super(plugin, contextId);
+ }
+
+ protected void onPreviousPage(@NotNull Player player, @NotNull InventoryEngine inventory) {
+ refreshInventory(player);
+ }
+
+ protected void onCannotPreviousPage(@NotNull Player player, @NotNull InventoryEngine inventory) {
+ }
+
+ @Override
+ public void onClick(@NotNull Player player, @NotNull InventoryClickEvent event, @NotNull InventoryEngine inventory, int slot, @NotNull Placeholders placeholders) {
+ int currentPage = this.manager.getPage(player.getUniqueId(), this.contextId);
+ if (currentPage > 0) {
+ this.manager.previousPage(player.getUniqueId(), this.contextId);
+ onPreviousPage(player, inventory);
+ } else {
+ onCannotPreviousPage(player, inventory);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/button/loader/PaginationNextButtonLoader.java b/src/main/java/fr/maxlego08/menu/button/loader/PaginationNextButtonLoader.java
new file mode 100644
index 00000000..f42bb371
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/button/loader/PaginationNextButtonLoader.java
@@ -0,0 +1,28 @@
+package fr.maxlego08.menu.button.loader;
+
+import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.button.Button;
+import fr.maxlego08.menu.api.button.DefaultButtonValue;
+import fr.maxlego08.menu.api.loader.ButtonLoader;
+import fr.maxlego08.menu.button.buttons.PaginationNextButton;
+import fr.maxlego08.menu.zcore.logger.Logger;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PaginationNextButtonLoader extends ButtonLoader {
+
+ public PaginationNextButtonLoader(MenuPlugin plugin) {
+ super(plugin, "pagination_next");
+ }
+
+ @Override
+ public @Nullable Button load(@NotNull YamlConfiguration configuration, @NotNull String path, @NotNull DefaultButtonValue defaultButtonValue) {
+ String contextId = configuration.getString(path + "context-id");
+ if (contextId == null) {
+ Logger.info("Context-id is required for pagination_next button at path: " + path);
+ return null;
+ }
+ return new PaginationNextButton((MenuPlugin) this.plugin, contextId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/button/loader/PaginationPreviousButtonLoader.java b/src/main/java/fr/maxlego08/menu/button/loader/PaginationPreviousButtonLoader.java
new file mode 100644
index 00000000..c1855fbf
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/button/loader/PaginationPreviousButtonLoader.java
@@ -0,0 +1,28 @@
+package fr.maxlego08.menu.button.loader;
+
+import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.button.Button;
+import fr.maxlego08.menu.api.button.DefaultButtonValue;
+import fr.maxlego08.menu.api.loader.ButtonLoader;
+import fr.maxlego08.menu.button.buttons.PaginationPreviousButton;
+import fr.maxlego08.menu.zcore.logger.Logger;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PaginationPreviousButtonLoader extends ButtonLoader {
+
+ public PaginationPreviousButtonLoader(MenuPlugin plugin) {
+ super(plugin, "pagination_previous");
+ }
+
+ @Override
+ public @Nullable Button load(@NotNull YamlConfiguration configuration, @NotNull String path, @NotNull DefaultButtonValue defaultButtonValue) {
+ String contextId = configuration.getString(path + "context-id");
+ if (contextId == null) {
+ Logger.info("Context-id is required for pagination_previous button at path: " + path);
+ return null;
+ }
+ return new PaginationPreviousButton((MenuPlugin) this.plugin, contextId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java b/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java
index ed3959f7..46bd716b 100644
--- a/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java
+++ b/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java
@@ -267,5 +267,6 @@ protected void onConnect(PlayerJoinEvent event, Player player) {
@Override
protected void onQuit(PlayerQuitEvent event, Player player) {
this.cooldownClick.remove(player.getUniqueId());
+ this.plugin.getInventoryManager().getPaginationManager().removePlayerStates(player.getUniqueId());
}
}
diff --git a/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java b/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
new file mode 100644
index 00000000..49d93e24
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
@@ -0,0 +1,69 @@
+package fr.maxlego08.menu.pagination;
+
+import fr.maxlego08.menu.api.pagination.PaginationManager;
+import fr.maxlego08.menu.api.pagination.PaginationState;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ZPaginationManager implements PaginationManager {
+ private final Map> paginationStates = new ConcurrentHashMap<>();
+
+ @Override
+ public @NotNull PaginationState getOrCreateState(@NotNull UUID playerId, @NotNull String contextId) {
+ return paginationStates
+ .computeIfAbsent(playerId, k -> new ConcurrentHashMap<>())
+ .computeIfAbsent(contextId, k -> new PaginationState());
+ }
+
+ @Override
+ public int getPage(@NotNull UUID playerId, @NotNull String contextId) {
+ PaginationState state = getState(playerId, contextId);
+ return state != null ? state.getCurrentPage() : 0;
+ }
+
+ @Override
+ public void setPage(@NotNull UUID playerId, @NotNull String contextId, int page) {
+ getOrCreateState(playerId, contextId).setCurrentPage(page);
+ }
+
+ @Override
+ public void nextPage(@NotNull UUID playerId, @NotNull String contextId) {
+ getOrCreateState(playerId, contextId).nextPage();
+ }
+
+ @Override
+ public void previousPage(@NotNull UUID playerId, @NotNull String contextId) {
+ getOrCreateState(playerId, contextId).previousPage();
+ }
+
+ @Override
+ public void reset(@NotNull UUID playerId, @NotNull String contextId) {
+ removeState(playerId, contextId);
+ }
+
+ @Override
+ public @Nullable PaginationState getState(@NotNull UUID playerId, @NotNull String contextId) {
+ Map playerStates = paginationStates.get(playerId);
+ return playerStates != null ? playerStates.get(contextId) : null;
+ }
+
+ @Override
+ public void removeState(@NotNull UUID playerId, @NotNull String contextId) {
+ Map playerStates = paginationStates.get(playerId);
+ if (playerStates != null) playerStates.remove(contextId);
+ }
+
+ @Override
+ public void removePlayerStates(@NotNull UUID playerId) {
+ paginationStates.remove(playerId);
+ }
+
+ @Override
+ public void clearAll() {
+ paginationStates.clear();
+ }
+}
\ No newline at end of file
From 16575e4592918bdca1fc012699023f7b888bf45a Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:04:28 +0100
Subject: [PATCH 2/8] feat: add configuration to skip close actions on
inventory switch to fix issue with back/inv action on close actions
---
.../menu/api/configuration/Configuration.java | 6 ++++++
src/main/java/fr/maxlego08/menu/ZInventory.java | 14 ++++++++++++--
src/main/resources/config.yml | 9 +++++++++
3 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/Configuration.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/Configuration.java
index 793cf7e9..d097f250 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/configuration/Configuration.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/configuration/Configuration.java
@@ -320,6 +320,9 @@ public class Configuration {
label = "Enable performance debug"
)
public static boolean enablePerformanceDebug = false;
+
+ public static List skipCloseActionsOnInventorySwitch = Arrays.asList("inventory", "inv", "back");
+
public static PerformanceFilterMode performanceFilterMode = PerformanceFilterMode.DISABLED;
public static List performanceFilterOperations = new ArrayList<>();
public static long performanceThresholdMs = 10;
@@ -416,6 +419,7 @@ public void load(@NotNull FileConfiguration fileConfiguration) {
enablePerformanceDebug = fileConfiguration.getBoolean(ConfigPath.ENABLE_PERFORMANCE_DEBUG.getPath(), false);
performanceThresholdMs = fileConfiguration.getLong(ConfigPath.PERFORMANCE_DEBUG_THRESHOLD_MS.getPath(), 10L);
performanceFilterOperations = fileConfiguration.getStringList(ConfigPath.PERFORMANCE_DEBUG_FILTER_OPERATIONS.getPath());
+ skipCloseActionsOnInventorySwitch = fileConfiguration.getStringList(ConfigPath.SKIP_CLOSE_ACTIONS_ON_INVENTORY_SWITCH.getPath());
try {
performanceFilterMode = PerformanceFilterMode.valueOf(fileConfiguration.getString(ConfigPath.PERFORMANCE_DEBUG_FILTER_MODE.getPath(), PerformanceFilterMode.DISABLED.name()).toUpperCase());
} catch (IllegalArgumentException e) {
@@ -470,6 +474,7 @@ public void save(@NotNull FileConfiguration fileConfiguration,@NotNull File file
fileConfiguration.set(ConfigPath.ENABLE_PLAYER_COMMANDS_AS_OP_ACTION.getPath(), enablePlayerCommandsAsOPAction);
fileConfiguration.set(ConfigPath.OP_GRANT_METHOD.getPath(), opGrantMethod.name());
fileConfiguration.set(ConfigPath.ENABLE_TOAST.getPath(), enableToast);
+ fileConfiguration.set(ConfigPath.SKIP_CLOSE_ACTIONS_ON_INVENTORY_SWITCH.getPath(), skipCloseActionsOnInventorySwitch);
fileConfiguration.set(ConfigPath.ENABLE_PACKET_EVENT_CLICK_LIMITER.getPath(), enablePacketEventClickLimiter);
fileConfiguration.set(ConfigPath.PACKET_EVENT_CLICK_LIMITER_MILLISECONDS.getPath(), packetEventClickLimiterMilliseconds);
fileConfiguration.set(ConfigPath.ENABLE_PERFORMANCE_DEBUG.getPath(), enablePerformanceDebug);
@@ -526,6 +531,7 @@ private enum ConfigPath {
ENABLE_PLAYER_COMMANDS_AS_OP_ACTION("enable-player-commands-as-op-action"),
OP_GRANT_METHOD("op-grant-method"),
ENABLE_TOAST("enable-toast"),
+ SKIP_CLOSE_ACTIONS_ON_INVENTORY_SWITCH("skip-close-actions-on-inventory-switch"),
ENABLE_PACKET_EVENT_CLICK_LIMITER("enable-packet-event-click-limiter"),
PACKET_EVENT_CLICK_LIMITER_MILLISECONDS("packet-event-click-limiter-milliseconds"),
diff --git a/src/main/java/fr/maxlego08/menu/ZInventory.java b/src/main/java/fr/maxlego08/menu/ZInventory.java
index 1b3ad212..f8b51e64 100644
--- a/src/main/java/fr/maxlego08/menu/ZInventory.java
+++ b/src/main/java/fr/maxlego08/menu/ZInventory.java
@@ -5,6 +5,7 @@
import fr.maxlego08.menu.api.animation.TitleAnimation;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.PaginateButton;
+import fr.maxlego08.menu.api.configuration.Configuration;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.engine.InventoryResult;
import fr.maxlego08.menu.api.pattern.Pattern;
@@ -273,6 +274,7 @@ public void closeInventory(Player player, InventoryEngine inventoryDefault) {
ZMenuPlugin.getInstance().getScheduler().runAtEntityLater(player, task -> {
InventoryHolder newHolder = CompatibilityUtil.getTopInventory(player).getHolder();
+ boolean isInNewzMenuInventory = newHolder instanceof InventoryDefault;
if (newHolder != null && !(newHolder instanceof InventoryDefault)) {
clearPlayerInventoryButtons(player, inventoryDefault);
@@ -281,10 +283,18 @@ public void closeInventory(Player player, InventoryEngine inventoryDefault) {
this.clearInvType.getOnInventoryClose().accept(inventoriesPlayer, player);
}
}
+ var placeholders = new Placeholders();
+ if (isInNewzMenuInventory) {
+ for (Action action : this.closeActions) {
+ if (!Configuration.skipCloseActionsOnInventorySwitch.contains(action.getType())) {
+ action.preExecute(player, null, inventoryDefault, placeholders);
+ }
+ }
+ } else {
+ this.closeActions.forEach(action -> action.preExecute(player, null, inventoryDefault, placeholders));
+ }
}, 1);
- var placeholders = new Placeholders();
- this.closeActions.forEach(action -> action.preExecute(player, null, inventoryDefault, placeholders));
}
@Override
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index afba7f9d..d1bd45fb 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -97,6 +97,15 @@ enable-player-commands-as-op-action: false
# This setting only applies when enable-player-commands-as-op-action is true.
op-grant-method: ATTACHMENT
+# Skip close actions on inventory switch.
+# Prevents certain action types from executing when a player opens a new zMenu inventory.
+# This prevents actions like "open another inventory" or "go back" from reopening old menus.
+# List the action types you want to skip (e.g., "inventory", "inv", "back").
+skip-close-actions-on-inventory-switch:
+ - inventory
+ - inv
+ - back
+
# Enable FastEvent system.
# Replaces some Bukkit events with a faster alternative. Enables better performance at the cost of API changes.
# Refer to documentation before enabling this.
From dd5dbed6c4d978ec89639d9d0f9976744f023b39 Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:12:02 +0100
Subject: [PATCH 3/8] feat: add pagination placeholders to MenuPlaceholders for
enhanced inventory navigation
---
.../menu/placeholder/MenuPlaceholders.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java b/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
index 1d661600..83547dbe 100644
--- a/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
+++ b/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
@@ -20,11 +20,36 @@ public void register(MenuPlugin plugin) {
fr.maxlego08.menu.api.placeholder.LocalPlaceholder placeholder = fr.maxlego08.menu.api.placeholder.LocalPlaceholder.getInstance();
var inventoryManager = plugin.getInventoryManager();
+ var paginationManager = inventoryManager.getPaginationManager();
+
placeholder.register("test", (a, b) -> "&ctest");
+
placeholder.register("player_page", (player, s) -> String.valueOf(inventoryManager.getPage(player)));
placeholder.register("player_next_page", (player, s) -> String.valueOf(inventoryManager.getPage(player) + 1));
placeholder.register("player_previous_page", (player, s) -> String.valueOf(inventoryManager.getPage(player) - 1));
placeholder.register("player_max_page", (player, s) -> String.valueOf(inventoryManager.getMaxPage(player)));
+
+ placeholder.register("pagination_page_", (player, contextId) -> {
+ if (paginationManager == null) return "0";
+ return String.valueOf(paginationManager.getPage(player.getUniqueId(), contextId));
+ });
+
+ placeholder.register("pagination_page_one_indexed_", (player, contextId) -> {
+ if (paginationManager == null) return "0";
+ return String.valueOf(paginationManager.getPage(player.getUniqueId(), contextId) + 1);
+ });
+
+ placeholder.register("pagination_next_page_", (player, contextId) -> {
+ if (paginationManager == null) return "0";
+ return String.valueOf(paginationManager.getPage(player.getUniqueId(), contextId) + 1);
+ });
+
+ placeholder.register("pagination_previous_page_", (player, contextId) -> {
+ if (paginationManager == null) return "0";
+ int currentPage = paginationManager.getPage(player.getUniqueId(), contextId);
+ return String.valueOf(Math.max(0, currentPage - 1));
+ });
+
placeholder.register("player_previous_inventories", (playeofflinePlayer, s) -> {
if (playeofflinePlayer.isOnline()) {
Player player = playeofflinePlayer.getPlayer();
From 8603d63135dc3c10bd5a1530e29d34e17dd5ab30 Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:27:19 +0100
Subject: [PATCH 4/8] feat: implement ResetPaginationAction and loader to
manage pagination resets (for buttons paginations)
---
.../fr/maxlego08/menu/ZInventoryManager.java | 1 +
.../loader/actions/ResetPaginationLoader.java | 64 ++++++++++++++++
.../actions/ResetPaginationAction.java | 74 +++++++++++++++++++
3 files changed, 139 insertions(+)
create mode 100644 src/main/java/fr/maxlego08/menu/loader/actions/ResetPaginationLoader.java
create mode 100644 src/main/java/fr/maxlego08/menu/requirement/actions/ResetPaginationAction.java
diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
index b7c17cad..2cbe8d99 100644
--- a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
+++ b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
@@ -396,6 +396,7 @@ public void loadButtons() {
buttonManager.registerAction(new ActionBarLoader());
buttonManager.registerAction(new RefreshLoader());
buttonManager.registerAction(new RefreshInventoryLoader());
+ buttonManager.registerAction(new ResetPaginationLoader(this.paginationManager));
buttonManager.registerAction(new DiscordLoader());
buttonManager.registerAction(new DiscordComponentV2Loader());
buttonManager.registerAction(new TeleportLoader(this.plugin));
diff --git a/src/main/java/fr/maxlego08/menu/loader/actions/ResetPaginationLoader.java b/src/main/java/fr/maxlego08/menu/loader/actions/ResetPaginationLoader.java
new file mode 100644
index 00000000..2688258c
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/loader/actions/ResetPaginationLoader.java
@@ -0,0 +1,64 @@
+package fr.maxlego08.menu.loader.actions;
+
+import fr.maxlego08.menu.api.loader.ActionLoader;
+import fr.maxlego08.menu.api.pagination.PaginationManager;
+import fr.maxlego08.menu.api.requirement.Action;
+import fr.maxlego08.menu.api.utils.TypedMapAccessor;
+import fr.maxlego08.menu.requirement.actions.ResetPaginationAction;
+import org.jetbrains.annotations.Nullable;
+import org.jspecify.annotations.NonNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResetPaginationLoader extends ActionLoader {
+ private final PaginationManager paginationManager;
+
+ public ResetPaginationLoader(PaginationManager paginationManager) {
+ super("reset-pagination");
+ this.paginationManager = paginationManager;
+ }
+
+ @Override
+ public @Nullable Action load(@NonNull String path, @NonNull TypedMapAccessor accessor, @NonNull File file) {
+ String typeStr = accessor.getString("type", "context").toLowerCase();
+
+ try {
+ ResetPaginationAction.ResetType resetType = ResetPaginationAction.ResetType.valueOf(typeStr.toUpperCase());
+
+ if (resetType == ResetPaginationAction.ResetType.CONTEXT) {
+ List contextIds = loadContextIds(accessor);
+ if (contextIds.isEmpty()) {
+ return null;
+ }
+ return new ResetPaginationAction(this.paginationManager, resetType, contextIds);
+ } else if (resetType == ResetPaginationAction.ResetType.ALL) {
+ return new ResetPaginationAction(this.paginationManager, resetType, null);
+ }
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ return null;
+ }
+
+ private List loadContextIds(@NonNull TypedMapAccessor accessor) {
+ List contextIds = new ArrayList<>();
+
+ List contextIdList = accessor.getStringList("context-ids");
+ if (!contextIdList.isEmpty()) {
+ contextIds.addAll(contextIdList);
+ }
+
+ if (contextIds.isEmpty()) {
+ String contextId = accessor.getString("context-id");
+ if (contextId != null && !contextId.isEmpty()) {
+ contextIds.add(contextId);
+ }
+ }
+
+ return contextIds;
+ }
+}
+
diff --git a/src/main/java/fr/maxlego08/menu/requirement/actions/ResetPaginationAction.java b/src/main/java/fr/maxlego08/menu/requirement/actions/ResetPaginationAction.java
new file mode 100644
index 00000000..6354eb70
--- /dev/null
+++ b/src/main/java/fr/maxlego08/menu/requirement/actions/ResetPaginationAction.java
@@ -0,0 +1,74 @@
+package fr.maxlego08.menu.requirement.actions;
+
+import fr.maxlego08.menu.api.button.Button;
+import fr.maxlego08.menu.api.engine.InventoryEngine;
+import fr.maxlego08.menu.api.pagination.PaginationManager;
+import fr.maxlego08.menu.api.requirement.Action;
+import fr.maxlego08.menu.api.utils.Placeholders;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResetPaginationAction extends Action {
+ public enum ResetType {
+ /**
+ * Reset a specific pagination context by ID
+ */
+ CONTEXT,
+ /**
+ * Reset all pagination contexts for the player
+ */
+ ALL
+ }
+
+ private final PaginationManager paginationManager;
+ private final ResetType resetType;
+ private final List<@NotNull String> contextIds;
+
+ public ResetPaginationAction(@NotNull PaginationManager paginationManager, @NotNull ResetType resetType, @Nullable List<@NotNull String> contextIds) {
+ this.paginationManager = paginationManager;
+ this.resetType = resetType;
+ this.contextIds = contextIds != null ? new ArrayList<>(contextIds) : new ArrayList<>();
+ }
+
+ @Override
+ public void execute(@NotNull Player player, @Nullable Button button, @NotNull InventoryEngine inventoryEngine, @NotNull Placeholders placeholders) {
+ switch (this.resetType) {
+ case CONTEXT -> {
+ for (String contextId : this.contextIds) {
+ if (!contextId.isEmpty()) {
+ this.paginationManager.reset(player.getUniqueId(), inventoryEngine.getPlugin().parse(player, placeholders.parse(contextId)));
+ }
+ }
+ }
+ case ALL -> this.paginationManager.removePlayerStates(player.getUniqueId());
+ }
+ }
+
+ @NotNull
+ public ResetType getResetType() {
+ return this.resetType;
+ }
+
+ @NotNull
+ public List getContextIds() {
+ return new ArrayList<>(this.contextIds);
+ }
+
+ @Nullable
+ public String getContextId() {
+ return this.contextIds.isEmpty() ? null : this.contextIds.getFirst();
+ }
+
+ @Override
+ public String toString() {
+ return "ResetPaginationAction{" +
+ "resetType=" + resetType +
+ ", contextIds=" + contextIds +
+ '}';
+ }
+}
+
From e6d73dc5d5ff8a4265a3ec52d01d70f3999be940 Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:36:56 +0100
Subject: [PATCH 5/8] feat: optimize pagination calculations and improve page
size handling
---
.../api/button/GenericPaginateButton.java | 22 ++++++++++++++++---
.../api/button/GenericPaginationButton.java | 14 +++++++-----
2 files changed, 28 insertions(+), 8 deletions(-)
diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
index d1569765..8895055f 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginateButton.java
@@ -50,8 +50,8 @@ public final void setCurrentPage(@NotNull Player player, int page) {
* @return true if advanced, false if already at the last page
*/
public final boolean nextPage(@NotNull Player player) {
- int maxPage = getMaxPage(player);
int currentPage = getCurrentPage(player);
+ int maxPage = getMaxPage(player);
if (currentPage < maxPage) {
getPaginationManager().nextPage(player.getUniqueId(), getContextId(player));
return true;
@@ -85,6 +85,7 @@ public final void resetPagination(@NotNull Player player) {
/**
* Calculates the maximum page number (0-based index).
+ * This caches the page size to avoid multiple getSlots() calls.
*
* @param player the player
* @return the maximum page
@@ -93,7 +94,21 @@ public final int getMaxPage(@NotNull Player player) {
int totalSize = getPaginationSize(player);
int pageSize = getSlots().size();
if (pageSize <= 0) return 0;
- return Math.max(0, (int) Math.ceil((double) totalSize / pageSize) - 1);
+ return Math.max(0, (totalSize - 1) / pageSize);
+ }
+
+ /**
+ * Calculates the maximum page number with pre-calculated page size (0-based index).
+ * Use this when page size is already available to avoid redundant getSlots() calls.
+ *
+ * @param player the player
+ * @param pageSize the page size
+ * @return the maximum page
+ */
+ public final int getMaxPage(@NotNull Player player, int pageSize) {
+ if (pageSize <= 0) return 0;
+ int totalSize = getPaginationSize(player);
+ return Math.max(0, (totalSize - 1) / pageSize);
}
/**
@@ -103,7 +118,8 @@ public final int getMaxPage(@NotNull Player player) {
* @return true if there's a next page
*/
public final boolean hasNextPage(@NotNull Player player) {
- return getCurrentPage(player) < getMaxPage(player);
+ int currentPage = getCurrentPage(player);
+ return currentPage < getMaxPage(player);
}
/**
diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
index 1962b5e5..ee36450e 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
@@ -6,6 +6,7 @@
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
+import java.util.Collection;
import java.util.List;
public abstract class GenericPaginationButton extends GenericPaginateButton {
@@ -38,20 +39,23 @@ protected abstract void renderElement(
@Override
public final void onRender(@NotNull Player player, @NotNull InventoryEngine inventory) {
List elements = getElements(player);
- int pageSize = getSlots().size();
+ Collection slots = getSlots();
+ int pageSize = slots.size();
int currentPage = getCurrentPageOneIndexed(player);
+
+ int maxPage = getMaxPage(player, pageSize) + 1;
Pagination pagination = new Pagination<>();
- List paginatedElements = pagination.paginate(elements, pageSize, currentPage);
+ List paginatedElements = pagination.paginate(elements, pageSize, currentPage - 1);
int slotIndex = 0;
- for (Integer slot : getSlots()) {
+ for (Integer slot : slots) {
if (slotIndex >= paginatedElements.size()) break;
T element = paginatedElements.get(slotIndex);
Placeholders placeholders = new Placeholders();
- placeholders.register("page", String.valueOf(getCurrentPageOneIndexed(player)));
- placeholders.register("max_page", String.valueOf(getMaxPage(player) + 1));
+ placeholders.register("page", String.valueOf(currentPage));
+ placeholders.register("max_page", String.valueOf(maxPage));
renderElement(player, inventory, slot, element, placeholders);
slotIndex++;
From 3e598f8e1e4fbdbc49c60a0deee82373e714a3a8 Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:50:21 +0100
Subject: [PATCH 6/8] feat: enhance pagination management with max page
tracking and UI adjustments
---
.../api/button/GenericPaginationButton.java | 8 +++--
.../api/pagination/PaginationManager.java | 18 +++++++++++
.../menu/api/pagination/PaginationState.java | 30 ++++++++++++++++++-
.../menu/pagination/ZPaginationManager.java | 11 +++++++
.../menu/placeholder/MenuPlaceholders.java | 22 +++++++++++++-
5 files changed, 84 insertions(+), 5 deletions(-)
diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
index ee36450e..906458ab 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/button/GenericPaginationButton.java
@@ -43,10 +43,12 @@ public final void onRender(@NotNull Player player, @NotNull InventoryEngine inve
int pageSize = slots.size();
int currentPage = getCurrentPageOneIndexed(player);
- int maxPage = getMaxPage(player, pageSize) + 1;
+ int maxPage = getMaxPage(player, pageSize);
+
+ getPaginationManager().setMaxPage(player.getUniqueId(), getContextId(player), maxPage);
Pagination pagination = new Pagination<>();
- List paginatedElements = pagination.paginate(elements, pageSize, currentPage - 1);
+ List paginatedElements = pagination.paginate(elements, pageSize, currentPage);
int slotIndex = 0;
for (Integer slot : slots) {
@@ -55,7 +57,7 @@ public final void onRender(@NotNull Player player, @NotNull InventoryEngine inve
T element = paginatedElements.get(slotIndex);
Placeholders placeholders = new Placeholders();
placeholders.register("page", String.valueOf(currentPage));
- placeholders.register("max_page", String.valueOf(maxPage));
+ placeholders.register("max_page", String.valueOf(maxPage + 1));
renderElement(player, inventory, slot, element, placeholders);
slotIndex++;
diff --git a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
index dfbefc78..ec8e09ab 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationManager.java
@@ -95,6 +95,24 @@ default boolean hasState(@NotNull UUID playerId, @NotNull String contextId) {
*/
void removePlayerStates(@NotNull UUID playerId);
+ /**
+ * Gets the maximum page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @return the maximum page (0-based index), or 0 if not found
+ */
+ int getMaxPage(@NotNull UUID playerId, @NotNull String contextId);
+
+ /**
+ * Sets the maximum page for a player and context.
+ *
+ * @param playerId the player's UUID
+ * @param contextId the context identifier
+ * @param maxPage the maximum page to set (0-based index)
+ */
+ void setMaxPage(@NotNull UUID playerId, @NotNull String contextId, int maxPage);
+
/**
* Clears all pagination states.
*/
diff --git a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
index 818609fe..ade0eb1a 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/pagination/PaginationState.java
@@ -2,6 +2,7 @@
public class PaginationState {
private int currentPage;
+ private int maxPage = 0;
public PaginationState() {
this(0);
@@ -39,8 +40,35 @@ public void previousPage() {
}
}
+ /**
+ * Gets the maximum page number (0-based index).
+ *
+ * @return the maximum page
+ */
+ public int getMaxPage() {
+ return maxPage;
+ }
+
+ /**
+ * Sets the maximum page number (0-based index).
+ *
+ * @param maxPage the maximum page to set
+ */
+ public void setMaxPage(int maxPage) {
+ this.maxPage = Math.max(0, maxPage);
+ }
+
+ /**
+ * Gets the maximum page number (1-based index for UI purposes).
+ *
+ * @return the maximum page (1-based)
+ */
+ public int getMaxPageOneIndexed() {
+ return this.maxPage + 1;
+ }
+
@Override
public String toString() {
- return String.format("PaginationState{currentPage=%d}", currentPage);
+ return String.format("PaginationState{currentPage=%d, maxPage=%d}", currentPage, maxPage);
}
}
\ No newline at end of file
diff --git a/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java b/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
index 49d93e24..68b7859f 100644
--- a/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
+++ b/src/main/java/fr/maxlego08/menu/pagination/ZPaginationManager.java
@@ -45,6 +45,17 @@ public void reset(@NotNull UUID playerId, @NotNull String contextId) {
removeState(playerId, contextId);
}
+ @Override
+ public int getMaxPage(@NotNull UUID playerId, @NotNull String contextId) {
+ PaginationState state = getState(playerId, contextId);
+ return state != null ? state.getMaxPage() : 0;
+ }
+
+ @Override
+ public void setMaxPage(@NotNull UUID playerId, @NotNull String contextId, int maxPage) {
+ getOrCreateState(playerId, contextId).setMaxPage(maxPage);
+ }
+
@Override
public @Nullable PaginationState getState(@NotNull UUID playerId, @NotNull String contextId) {
Map playerStates = paginationStates.get(playerId);
diff --git a/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java b/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
index 83547dbe..8cf969bb 100644
--- a/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
+++ b/src/main/java/fr/maxlego08/menu/placeholder/MenuPlaceholders.java
@@ -41,7 +41,7 @@ public void register(MenuPlugin plugin) {
placeholder.register("pagination_next_page_", (player, contextId) -> {
if (paginationManager == null) return "0";
- return String.valueOf(paginationManager.getPage(player.getUniqueId(), contextId) + 1);
+ return String.valueOf(paginationManager.getPage(player.getUniqueId(), contextId) + 2);
});
placeholder.register("pagination_previous_page_", (player, contextId) -> {
@@ -50,6 +50,26 @@ public void register(MenuPlugin plugin) {
return String.valueOf(Math.max(0, currentPage - 1));
});
+ placeholder.register("pagination_max_page_", (player, contextId) -> {
+ if (paginationManager == null) return "1";
+
+ String actualContextId = contextId;
+ int defaultMaxPage = 0;
+
+ if (contextId.contains(":")) {
+ String[] parts = contextId.split(":", 2);
+ actualContextId = parts[0];
+ try {
+ defaultMaxPage = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ int storedMaxPage = paginationManager.getMaxPage(player.getUniqueId(), actualContextId);
+ int maxPage = Math.max(storedMaxPage, defaultMaxPage);
+ return String.valueOf(maxPage + 1);
+ });
+
placeholder.register("player_previous_inventories", (playeofflinePlayer, s) -> {
if (playeofflinePlayer.isOnline()) {
Player player = playeofflinePlayer.getPlayer();
From 3904e73f92e07c6bc947eace4ae614e02eb67739 Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Thu, 26 Mar 2026 18:16:49 +0100
Subject: [PATCH 7/8] feat: simplify item button click handling by removing
null check for clickable buttons
---
.../maxlego08/menu/inventory/inventories/InventoryDefault.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java b/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java
index c0c79688..1fc78027 100644
--- a/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java
+++ b/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java
@@ -321,7 +321,7 @@ public void displayFinalButton(@NotNull Button button, @NotNull Placeholders pla
ItemButton itemButton = this.addItem(button.isPlayerInventory(), slot, itemStack);
perfDebug.end();
- if (itemButton != null && button.isClickable()) {
+ if (button.isClickable()) {
itemButton.setClick(event -> {
if (event.getClick() == ClickType.DOUBLE_CLICK) return;
From e3a7b236ebf0d3767461539b84b98f7262439b6d Mon Sep 17 00:00:00 2001
From: 1robie <97293924+1robie@users.noreply.github.com>
Date: Sat, 4 Apr 2026 21:41:49 +0200
Subject: [PATCH 8/8] feat: introduce PacketManager interface and refactor
title name handling in packet events
---
.../fr/maxlego08/menu/api/MenuPlugin.java | 3 ++
.../fr/maxlego08/menu/api/PacketManager.java | 18 +++++++
.../menu/hooks/packetevents/PacketUtils.java | 33 ++++++++++++-
.../action/PacketEventChangeTitleName.java | 28 +++--------
.../listener/PacketTitleListener.java | 48 +++++++++++--------
.../PacketEventChangeTitleNameLoader.java | 13 ++---
.../fr/maxlego08/menu/ZInventoryManager.java | 5 +-
.../java/fr/maxlego08/menu/ZMenuPlugin.java | 27 ++++++-----
8 files changed, 110 insertions(+), 65 deletions(-)
create mode 100644 API/src/main/java/fr/maxlego08/menu/api/PacketManager.java
diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java
index 36e52713..6da00b7e 100644
--- a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java
+++ b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java
@@ -21,6 +21,7 @@
import java.io.File;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
public interface MenuPlugin extends Plugin {
@@ -134,6 +135,8 @@ public interface MenuPlugin extends Plugin {
*/
FontImage getFontImage();
+ Optional getPacketManager();
+
/**
* Returns the data manager.
* This method returns the data manager, which is used for managing data related to players.
diff --git a/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java b/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java
new file mode 100644
index 00000000..649bc2e8
--- /dev/null
+++ b/API/src/main/java/fr/maxlego08/menu/api/PacketManager.java
@@ -0,0 +1,18 @@
+package fr.maxlego08.menu.api;
+
+import net.kyori.adventure.text.Component;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public interface PacketManager {
+
+ void onLoad();
+
+ void onEnable();
+
+ void onDisable();
+
+ void editInventoryTitleName(@NotNull Player player, @NotNull Component title);
+
+ void editInventoryTitleName(@NotNull Player player, @NotNull String title);
+}
diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java
index faf5a219..5149d752 100644
--- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java
+++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java
@@ -3,28 +3,36 @@
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.EventManager;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
+import com.github.retrooper.packetevents.manager.player.PlayerManager;
+import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
import fr.maxlego08.menu.api.Inventory;
import fr.maxlego08.menu.api.InventoryListener;
import fr.maxlego08.menu.api.MenuPlugin;
+import fr.maxlego08.menu.api.PacketManager;
import fr.maxlego08.menu.api.configuration.Configuration;
import fr.maxlego08.menu.api.engine.BaseInventory;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.engine.ItemButton;
import fr.maxlego08.menu.api.utils.CompatibilityUtil;
+import fr.maxlego08.menu.api.utils.PaperMetaUpdater;
import fr.maxlego08.menu.hooks.packetevents.listener.PacketAnimationListener;
import fr.maxlego08.menu.hooks.packetevents.listener.PacketEventClickLimiterListener;
import fr.maxlego08.menu.hooks.packetevents.listener.PacketTitleListener;
import fr.maxlego08.menu.zcore.logger.Logger;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
+import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
-public class PacketUtils implements InventoryListener {
+public class PacketUtils implements InventoryListener, PacketManager {
+ private final PlayerManager playerManager = PacketEvents.getAPI().getPlayerManager();
+
private PacketAnimationListener packetAnimationListener;
private PacketTitleListener packetTitleListener;
@@ -35,11 +43,13 @@ public PacketUtils(MenuPlugin plugin) {
this.plugin = plugin;
}
+ @Override
public void onLoad() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this.plugin));
PacketEvents.getAPI().load();
}
+ @Override
public void onEnable() {
PacketEvents.getAPI().init();
EventManager eventManager = PacketEvents.getAPI().getEventManager();
@@ -52,6 +62,7 @@ public void onEnable() {
}
}
+ @Override
public void onDisable() {
PacketEvents.getAPI().terminate();
}
@@ -110,4 +121,24 @@ public PacketAnimationListener getPacketAnimationListener() {
public PacketTitleListener getPacketTitleListener() {
return packetTitleListener;
}
+
+ @Override
+ public void editInventoryTitleName(@NotNull Player player, @NotNull Component title) {
+ this.packetTitleListener.getPlayerPacketInformation(player.getUniqueId()).ifPresent(playerPacketInformation -> {
+ WrapperPlayServerOpenWindow wrapperPlayServerOpenWindow = playerPacketInformation.getWrapperPlayServerOpenWindow();
+ WrapperPlayServerOpenWindow newWrapperPlayServerOpenWindow1 = new WrapperPlayServerOpenWindow(wrapperPlayServerOpenWindow.getContainerId(),
+ wrapperPlayServerOpenWindow.getType(),
+ title);
+ this.playerManager.sendPacket(player, newWrapperPlayServerOpenWindow1);
+ this.playerManager.sendPacket(player, playerPacketInformation.getWrapperPlayServerWindowItems());
+ });
+ }
+
+ @Override
+ public void editInventoryTitleName(@NotNull Player player, @NotNull String title) {
+ if (this.plugin.getMetaUpdater() instanceof PaperMetaUpdater paperMetaUpdater) {
+ Component component = paperMetaUpdater.getComponent(title);
+ this.editInventoryTitleName(player, component);
+ }
+ }
}
diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/action/PacketEventChangeTitleName.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/action/PacketEventChangeTitleName.java
index 8ea30654..b0c38dfb 100644
--- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/action/PacketEventChangeTitleName.java
+++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/action/PacketEventChangeTitleName.java
@@ -1,42 +1,26 @@
package fr.maxlego08.menu.hooks.packetevents.action;
-import com.github.retrooper.packetevents.PacketEvents;
-import com.github.retrooper.packetevents.manager.player.PlayerManager;
-import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
+import fr.maxlego08.menu.api.PacketManager;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.engine.InventoryEngine;
-import fr.maxlego08.menu.api.utils.PaperMetaUpdater;
import fr.maxlego08.menu.api.utils.Placeholders;
import fr.maxlego08.menu.common.utils.ActionHelper;
-import fr.maxlego08.menu.hooks.packetevents.listener.PacketTitleListener;
-import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PacketEventChangeTitleName extends ActionHelper {
- private final PaperMetaUpdater metaUpdater;
- private final PacketTitleListener packetTitleListener;
private final String newInventoryName;
- private final PlayerManager playerManager = PacketEvents.getAPI().getPlayerManager();
+ private final PacketManager packetManager;
- public PacketEventChangeTitleName(@NotNull PaperMetaUpdater metaUpdater, PacketTitleListener packetTitleListener, String newInventoryName) {
- this.metaUpdater = metaUpdater;
- this.packetTitleListener = packetTitleListener;
+
+ public PacketEventChangeTitleName(String newInventoryName, PacketManager packetManager) {
this.newInventoryName = newInventoryName;
+ this.packetManager = packetManager;
}
@Override
protected void execute(@NotNull Player player, @Nullable Button button, @NotNull InventoryEngine inventoryEngine, @NotNull Placeholders placeholders) {
- Component component = metaUpdater.getComponent(papi(placeholders.parse(this.newInventoryName), player));
- PacketTitleListener.PlayerPacketInformation playerPacketInformation = this.packetTitleListener.getPlayerPacketInformation(player.getUniqueId());
- if (playerPacketInformation != null) {
- WrapperPlayServerOpenWindow wrapperPlayServerOpenWindow = playerPacketInformation.getWrapperPlayServerOpenWindow();
- WrapperPlayServerOpenWindow newWrapperPlayServerOpenWindow1 = new WrapperPlayServerOpenWindow(wrapperPlayServerOpenWindow.getContainerId(),
- wrapperPlayServerOpenWindow.getType(),
- component);
- this.playerManager.sendPacket(player, newWrapperPlayServerOpenWindow1);
- this.playerManager.sendPacket(player, playerPacketInformation.getWrapperPlayServerWindowItems());
- }
+ this.packetManager.editInventoryTitleName(player, papi(placeholders.parse(this.newInventoryName), player));
}
}
diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java
index 6cdb40b2..8edaa243 100644
--- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java
+++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketTitleListener.java
@@ -10,6 +10,7 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
import java.util.UUID;
public class PacketTitleListener implements PacketListener {
@@ -40,30 +41,35 @@ public void setWrapperPlayServerOpenWindow(WrapperPlayServerOpenWindow wrapperPl
public void onPacketSend(PacketSendEvent event) {
//TODO: Only store packets for players who have menus open from zMenu
PacketTypeCommon packetType = event.getPacketType();
- if (packetType == PacketType.Play.Server.OPEN_WINDOW){
- WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event);
- Player player = event.getPlayer();
- if (player == null) return;
- UUID playerUniqueId = player.getUniqueId();
- this.playerPacketInformation.computeIfAbsent(playerUniqueId, k -> new PlayerPacketInformation())
- .setWrapperPlayServerOpenWindow(wrapper);
- } else if (packetType == PacketType.Play.Server.CLOSE_WINDOW){
- Player player = event.getPlayer();
- if (player == null) return;
- UUID playerUniqueId = player.getUniqueId();
- this.playerPacketInformation.remove(playerUniqueId);
- } else if (packetType == PacketType.Play.Server.WINDOW_ITEMS){
- WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event);
- Player player = event.getPlayer();
- if (player == null) return;
- UUID playerUniqueId = player.getUniqueId();
- this.playerPacketInformation.computeIfAbsent(playerUniqueId, k -> new PlayerPacketInformation())
- .setWrapperPlayServerWindowItems(wrapper);
+ switch (packetType) {
+ case PacketType.Play.Server.OPEN_WINDOW -> {
+ WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event);
+ Player player = event.getPlayer();
+ if (player == null) return;
+ UUID playerUniqueId = player.getUniqueId();
+ this.playerPacketInformation.computeIfAbsent(playerUniqueId, k -> new PlayerPacketInformation())
+ .setWrapperPlayServerOpenWindow(wrapper);
+ }
+ case PacketType.Play.Server.CLOSE_WINDOW -> {
+ Player player = event.getPlayer();
+ if (player == null) return;
+ UUID playerUniqueId = player.getUniqueId();
+ this.playerPacketInformation.remove(playerUniqueId);
+ }
+ case PacketType.Play.Server.WINDOW_ITEMS -> {
+ WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event);
+ Player player = event.getPlayer();
+ if (player == null) return;
+ UUID playerUniqueId = player.getUniqueId();
+ this.playerPacketInformation.computeIfAbsent(playerUniqueId, k -> new PlayerPacketInformation())
+ .setWrapperPlayServerWindowItems(wrapper);
+ }
+ default -> {}
}
}
- public PlayerPacketInformation getPlayerPacketInformation(UUID playerUUID) {
- return this.playerPacketInformation.get(playerUUID);
+ public Optional getPlayerPacketInformation(UUID playerUUID) {
+ return Optional.ofNullable(this.playerPacketInformation.get(playerUUID));
}
diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventChangeTitleNameLoader.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventChangeTitleNameLoader.java
index 7faf1b0e..d9d2e5e6 100644
--- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventChangeTitleNameLoader.java
+++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventChangeTitleNameLoader.java
@@ -1,29 +1,26 @@
package fr.maxlego08.menu.hooks.packetevents.loader;
+import fr.maxlego08.menu.api.PacketManager;
import fr.maxlego08.menu.api.loader.ActionLoader;
import fr.maxlego08.menu.api.requirement.Action;
-import fr.maxlego08.menu.api.utils.PaperMetaUpdater;
import fr.maxlego08.menu.api.utils.TypedMapAccessor;
import fr.maxlego08.menu.hooks.packetevents.action.PacketEventChangeTitleName;
-import fr.maxlego08.menu.hooks.packetevents.listener.PacketTitleListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class PacketEventChangeTitleNameLoader extends ActionLoader {
- private final PaperMetaUpdater metaUpdater;
- private final PacketTitleListener packetTitleListener;
+ private final PacketManager packetManager;
- public PacketEventChangeTitleNameLoader(PaperMetaUpdater metaUpdater, PacketTitleListener packetTitleListener) {
+ public PacketEventChangeTitleNameLoader(PacketManager packetManager) {
super("change-title", "change-title-name");
- this.metaUpdater = metaUpdater;
- this.packetTitleListener = packetTitleListener;
+ this.packetManager = packetManager;
}
@Override
public @Nullable Action load(@NotNull String path, @NotNull TypedMapAccessor accessor, @NotNull File file) {
String newInventoryName = accessor.getString("inventory-name", "menu");
- return new PacketEventChangeTitleName(this.metaUpdater, this.packetTitleListener, newInventoryName);
+ return new PacketEventChangeTitleName(newInventoryName, this.packetManager);
}
}
diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
index 2cbe8d99..871eb014 100644
--- a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
+++ b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java
@@ -410,8 +410,9 @@ public void loadButtons() {
buttonManager.registerAction(new DialogLoader(this.plugin, this.plugin.getDialogManager()));
}
if (this.plugin.isEnable(Plugins.PACKETEVENTS)) {
- if (this.plugin.getMetaUpdater() instanceof PaperMetaUpdater paperMetaUpdater)
- buttonManager.registerAction(new PacketEventChangeTitleNameLoader(paperMetaUpdater, this.plugin.getPacketUtils().getPacketTitleListener()));
+
+ Optional packetManager = this.plugin.getPacketManager();
+ packetManager.ifPresent(manager -> buttonManager.registerAction(new PacketEventChangeTitleNameLoader(manager)));
}
// Loading ButtonLoader
diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java
index 2304b510..7562b40e 100644
--- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java
+++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java
@@ -118,7 +118,7 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin {
private DupeManager dupeManager;
private FontImage fontImage = new EmptyFont();
private MetaUpdater metaUpdater = new ClassicMeta();
- private PacketUtils packetUtils;
+ private PacketManager packetManager;
public static ZMenuPlugin getInstance() {
return instance;
@@ -127,8 +127,10 @@ public static ZMenuPlugin getInstance() {
@Override
public void onLoad() {
if (this.isActive(Plugins.PACKETEVENTS)) {
- this.packetUtils = new PacketUtils(this);
- this.packetUtils.onLoad();
+ this.packetManager = new PacketUtils(this);
+ }
+ if (this.packetManager != null) {
+ this.packetManager.onLoad();
}
}
@@ -140,8 +142,8 @@ public void onEnable() {
this.saveDefaultConfig();
Configuration.getInstance().load(getConfig());
- if (this.packetUtils != null) {
- this.packetUtils.onEnable();
+ if (this.packetManager != null) {
+ this.packetManager.onEnable();
}
this.scheduler = this.foliaLib.getScheduler();
@@ -407,8 +409,9 @@ private List getInventoriesFiles() {
@Override
public void onDisable() {
- if (this.packetUtils != null)
- this.packetUtils.onDisable();
+ if (this.packetManager != null) {
+ this.packetManager.onDisable();
+ }
this.preDisable();
@@ -536,6 +539,12 @@ public MenuItemStack loadItemStack(YamlConfiguration configuration, String path,
return this.inventoryManager.loadItemStack(configuration, path, file);
}
+ @Override
+ public Optional getPacketManager() {
+ return Optional.ofNullable(this.packetManager);
+
+ }
+
/**
* Returns the class that will manage the website
*
@@ -554,10 +563,6 @@ public CommandMenu getCommandMenu() {
return commandMenu;
}
- public PacketUtils getPacketUtils() {
- return packetUtils;
- }
-
@Override
public DataManager getDataManager() {
return dataManager;