mirror of
https://github.com/CivMC/Civ.git
synced 2026-07-18 00:20:44 +00:00
@@ -15,7 +15,6 @@ dependencies {
|
||||
api("com.mojang:datafixerupper:1.0.20")
|
||||
api("com.zaxxer:HikariCP:5.0.1")
|
||||
api("co.aikar:taskchain-bukkit:3.7.2")
|
||||
api("com.github.IPVP-MC:canvas:91ec97f076")
|
||||
api("org.apache.commons:commons-lang3:3.12.0")
|
||||
api("org.apache.commons:commons-collections4:4.4")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
package org.ipvp.canvas.type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.ipvp.canvas.Menu;
|
||||
import org.ipvp.canvas.slot.DefaultSlot;
|
||||
import org.ipvp.canvas.slot.Slot;
|
||||
import vg.civcraft.mc.civmodcore.chat.ChatUtils;
|
||||
import vg.civcraft.mc.civmodcore.inventory.InventoryUtils;
|
||||
import vg.civcraft.mc.civmodcore.inventory.gui.canvas.MenuUtils;
|
||||
import vg.civcraft.mc.civmodcore.utilities.NullUtils;
|
||||
|
||||
public abstract class AbstractCivMenu extends AbstractMenu {
|
||||
|
||||
private static final Field VIEWERS_FIELD;
|
||||
private static final Field SLOTS_FIELD;
|
||||
private static final Method UPDATE_INV_CONTENTS_METHOD;
|
||||
|
||||
static {
|
||||
VIEWERS_FIELD = FieldUtils.getDeclaredField(AbstractMenu.class, "viewers", true);
|
||||
SLOTS_FIELD = FieldUtils.getDeclaredField(AbstractMenu.class, "slots", true);
|
||||
UPDATE_INV_CONTENTS_METHOD = MethodUtils.getMatchingMethod(AbstractMenu.class,
|
||||
"updateInventoryContents", Player.class, Inventory.class);
|
||||
UPDATE_INV_CONTENTS_METHOD.setAccessible(true);
|
||||
}
|
||||
|
||||
private final Component title;
|
||||
private OpenHandler openHandler;
|
||||
private UpdateHandler updateHandler;
|
||||
protected boolean callCloseHandlerRegardless;
|
||||
|
||||
protected AbstractCivMenu(final Component title,
|
||||
final int slots,
|
||||
final Menu parent,
|
||||
final boolean redraw) {
|
||||
super(null, slots, parent, redraw);
|
||||
if (!InventoryUtils.isValidChestSize(slots)) {
|
||||
throw new IllegalArgumentException("Not a valid chest size.");
|
||||
}
|
||||
this.title = ChatUtils.isNullOrEmpty(title) ? InventoryType.CHEST.defaultTitle() : title;
|
||||
}
|
||||
|
||||
protected AbstractCivMenu(final Component title,
|
||||
final InventoryType type,
|
||||
final Menu parent,
|
||||
final boolean redraw) {
|
||||
super(null, type, parent, redraw);
|
||||
this.title = ChatUtils.isNullOrEmpty(title) ? type.defaultTitle() : title;
|
||||
this.inventorySlots = type.getDefaultSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a clone of this menu's title.
|
||||
*/
|
||||
protected Component getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Retrieves this menu's open event handler.
|
||||
*/
|
||||
public Optional<OpenHandler> getOpenHandler() {
|
||||
return Optional.ofNullable(this.openHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the open event handler for this menu. Use
|
||||
* {@link MenuUtils#overrideOpenHandler(AbstractCivMenu, OpenHandler, boolean)} instead if you'd prefer to
|
||||
* override the event handler rather than replace it.
|
||||
*
|
||||
* @param openHandler The open event handler to set for this menu.
|
||||
*/
|
||||
public void setOpenHandler(final OpenHandler openHandler) {
|
||||
this.openHandler = openHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Retrieves this menu's update event handler.
|
||||
*/
|
||||
public Optional<UpdateHandler> getUpdateHandler() {
|
||||
return Optional.ofNullable(this.updateHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the update event handler for this menu. Use
|
||||
* {@link MenuUtils#overrideUpdateHandler(AbstractCivMenu, UpdateHandler, boolean)} instead if you'd prefer to
|
||||
* override the event handler rather than replace it.
|
||||
*
|
||||
* @param updateHandler The update event handler to set for this menu.
|
||||
*/
|
||||
public void setUpdateHandler(final UpdateHandler updateHandler) {
|
||||
this.updateHandler = updateHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the close event handler for this menu. Use
|
||||
* {@link MenuUtils#overrideCloseHandler(AbstractCivMenu, CloseHandler, boolean)} instead if you'd prefer to
|
||||
* override the event handler rather than replace it.
|
||||
*
|
||||
* @param closeHandler The close event handler to set for this menu.
|
||||
*/
|
||||
@Override
|
||||
public void setCloseHandler(final CloseHandler closeHandler) {
|
||||
super.setCloseHandler(closeHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns whether or not this menu will invoke its close event handler regardless of whether
|
||||
* {@link #closedByPlayer(Player, boolean)}'s second parameter, "triggerCloseHandler", is false.
|
||||
*/
|
||||
public boolean isCallingCloseHandlerRegardless() {
|
||||
return this.callCloseHandlerRegardless;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callCloseHandlerRegardless Set this to true if you want this menu to invoke its close event handler
|
||||
* regardless of whether {@link #closedByPlayer(Player, boolean)}'s second
|
||||
* parameter, "triggerCloseHandler", is false.
|
||||
*/
|
||||
public void setCallCloseHandlerRegardless(final boolean callCloseHandlerRegardless) {
|
||||
this.callCloseHandlerRegardless = callCloseHandlerRegardless;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the total slot count for this menu.
|
||||
*/
|
||||
protected int getSlotCount() {
|
||||
return this.inventorySlots;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the underlying viewers via reflection.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final Set<MenuHolder> getRawViewers() {
|
||||
try {
|
||||
return (Set<MenuHolder>) FieldUtils.readField(VIEWERS_FIELD, this);
|
||||
}
|
||||
catch (final IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the underlying slots via reflection.
|
||||
*/
|
||||
protected final DefaultSlot[] getRawSlots() {
|
||||
try {
|
||||
return (DefaultSlot[]) FieldUtils.readField(SLOTS_FIELD, this);
|
||||
}
|
||||
catch (final IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches from this menu to a given menu.
|
||||
*
|
||||
* @param viewer The viewer to switch.
|
||||
* @param otherMenu The other menu to switch to.
|
||||
*/
|
||||
public void openOtherMenu(final Player viewer, final AbstractCivMenu otherMenu) {
|
||||
if (otherMenu == null) {
|
||||
viewer.closeInventory();
|
||||
return;
|
||||
}
|
||||
otherMenu.open(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this menu's parent, or closes if it doesn't exist.
|
||||
*
|
||||
* @param viewer The viewer to navigate.
|
||||
*/
|
||||
public void openParent(final Player viewer) {
|
||||
final var parent = getParent().orElse(null);
|
||||
if (parent == null) {
|
||||
viewer.closeInventory();
|
||||
return;
|
||||
}
|
||||
if (parent instanceof AbstractCivMenu) {
|
||||
((AbstractCivMenu) parent).openOtherMenu(viewer, this);
|
||||
return;
|
||||
}
|
||||
parent.open(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically a carbon copy of {@link AbstractMenu#closedByPlayer(Player, boolean)}, except
|
||||
* for the additional logic of {@link #callCloseHandlerRegardless} to force the menu to call
|
||||
* the close handler even if {@code triggerCloseHandler} is false.
|
||||
*
|
||||
* @param viewer The closing viewer.
|
||||
* @param triggerCloseHandler Whether to call the close handler, which defers to
|
||||
* {@link #callCloseHandlerRegardless}.
|
||||
*/
|
||||
@Override
|
||||
public void closedByPlayer(final Player viewer, final boolean triggerCloseHandler) {
|
||||
final var currentInventory = viewer.getOpenInventory().getTopInventory().getHolder();
|
||||
final var viewers = getRawViewers();
|
||||
if (!(currentInventory instanceof MenuHolder)
|
||||
|| !viewers.contains(currentInventory)) {
|
||||
return;
|
||||
}
|
||||
if (triggerCloseHandler || this.callCloseHandlerRegardless) {
|
||||
getCloseHandler().ifPresent(handler -> handler.close(viewer, this));
|
||||
}
|
||||
viewers.remove((MenuHolder) currentInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewer The viewer to update this menu for.
|
||||
*/
|
||||
@Override
|
||||
public void update(final Player viewer) throws IllegalStateException {
|
||||
if (!isOpen(viewer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var currentHolder = viewer.getOpenInventory().getTopInventory().getHolder();
|
||||
final var currentInventory = NullUtils.isNotNull(currentHolder).getInventory();
|
||||
|
||||
for (final Slot slot : getRawSlots()) {
|
||||
currentInventory.setItem(slot.getIndex(), slot.getItem(viewer));
|
||||
}
|
||||
|
||||
getUpdateHandler().ifPresent(handler -> handler.handle(viewer, this));
|
||||
viewer.updateInventory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically a carbon copy of {@link AbstractMenu#open(Player)}, which is needed to override the
|
||||
* {@code createInventory()} method, but it's private so the entire function needs to be copied,
|
||||
* alongside its other dependent private methods.
|
||||
*/
|
||||
@Override
|
||||
public void open(final Player viewer) {
|
||||
final var currentHolder = viewer.getOpenInventory().getTopInventory().getHolder();
|
||||
final MenuHolder holder;
|
||||
if (currentHolder instanceof MenuHolder) {
|
||||
holder = (MenuHolder) currentHolder;
|
||||
final var currentMenu = holder.getMenu();
|
||||
|
||||
if (currentMenu == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory inventory;
|
||||
if (isRedraw() && Objects.equals(getDimensions(), currentMenu.getDimensions())) {
|
||||
inventory = holder.getInventory();
|
||||
if (currentMenu instanceof AbstractMenu) {
|
||||
((AbstractMenu) currentMenu).closedByPlayer(viewer, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentMenu.close(viewer);
|
||||
inventory = createInventory(holder);
|
||||
holder.setInventory(inventory);
|
||||
viewer.openInventory(inventory);
|
||||
}
|
||||
|
||||
updateInventoryContents(viewer, inventory);
|
||||
holder.setMenu(this);
|
||||
}
|
||||
else {
|
||||
// Create new MenuHolder for the player
|
||||
holder = new MenuHolder(viewer, this);
|
||||
final var inventory = createInventory(holder);
|
||||
updateInventoryContents(viewer, inventory);
|
||||
holder.setInventory(inventory);
|
||||
viewer.openInventory(inventory);
|
||||
}
|
||||
getRawViewers().add(holder);
|
||||
getOpenHandler().ifPresent(handler -> handler.handle(viewer, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically a carbon copy of {@link AbstractMenu}'s version, but uses a component title instead.
|
||||
*
|
||||
* @param holder The menu's inventory holder.
|
||||
* @return Returns the new inventory.
|
||||
*/
|
||||
protected Inventory createInventory(final InventoryHolder holder) {
|
||||
if (this.inventoryType == null) {
|
||||
return Bukkit.createInventory(holder, this.inventorySlots, this.title);
|
||||
}
|
||||
return Bukkit.createInventory(holder, this.inventoryType, this.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link AbstractMenu}'s version via reflection.
|
||||
*
|
||||
* @param viewer The viewer to update.
|
||||
* @param inventory The inventory to update.
|
||||
*/
|
||||
protected void updateInventoryContents(final Player viewer, final Inventory inventory) {
|
||||
try {
|
||||
UPDATE_INV_CONTENTS_METHOD.invoke(this, viewer, inventory);
|
||||
}
|
||||
catch (final InvocationTargetException | IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package org.ipvp.canvas.type;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ipvp.canvas.Menu;
|
||||
import vg.civcraft.mc.civmodcore.chat.ChatUtils;
|
||||
|
||||
public class CivChestMenu extends AbstractCivMenu {
|
||||
|
||||
private Dimension dimension;
|
||||
|
||||
public CivChestMenu(final Component title, final int rows) {
|
||||
this(title, rows, true);
|
||||
}
|
||||
|
||||
public CivChestMenu(final Component title, final int rows, final boolean redraw) {
|
||||
this(title, rows, null, redraw);
|
||||
}
|
||||
|
||||
public CivChestMenu(final Component title, final int rows, final Menu parent, final boolean redraw) {
|
||||
super(title, rows * 9, parent, redraw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Return this chest menu's row and column counts.
|
||||
*/
|
||||
@Override
|
||||
public Dimension getDimensions() {
|
||||
if (this.dimension == null) {
|
||||
this.dimension = new Dimension(getSlotCount() / 9, 9);
|
||||
}
|
||||
return this.dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new builder.
|
||||
*
|
||||
* @param rows The amount of rows for the inventory to contain
|
||||
* @throws IllegalArgumentException if rows is not between 1 and 6 inclusive
|
||||
*/
|
||||
public static Builder builder(final int rows) {
|
||||
if (rows < 1 || rows > 6) {
|
||||
throw new IllegalArgumentException("rows must be a value from 1 to 6");
|
||||
}
|
||||
return new Builder(rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for creating a CustomChestMenu instance.
|
||||
*/
|
||||
public static class Builder extends AbstractMenu.Builder<Builder> {
|
||||
|
||||
private Component title;
|
||||
|
||||
Builder(final int rows) {
|
||||
super(new Dimension(rows, 9));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Builder title(final String title) {
|
||||
if (StringUtils.isBlank(title)) {
|
||||
this.title = null;
|
||||
return this;
|
||||
}
|
||||
this.title = LegacyComponentSerializer.legacyAmpersand().deserialize(title);
|
||||
if (ChatUtils.isNullOrEmpty(this.title)) {
|
||||
this.title = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder title(final Component title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Component getComponentTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CivChestMenu build() {
|
||||
return new CivChestMenu(getComponentTitle(),
|
||||
getDimensions().getArea(), getParent(), isRedraw());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.ipvp.canvas.type;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface OpenHandler {
|
||||
|
||||
/**
|
||||
* Is called by {@link AbstractCivMenu} when one of its menus are opened by a viewer.
|
||||
*
|
||||
* @param viewer The viewer of the opened menu.
|
||||
* @param menu The menu being opened.
|
||||
*/
|
||||
void handle(Player viewer, AbstractCivMenu menu);
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.ipvp.canvas.type;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface UpdateHandler {
|
||||
|
||||
/**
|
||||
* Is called by {@link AbstractCivMenu} when one of its menus are updated. This is called after the menu has been
|
||||
* updated but before the update packets have been sent to the player.
|
||||
*
|
||||
* @param viewer The viewer of the updating menu.
|
||||
* @param menu The menu being updated.
|
||||
*/
|
||||
void handle(Player viewer, AbstractCivMenu menu);
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.java.JavaPluginLoader;
|
||||
import org.ipvp.canvas.MenuFunctionListener;
|
||||
import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager;
|
||||
import vg.civcraft.mc.civmodcore.commands.ChunkMetaCommand;
|
||||
import vg.civcraft.mc.civmodcore.commands.CommandManager;
|
||||
@@ -85,7 +84,6 @@ public class CivModCorePlugin extends ACivMod {
|
||||
}
|
||||
ScoreBoardAPI.setDefaultHeader(this.config.getScoreboardHeader());
|
||||
// Register listeners
|
||||
registerListener(new MenuFunctionListener());
|
||||
registerListener(new ClickableInventoryListener());
|
||||
registerListener(DialogManager.INSTANCE);
|
||||
registerListener(new ScoreBoardListener());
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
package vg.civcraft.mc.civmodcore.inventory.gui.canvas;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.ipvp.canvas.slot.SlotSettings;
|
||||
import org.ipvp.canvas.type.AbstractCivMenu;
|
||||
|
||||
/**
|
||||
* Better version of {@link org.ipvp.canvas.paginate.PaginatedMenuBuilder} as it allows you to apply pagination to an
|
||||
* already existing menu, rather than using a builder to spawn a bunch of new menu instances that'll just be used by a
|
||||
* single player before promptly discarded.
|
||||
*/
|
||||
public final class MenuPagination {
|
||||
|
||||
private final AbstractCivMenu menu;
|
||||
private final Map<UUID, INTERNAL_PlayerPager> playerPages;
|
||||
private final List<SlotSettings> menuBar;
|
||||
private final ElementSupplier supplier;
|
||||
private final int buttonSlots;
|
||||
|
||||
private MenuPagination(final AbstractCivMenu menu,
|
||||
final ElementSupplier supplier,
|
||||
final List<SlotSettings> menuBar) {
|
||||
this.menu = Objects.requireNonNull(menu, "Menu cannot be null");
|
||||
final int rows = menu.getDimensions().getRows();
|
||||
if (rows < 2 || rows > 5) {
|
||||
throw new IllegalArgumentException("Row count must be between 2 and 5 inclusively");
|
||||
}
|
||||
this.playerPages = new Object2ObjectAVLTreeMap<>();
|
||||
this.menuBar = List.copyOf(menuBar);
|
||||
if (menuBar.size() > 9) {
|
||||
throw new IllegalArgumentException("Bar cannot contain more than 9 buttons");
|
||||
}
|
||||
this.supplier = Objects.requireNonNull(supplier, "Element supplier cannot be null");
|
||||
this.buttonSlots = (rows * 9) - 9;
|
||||
|
||||
MenuUtils.overrideOpenHandler(menu, (viewer, opened) -> {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
pager.pageNumber = 0;
|
||||
pager.elements = this.supplier.generate(viewer, opened);
|
||||
INTERNAL_applyPlayerPage(viewer, opened, INTERNAL_getPlayerPager(viewer));
|
||||
}, true);
|
||||
|
||||
MenuUtils.overrideUpdateHandler(menu, (viewer, updated) -> {
|
||||
INTERNAL_applyPlayerPage(viewer, updated, INTERNAL_getPlayerPager(viewer));
|
||||
}, true);
|
||||
|
||||
menu.setCallCloseHandlerRegardless(true);
|
||||
MenuUtils.overrideCloseHandler(menu, (viewer, ignored) -> {
|
||||
this.playerPages.remove(viewer.getUniqueId());
|
||||
}, true);
|
||||
|
||||
for (int i = 0; i < this.buttonSlots; i++) {
|
||||
menu.getSlot(i).setClickHandler((viewer, click) -> {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
final int buttonIndex = INTERNAL_calculateElementIndex(pager, click.getClickedSlot().getIndex());
|
||||
if (buttonIndex >= pager.elements.size()) {
|
||||
return;
|
||||
}
|
||||
final var handler = pager.elements.get(buttonIndex).getClickHandler();
|
||||
if (handler == null) {
|
||||
return;
|
||||
}
|
||||
handler.click(viewer, click);
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
final var slotIndex = INTERNAL_calculateBarIndex(i);
|
||||
if (i >= this.menuBar.size()) {
|
||||
menu.getSlot(slotIndex).setSettings(MenuUtils.createEmptySlot());
|
||||
break;
|
||||
}
|
||||
final var barElement = this.menuBar.get(i);
|
||||
if (barElement == null) {
|
||||
menu.getSlot(slotIndex).setSettings(MenuUtils.createEmptySlot());
|
||||
continue;
|
||||
}
|
||||
menu.getSlot(slotIndex).setSettings(barElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewer The viewer to send to the next page.
|
||||
*/
|
||||
public void nextPage(final Player viewer) {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
pager.pageNumber++;
|
||||
this.menu.update(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewer The viewer to send to the previous page.
|
||||
*/
|
||||
public void previousPage(final Player viewer) {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
pager.pageNumber--;
|
||||
this.menu.update(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewer The viewer to check if they have a next page.
|
||||
* @return Returns whether the viewer has a next page.
|
||||
*/
|
||||
public boolean hasNextPage(final Player viewer) {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
final int elementsSize = CollectionUtils.size(pager.elements);
|
||||
final int totalPages = elementsSize / this.buttonSlots;
|
||||
if (totalPages == 0) {
|
||||
return false;
|
||||
}
|
||||
return pager.pageNumber >= totalPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewer The viewer to check if they have a previous page.
|
||||
* @return Returns whether the viewer has a previous page.
|
||||
*/
|
||||
public boolean hasPreviousPage(final Player viewer) {
|
||||
final var pager = INTERNAL_getPlayerPager(viewer);
|
||||
final int elementsSize = CollectionUtils.size(pager.elements);
|
||||
final int totalPages = elementsSize / this.buttonSlots;
|
||||
if (totalPages == 0) {
|
||||
return false;
|
||||
}
|
||||
return pager.pageNumber < totalPages;
|
||||
}
|
||||
|
||||
protected void INTERNAL_applyPlayerPage(final Player viewer,
|
||||
final AbstractCivMenu menu,
|
||||
final INTERNAL_PlayerPager pager) {
|
||||
pager.pageNumber = Math.max(pager.pageNumber, 0);
|
||||
pager.pageNumber = Math.min(pager.pageNumber, pager.elements.size() / this.buttonSlots);
|
||||
for (int i = 0; i < this.buttonSlots; i++) {
|
||||
final int buttonIndex = INTERNAL_calculateElementIndex(pager, i);
|
||||
if (buttonIndex >= pager.elements.size()) {
|
||||
menu.getSlot(i).setRawItem(viewer, null);
|
||||
continue;
|
||||
}
|
||||
final var template = pager.elements.get(buttonIndex).getItemTemplate();
|
||||
if (template == null) {
|
||||
menu.getSlot(i).setRawItem(viewer, null);
|
||||
continue;
|
||||
}
|
||||
menu.getSlot(i).setRawItem(viewer, template.getItem(viewer));
|
||||
}
|
||||
}
|
||||
|
||||
protected INTERNAL_PlayerPager INTERNAL_getPlayerPager(final Player viewer) {
|
||||
return this.playerPages.computeIfAbsent(viewer.getUniqueId(), (uuid) -> new INTERNAL_PlayerPager());
|
||||
}
|
||||
|
||||
protected int INTERNAL_calculateElementIndex(final INTERNAL_PlayerPager pager, final int slot) {
|
||||
return (pager.pageNumber * this.buttonSlots) + slot;
|
||||
}
|
||||
|
||||
protected int INTERNAL_calculateBarIndex(final int slot) {
|
||||
return this.buttonSlots + slot;
|
||||
}
|
||||
|
||||
protected static class INTERNAL_PlayerPager {
|
||||
private int pageNumber;
|
||||
private List<SlotSettings> elements;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ElementSupplier {
|
||||
List<SlotSettings> generate(Player viewer, AbstractCivMenu menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a new pagination builder.
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private AbstractCivMenu menu;
|
||||
private ElementSupplier supplier;
|
||||
private final SlotSettings[] menuBar;
|
||||
|
||||
Builder() {
|
||||
this.menuBar = new SlotSettings[] {
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot(),
|
||||
MenuUtils.createEmptySlot()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param menu The menu to paginate.
|
||||
* @return Returns this builder.
|
||||
*/
|
||||
public Builder setMenu(final AbstractCivMenu menu) {
|
||||
this.menu = menu;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param supplier Supplier method to return all the elements to be paginated.
|
||||
* @return Returns this builder.
|
||||
*/
|
||||
public Builder setElementSupplier(final ElementSupplier supplier) {
|
||||
this.supplier = supplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The index of the menu bar element from 0-8 inclusively.
|
||||
* @param element The menu bar element to add.
|
||||
* @return Returns this builder.
|
||||
*/
|
||||
public Builder setMenuBarElement(final int index, final SlotSettings element) {
|
||||
this.menuBar[index] = element == null ? MenuUtils.createEmptySlot() : element;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a constructed menu pagination based on this builder.
|
||||
*/
|
||||
public MenuPagination build() {
|
||||
return new MenuPagination(this.menu, this.supplier, Arrays.asList(this.menuBar));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package vg.civcraft.mc.civmodcore.inventory.gui.canvas;
|
||||
|
||||
import org.ipvp.canvas.Menu;
|
||||
import org.ipvp.canvas.slot.SlotSettings;
|
||||
import org.ipvp.canvas.type.AbstractCivMenu;
|
||||
import org.ipvp.canvas.type.OpenHandler;
|
||||
import org.ipvp.canvas.type.UpdateHandler;
|
||||
|
||||
public final class MenuUtils {
|
||||
|
||||
/**
|
||||
* @return Returns an empty, unmodifiable slot.
|
||||
*/
|
||||
public static SlotSettings createEmptySlot() {
|
||||
return SlotSettings.builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a given menu's open event handler, in lieu of just replacing it, meaning that you can add handlers to
|
||||
* a menu without fear of removing any previously added functionality.
|
||||
*
|
||||
* @param menu The menu to add the handler for.
|
||||
* @param handler The handler to add.
|
||||
* @param callSuperBefore Whether or not to call the existing handler before or after the given handler.
|
||||
*/
|
||||
public static void overrideOpenHandler(final AbstractCivMenu menu,
|
||||
final OpenHandler handler,
|
||||
final boolean callSuperBefore) {
|
||||
final var findExisting = menu.getOpenHandler();
|
||||
if (findExisting.isEmpty()) {
|
||||
menu.setOpenHandler(handler);
|
||||
return;
|
||||
}
|
||||
final var existing = findExisting.get();
|
||||
menu.setOpenHandler((viewer, openedMenu) -> {
|
||||
if (callSuperBefore) {
|
||||
existing.handle(viewer, openedMenu);
|
||||
}
|
||||
handler.handle(viewer, openedMenu);
|
||||
if (!callSuperBefore) {
|
||||
existing.handle(viewer, openedMenu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a given menu's update event handler, in lieu of just replacing it, meaning that you can add handlers
|
||||
* to a menu without fear of removing any previously added functionality.
|
||||
*
|
||||
* @param menu The menu to add the handler for.
|
||||
* @param handler The handler to add.
|
||||
* @param callSuperBefore Whether or not to call the existing handler before or after the given handler.
|
||||
*/
|
||||
public static void overrideUpdateHandler(final AbstractCivMenu menu,
|
||||
final UpdateHandler handler,
|
||||
final boolean callSuperBefore) {
|
||||
final var findExisting = menu.getUpdateHandler();
|
||||
if (findExisting.isEmpty()) {
|
||||
menu.setUpdateHandler(handler);
|
||||
return;
|
||||
}
|
||||
final var existing = findExisting.get();
|
||||
menu.setUpdateHandler((viewer, openedMenu) -> {
|
||||
if (callSuperBefore) {
|
||||
existing.handle(viewer, openedMenu);
|
||||
}
|
||||
handler.handle(viewer, openedMenu);
|
||||
if (!callSuperBefore) {
|
||||
existing.handle(viewer, openedMenu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a given menu's close event handler, in lieu of just replacing it, meaning that you can add handlers to
|
||||
* a menu without fear of removing any previously added functionality.
|
||||
*
|
||||
* @param menu The menu to add the handler for.
|
||||
* @param handler The handler to add.
|
||||
* @param callSuperBefore Whether or not to call the existing handler before or after the given handler.
|
||||
*/
|
||||
public static void overrideCloseHandler(final AbstractCivMenu menu,
|
||||
final Menu.CloseHandler handler,
|
||||
final boolean callSuperBefore) {
|
||||
final var findExisting = menu.getCloseHandler();
|
||||
if (findExisting.isEmpty()) {
|
||||
menu.setCloseHandler(handler);
|
||||
return;
|
||||
}
|
||||
final var existing = findExisting.get();
|
||||
menu.setCloseHandler((viewer, openedMenu) -> {
|
||||
if (callSuperBefore) {
|
||||
existing.close(viewer, openedMenu);
|
||||
}
|
||||
handler.close(viewer, openedMenu);
|
||||
if (!callSuperBefore) {
|
||||
existing.close(viewer, openedMenu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package vg.civcraft.mc.civmodcore.inventory.gui.canvas;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.ipvp.canvas.slot.SlotSettings;
|
||||
import org.ipvp.canvas.type.AbstractCivMenu;
|
||||
import org.ipvp.canvas.type.CivChestMenu;
|
||||
import vg.civcraft.mc.civmodcore.inventory.InventoryUtils;
|
||||
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
|
||||
|
||||
/**
|
||||
* Utility to automate creating views, which have multiple pages and automatically adjust their size
|
||||
*
|
||||
*/
|
||||
public class MultiPageCanvas {
|
||||
|
||||
private final Player viewer;
|
||||
private final Component title;
|
||||
private final List<SlotSettings> slots;
|
||||
private final boolean adjustSize;
|
||||
private final SlotSettings[] extraMenuItems;
|
||||
|
||||
private AbstractCivMenu window;
|
||||
private int currentPage;
|
||||
|
||||
public MultiPageCanvas(@Nonnull final Player viewer,
|
||||
@Nullable final Component title,
|
||||
@Nonnull final List<SlotSettings> slots,
|
||||
final boolean adjustSize) {
|
||||
this.viewer = Objects.requireNonNull(viewer);
|
||||
this.title = title;
|
||||
this.slots = List.copyOf(slots);
|
||||
this.adjustSize = adjustSize;
|
||||
this.extraMenuItems = new SlotSettings[7];
|
||||
this.currentPage = 0;
|
||||
}
|
||||
|
||||
private void INTERNAL_constructInventory() {
|
||||
// Clamp the total page within the valid range
|
||||
if (this.currentPage < 0) {
|
||||
this.currentPage = 0;
|
||||
}
|
||||
final int totalPages = INTERNAL_calculateTotalPages();
|
||||
if (this.currentPage >= totalPages) {
|
||||
this.currentPage = totalPages - 1;
|
||||
}
|
||||
// Update the window instance if necessary
|
||||
final int totalRows = INTERNAL_calculateTotalRows();
|
||||
if (this.window == null) {
|
||||
this.window = new CivChestMenu(this.title, totalRows);
|
||||
}
|
||||
else {
|
||||
this.window.clear();
|
||||
if (this.adjustSize && totalRows != this.window.getDimensions().getRows()) {
|
||||
this.window = new CivChestMenu(this.title, totalRows);
|
||||
}
|
||||
}
|
||||
// Add all content slots
|
||||
final int contentOffset = this.currentPage * InventoryUtils.CHEST_5_ROWS;
|
||||
final int contentLength = Math.min(this.slots.size() - contentOffset, InventoryUtils.CHEST_5_ROWS);
|
||||
for (int i = 0; i < contentLength; i++) {
|
||||
this.window.getSlot(i).setSettings(this.slots.get(contentOffset + i));
|
||||
}
|
||||
final int totalSlots = this.window.getDimensions().getArea();
|
||||
// Back button
|
||||
if (this.currentPage > 0) {
|
||||
this.window.getSlot(totalSlots - 10).setSettings(SlotSettings.builder()
|
||||
.itemTemplate((ignored) -> {
|
||||
final var item = new ItemStack(Material.ARROW);
|
||||
ItemUtils.handleItemMeta(item, (ItemMeta meta) -> {
|
||||
meta.displayName(Component.text()
|
||||
.color(NamedTextColor.GOLD)
|
||||
.content("Go to previous page")
|
||||
.build());
|
||||
return true;
|
||||
});
|
||||
return item;
|
||||
})
|
||||
.clickHandler((clicker, click) -> {
|
||||
if (MultiPageCanvas.this.currentPage > 0) {
|
||||
MultiPageCanvas.this.currentPage--;
|
||||
}
|
||||
showScreen();
|
||||
})
|
||||
.build());
|
||||
}
|
||||
// Next button
|
||||
if (this.currentPage < totalPages) {
|
||||
this.window.getSlot(totalSlots - 1).setSettings(SlotSettings.builder()
|
||||
.itemTemplate((ignored) -> {
|
||||
final var item = new ItemStack(Material.ARROW);
|
||||
ItemUtils.handleItemMeta(item, (ItemMeta meta) -> {
|
||||
meta.displayName(Component.text()
|
||||
.color(NamedTextColor.GOLD)
|
||||
.content("Go to next page")
|
||||
.build());
|
||||
return true;
|
||||
});
|
||||
return item;
|
||||
})
|
||||
.clickHandler((clicker, click) -> {
|
||||
if (MultiPageCanvas.this.currentPage < totalPages) {
|
||||
MultiPageCanvas.this.currentPage++;
|
||||
}
|
||||
showScreen();
|
||||
})
|
||||
.build());
|
||||
}
|
||||
// Other menu items
|
||||
for (int i = 0; i < this.extraMenuItems.length; i++) {
|
||||
final SlotSettings slot = this.extraMenuItems[i];
|
||||
if (slot == null) {
|
||||
continue;
|
||||
}
|
||||
this.window.getSlot(totalSlots - 9 + i).setSettings(slot);
|
||||
}
|
||||
}
|
||||
|
||||
protected int INTERNAL_calculateTotalPages() {
|
||||
final int size = this.slots.size();
|
||||
int pages = size / InventoryUtils.CHEST_5_ROWS;
|
||||
if ((size % InventoryUtils.CHEST_5_ROWS) > 0) {
|
||||
pages++;
|
||||
}
|
||||
return Math.max(pages, 0);
|
||||
}
|
||||
|
||||
private int INTERNAL_calculateTotalRows() {
|
||||
if (!this.adjustSize) {
|
||||
return 6;
|
||||
}
|
||||
final int clicks = this.slots.size();
|
||||
if (clicks >= InventoryUtils.CHEST_5_ROWS) {
|
||||
return 6;
|
||||
}
|
||||
final int rows = (clicks / 9) + ((clicks % 9) > 0 ? 1 : 0);
|
||||
return Math.max(rows, 2); // Always have at least 2 rows
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the current page
|
||||
*/
|
||||
public void showScreen() {
|
||||
INTERNAL_constructInventory();
|
||||
this.window.open(this.viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows setting a menu slot at the bottom of the GUI. Will not update the inventory, you'll need to do that
|
||||
* manually with {@link #showScreen()}.
|
||||
*
|
||||
* @param slot The slot to set.
|
||||
* @param index The index of the slot. Must be between 0 and 6 inclusively.
|
||||
*/
|
||||
public void setMenuSlot(@Nullable final SlotSettings slot, final int index) {
|
||||
if (index < 0 || index > 6) {
|
||||
throw new IllegalArgumentException("Index for MultiPageView menu must be between 0 and 6");
|
||||
}
|
||||
this.extraMenuItems[index] = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly changes the page of this view. Will not update the inventory, you'll need to do that manually with
|
||||
* {@link #showScreen()}.
|
||||
*
|
||||
* @param page The page to set.
|
||||
*/
|
||||
public void setPage(final int page) {
|
||||
this.currentPage = page;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user