diff --git a/pom.xml b/pom.xml index f2ee223..c136d46 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ su.nightexpress.nightcore nightcore - 2.7.2 + 2.7.3 21 diff --git a/src/main/java/su/nightexpress/nightcore/NightCore.java b/src/main/java/su/nightexpress/nightcore/NightCore.java index c0fb148..42b764f 100644 --- a/src/main/java/su/nightexpress/nightcore/NightCore.java +++ b/src/main/java/su/nightexpress/nightcore/NightCore.java @@ -8,7 +8,6 @@ import su.nightexpress.nightcore.core.CoreManager; import su.nightexpress.nightcore.core.CorePerms; import su.nightexpress.nightcore.core.command.CoreCommands; -import su.nightexpress.nightcore.dialog.Dialog; import su.nightexpress.nightcore.integration.VaultHook; import su.nightexpress.nightcore.language.LangAssets; import su.nightexpress.nightcore.util.Plugins; @@ -55,7 +54,6 @@ public void enable() { public void disable() { this.coreManager.shutdown(); - Dialog.shutdown(); if (Plugins.hasVault()) { VaultHook.shutdown(); } diff --git a/src/main/java/su/nightexpress/nightcore/api/event/MenuOpenEvent.java b/src/main/java/su/nightexpress/nightcore/api/event/MenuOpenEvent.java new file mode 100644 index 0000000..9a10703 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/api/event/MenuOpenEvent.java @@ -0,0 +1,53 @@ +package su.nightexpress.nightcore.api.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.Menu; + +public class MenuOpenEvent extends Event implements Cancellable { + + public static final HandlerList HANDLER_LIST = new HandlerList(); + + private final Player player; + private final Menu menu; + + private boolean cancelled; + + public MenuOpenEvent(@NotNull Player player, @NotNull Menu menu) { + this.player = player; + this.menu = menu; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + @NotNull + public Player getPlayer() { + return this.player; + } + + @NotNull + public Menu getMenu() { + return this.menu; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/config/ConfigValue.java b/src/main/java/su/nightexpress/nightcore/config/ConfigValue.java index d525877..a39e30f 100644 --- a/src/main/java/su/nightexpress/nightcore/config/ConfigValue.java +++ b/src/main/java/su/nightexpress/nightcore/config/ConfigValue.java @@ -5,8 +5,8 @@ import org.jetbrains.annotations.Nullable; import su.nightexpress.nightcore.util.StringUtil; import su.nightexpress.nightcore.util.TriFunction; -import su.nightexpress.nightcore.util.bukkit.NightItem; import su.nightexpress.nightcore.util.bukkit.NightSound; +import su.nightexpress.nightcore.util.bukkit.NightItem; import su.nightexpress.nightcore.util.wrapper.UniFormatter; import su.nightexpress.nightcore.util.wrapper.UniParticle; import su.nightexpress.nightcore.util.wrapper.UniSound; @@ -113,6 +113,7 @@ public static ConfigValue> create(@NotNull String path, @NotNull Set } @NotNull + @Deprecated public static ConfigValue create(@NotNull String path, @NotNull ItemStack defaultValue, @Nullable String... description) { return create(path, FileConfig::getItem, FileConfig::setItem, defaultValue, description); } diff --git a/src/main/java/su/nightexpress/nightcore/config/FileConfig.java b/src/main/java/su/nightexpress/nightcore/config/FileConfig.java index 14f4600..46ed49e 100644 --- a/src/main/java/su/nightexpress/nightcore/config/FileConfig.java +++ b/src/main/java/su/nightexpress/nightcore/config/FileConfig.java @@ -19,8 +19,8 @@ import org.jetbrains.annotations.Nullable; import su.nightexpress.nightcore.NightCorePlugin; import su.nightexpress.nightcore.util.*; -import su.nightexpress.nightcore.util.bukkit.NightItem; import su.nightexpress.nightcore.util.text.NightMessage; +import su.nightexpress.nightcore.util.bukkit.NightItem; import java.io.File; import java.io.IOException; diff --git a/src/main/java/su/nightexpress/nightcore/core/CoreLang.java b/src/main/java/su/nightexpress/nightcore/core/CoreLang.java index e270385..6cb0dc6 100644 --- a/src/main/java/su/nightexpress/nightcore/core/CoreLang.java +++ b/src/main/java/su/nightexpress/nightcore/core/CoreLang.java @@ -7,6 +7,7 @@ import su.nightexpress.nightcore.language.entry.LangItem; import su.nightexpress.nightcore.language.entry.LangString; import su.nightexpress.nightcore.language.entry.LangText; +import su.nightexpress.nightcore.ui.dialog.DialogManager; import static su.nightexpress.nightcore.util.Placeholders.*; import static su.nightexpress.nightcore.util.text.tag.Tags.*; @@ -140,6 +141,7 @@ public class CoreLang { " " ); + @Deprecated public static final LangText EDITOR_ACTION_EXIT = LangText.of("Editor.Action.Exit", TAG_NO_PREFIX, "", @@ -151,9 +153,30 @@ public class CoreLang { + " to leave input mode."), ""); + public static final LangString DIALOG_HEADER = LangString.of("Dialog.Header", + LIGHT_YELLOW.enclose(GENERIC_TIME)); + + public static final LangString DIALOG_DEFAULT_PROMPT = LangString.of("Dialog.DefaultPrompt", + LIGHT_GRAY.enclose("Enter " + LIGHT_GREEN.enclose("[Value]"))); + + public static final LangText DIALOG_INFO_EXIT = LangText.of("Dialog.Info.Exit", + TAG_NO_PREFIX, + "", + GRAY.enclose("Click " + + CLICK.encloseRun( + HOVER.encloseHint(GREEN.enclose("[Here]"), GRAY.enclose("Click to cancel.")), + "/" + DialogManager.EXIT + ) + + " to leave input mode."), + ""); + + @Deprecated public static final LangString EDITOR_INPUT_HEADER_MAIN = LangString.of("Editor.Input.Header.Main", GREEN.enclose(BOLD.enclose("Input Mode"))); + @Deprecated public static final LangString EDITOR_INPUT_HEADER_ERROR = LangString.of("Editor.Input.Header.Error", RED.enclose(BOLD.enclose("ERROR"))); + @Deprecated public static final LangString EDITOR_INPUT_ERROR_NOT_INTEGER = LangString.of("Editor.Input.Error.NotInteger", GRAY.enclose("Expecting " + RED.enclose("whole") + " number!")); + @Deprecated public static final LangString EDITOR_INPUT_ERROR_GENERIC = LangString.of("Editor.Input.Error.Generic", GRAY.enclose("Invalid value!")); public static final LangItem EDITOR_ITEM_CLOSE = LangItem.of("Editor.Generic.Close", LIGHT_RED.enclose(BOLD.enclose("Exit"))); diff --git a/src/main/java/su/nightexpress/nightcore/core/CoreManager.java b/src/main/java/su/nightexpress/nightcore/core/CoreManager.java index 86258c2..f924f9d 100644 --- a/src/main/java/su/nightexpress/nightcore/core/CoreManager.java +++ b/src/main/java/su/nightexpress/nightcore/core/CoreManager.java @@ -8,6 +8,11 @@ import su.nightexpress.nightcore.manager.AbstractManager; import su.nightexpress.nightcore.menu.MenuListener; import su.nightexpress.nightcore.menu.impl.AbstractMenu; +import su.nightexpress.nightcore.ui.UIListener; +import su.nightexpress.nightcore.ui.dialog.DialogManager; +import su.nightexpress.nightcore.ui.menu.MenuRegistry; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.util.TimeUtil; import java.util.HashSet; @@ -22,18 +27,21 @@ protected void onLoad() { this.addListener(new CoreListener(this.plugin)); this.addListener(new DialogListener(this.plugin)); this.addListener(new MenuListener(this.plugin)); + this.addListener(new UIListener(this.plugin)); - this.addAsyncTask(this::tickDialogs, 1); + this.addTask(this::tickDialogs, 1); this.addTask(this::tickMenus, 1); } @Override protected void onShutdown() { - + Dialog.shutdown(); + DialogManager.shutdown(); } private void tickDialogs() { Dialog.checkTimeOut(); + DialogManager.tickDialogs(); } private void tickMenus() { @@ -43,5 +51,12 @@ private void tickMenus() { menu.getOptions().setLastAutoRefresh(System.currentTimeMillis()); } }); + + MenuRegistry.getViewers().stream().map(MenuViewer::getMenu).distinct().forEach(menu -> { + if (menu.isReadyToRefresh()) { + menu.flush(); + menu.setAutoRefreshDate(TimeUtil.createFutureTimestamp(menu.getAutoRefreshInterval())); + } + }); } } diff --git a/src/main/java/su/nightexpress/nightcore/dialog/Dialog.java b/src/main/java/su/nightexpress/nightcore/dialog/Dialog.java index acecfa1..6f0ae7f 100644 --- a/src/main/java/su/nightexpress/nightcore/dialog/Dialog.java +++ b/src/main/java/su/nightexpress/nightcore/dialog/Dialog.java @@ -19,6 +19,7 @@ import static su.nightexpress.nightcore.util.text.tag.Tags.*; +@Deprecated public class Dialog { private static final Map DIALOG_MAP = new ConcurrentHashMap<>(); diff --git a/src/main/java/su/nightexpress/nightcore/dialog/DialogHandler.java b/src/main/java/su/nightexpress/nightcore/dialog/DialogHandler.java index 5d01b97..e9be184 100644 --- a/src/main/java/su/nightexpress/nightcore/dialog/DialogHandler.java +++ b/src/main/java/su/nightexpress/nightcore/dialog/DialogHandler.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.NotNull; +@Deprecated public interface DialogHandler { boolean onInput(@NotNull Dialog dialog, @NotNull WrappedInput input); diff --git a/src/main/java/su/nightexpress/nightcore/dialog/DialogListener.java b/src/main/java/su/nightexpress/nightcore/dialog/DialogListener.java index 781b853..13649b1 100644 --- a/src/main/java/su/nightexpress/nightcore/dialog/DialogListener.java +++ b/src/main/java/su/nightexpress/nightcore/dialog/DialogListener.java @@ -13,6 +13,7 @@ import java.util.HashSet; +@Deprecated public class DialogListener extends AbstractListener { public DialogListener(@NotNull NightCore plugin) { diff --git a/src/main/java/su/nightexpress/nightcore/dialog/WrappedInput.java b/src/main/java/su/nightexpress/nightcore/dialog/WrappedInput.java index 5a466ed..a2fd8f2 100644 --- a/src/main/java/su/nightexpress/nightcore/dialog/WrappedInput.java +++ b/src/main/java/su/nightexpress/nightcore/dialog/WrappedInput.java @@ -9,6 +9,7 @@ import su.nightexpress.nightcore.util.wrapper.UniDouble; import su.nightexpress.nightcore.util.wrapper.UniInt; +@Deprecated public class WrappedInput { private final String text; diff --git a/src/main/java/su/nightexpress/nightcore/language/entry/LangItem.java b/src/main/java/su/nightexpress/nightcore/language/entry/LangItem.java index 52427ff..b3e289b 100644 --- a/src/main/java/su/nightexpress/nightcore/language/entry/LangItem.java +++ b/src/main/java/su/nightexpress/nightcore/language/entry/LangItem.java @@ -5,9 +5,9 @@ import su.nightexpress.nightcore.NightCorePlugin; import su.nightexpress.nightcore.config.FileConfig; import su.nightexpress.nightcore.util.ItemUtil; -import su.nightexpress.nightcore.util.bukkit.NightItem; import su.nightexpress.nightcore.util.text.NightMessage; import su.nightexpress.nightcore.util.text.TextRoot; +import su.nightexpress.nightcore.util.bukkit.NightItem; import java.util.ArrayList; import java.util.Arrays; @@ -61,6 +61,7 @@ public void apply(@NotNull ItemStack item) { }); } + @Deprecated public void apply(@NotNull NightItem item) { item.setDisplayName(this.localizedName); item.setLore(this.localizedLore); diff --git a/src/main/java/su/nightexpress/nightcore/menu/MenuListener.java b/src/main/java/su/nightexpress/nightcore/menu/MenuListener.java index 9b869db..8d9beb0 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/MenuListener.java +++ b/src/main/java/su/nightexpress/nightcore/menu/MenuListener.java @@ -17,6 +17,7 @@ import su.nightexpress.nightcore.menu.click.ClickResult; import su.nightexpress.nightcore.menu.impl.AbstractMenu; +@Deprecated public class MenuListener extends AbstractListener { public MenuListener(@NotNull NightCore core) { diff --git a/src/main/java/su/nightexpress/nightcore/menu/MenuOptions.java b/src/main/java/su/nightexpress/nightcore/menu/MenuOptions.java index 30241a2..f55e859 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/MenuOptions.java +++ b/src/main/java/su/nightexpress/nightcore/menu/MenuOptions.java @@ -8,6 +8,7 @@ import java.util.function.UnaryOperator; +@Deprecated public class MenuOptions { private String title; diff --git a/src/main/java/su/nightexpress/nightcore/menu/MenuSize.java b/src/main/java/su/nightexpress/nightcore/menu/MenuSize.java index af531b3..da11de0 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/MenuSize.java +++ b/src/main/java/su/nightexpress/nightcore/menu/MenuSize.java @@ -1,5 +1,6 @@ package su.nightexpress.nightcore.menu; +@Deprecated public enum MenuSize { CHEST_9(9), diff --git a/src/main/java/su/nightexpress/nightcore/menu/MenuViewer.java b/src/main/java/su/nightexpress/nightcore/menu/MenuViewer.java index ab566eb..f471735 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/MenuViewer.java +++ b/src/main/java/su/nightexpress/nightcore/menu/MenuViewer.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import su.nightexpress.nightcore.util.Version; +@Deprecated public class MenuViewer { private final Player player; @@ -16,7 +17,7 @@ public class MenuViewer { private int page; private int pages; private long lastClickTime; - private boolean updateTitle; + private boolean updateTitle; public MenuViewer(@NotNull Player player) { this.player = player; @@ -47,7 +48,7 @@ public boolean canClickAgain(long cooldown) { @NotNull public Player getPlayer() { - return player; + return this.player; } @Nullable diff --git a/src/main/java/su/nightexpress/nightcore/menu/api/AutoFill.java b/src/main/java/su/nightexpress/nightcore/menu/api/AutoFill.java index efc537e..4fff866 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/api/AutoFill.java +++ b/src/main/java/su/nightexpress/nightcore/menu/api/AutoFill.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.function.Function; +@Deprecated public class AutoFill { private int[] slots; diff --git a/src/main/java/su/nightexpress/nightcore/menu/api/AutoFilled.java b/src/main/java/su/nightexpress/nightcore/menu/api/AutoFilled.java index a231b17..060a480 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/api/AutoFilled.java +++ b/src/main/java/su/nightexpress/nightcore/menu/api/AutoFilled.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; +@Deprecated public interface AutoFilled extends Menu { default boolean open(@NotNull Player player, int page) { diff --git a/src/main/java/su/nightexpress/nightcore/menu/api/Menu.java b/src/main/java/su/nightexpress/nightcore/menu/api/Menu.java index a1f5a4e..33d5045 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/api/Menu.java +++ b/src/main/java/su/nightexpress/nightcore/menu/api/Menu.java @@ -15,6 +15,7 @@ import java.util.*; +@Deprecated public interface Menu { void clear(); diff --git a/src/main/java/su/nightexpress/nightcore/menu/click/ClickAction.java b/src/main/java/su/nightexpress/nightcore/menu/click/ClickAction.java index e826ff7..61cb28d 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/click/ClickAction.java +++ b/src/main/java/su/nightexpress/nightcore/menu/click/ClickAction.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull; import su.nightexpress.nightcore.menu.MenuViewer; +@Deprecated public interface ClickAction { void onClick(@NotNull MenuViewer viewer, @NotNull InventoryClickEvent event); diff --git a/src/main/java/su/nightexpress/nightcore/menu/click/ClickResult.java b/src/main/java/su/nightexpress/nightcore/menu/click/ClickResult.java index fa4741f..a26f97d 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/click/ClickResult.java +++ b/src/main/java/su/nightexpress/nightcore/menu/click/ClickResult.java @@ -3,6 +3,7 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +@Deprecated public class ClickResult { private final int slot; diff --git a/src/main/java/su/nightexpress/nightcore/menu/click/ClickType.java b/src/main/java/su/nightexpress/nightcore/menu/click/ClickType.java index 11d2e0c..847d5e5 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/click/ClickType.java +++ b/src/main/java/su/nightexpress/nightcore/menu/click/ClickType.java @@ -3,6 +3,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; +@Deprecated public enum ClickType { LEFT, RIGHT, SHIFT_LEFT, SHIFT_RIGHT, diff --git a/src/main/java/su/nightexpress/nightcore/menu/impl/AbstractMenu.java b/src/main/java/su/nightexpress/nightcore/menu/impl/AbstractMenu.java index ad78a8d..368ade1 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/impl/AbstractMenu.java +++ b/src/main/java/su/nightexpress/nightcore/menu/impl/AbstractMenu.java @@ -24,6 +24,7 @@ import java.util.*; +@Deprecated public abstract class AbstractMenu

implements Menu { public static final Map PLAYER_MENUS = new HashMap<>(); diff --git a/src/main/java/su/nightexpress/nightcore/menu/impl/ConfigMenu.java b/src/main/java/su/nightexpress/nightcore/menu/impl/ConfigMenu.java index 24cf41e..8a8379c 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/impl/ConfigMenu.java +++ b/src/main/java/su/nightexpress/nightcore/menu/impl/ConfigMenu.java @@ -13,12 +13,13 @@ import su.nightexpress.nightcore.menu.item.ItemHandler; import su.nightexpress.nightcore.menu.item.MenuItem; import su.nightexpress.nightcore.util.*; -import su.nightexpress.nightcore.util.bukkit.NightItem; import su.nightexpress.nightcore.util.text.NightMessage; +import su.nightexpress.nightcore.util.bukkit.NightItem; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +@Deprecated public abstract class ConfigMenu

extends AbstractMenu

{ protected static final String DEFAULT_ITEM_SECTION = "Content"; diff --git a/src/main/java/su/nightexpress/nightcore/menu/impl/EditorMenu.java b/src/main/java/su/nightexpress/nightcore/menu/impl/EditorMenu.java index ce045a4..11f7a8f 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/impl/EditorMenu.java +++ b/src/main/java/su/nightexpress/nightcore/menu/impl/EditorMenu.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; +@Deprecated public abstract class EditorMenu

extends AbstractMenu

implements Linked { protected final ViewLink link; diff --git a/src/main/java/su/nightexpress/nightcore/menu/item/ItemHandler.java b/src/main/java/su/nightexpress/nightcore/menu/item/ItemHandler.java index aa57b1b..7cbbd66 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/item/ItemHandler.java +++ b/src/main/java/su/nightexpress/nightcore/menu/item/ItemHandler.java @@ -12,6 +12,7 @@ import java.util.UUID; import java.util.function.Predicate; +@Deprecated public class ItemHandler { public static final String RETURN = "return"; diff --git a/src/main/java/su/nightexpress/nightcore/menu/item/ItemOptions.java b/src/main/java/su/nightexpress/nightcore/menu/item/ItemOptions.java index d61a645..bbd559e 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/item/ItemOptions.java +++ b/src/main/java/su/nightexpress/nightcore/menu/item/ItemOptions.java @@ -9,6 +9,7 @@ import java.util.function.BiConsumer; import java.util.function.Predicate; +@Deprecated public class ItemOptions { private Predicate visibilityPolicy; diff --git a/src/main/java/su/nightexpress/nightcore/menu/item/MenuItem.java b/src/main/java/su/nightexpress/nightcore/menu/item/MenuItem.java index bb47bc6..0af113d 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/item/MenuItem.java +++ b/src/main/java/su/nightexpress/nightcore/menu/item/MenuItem.java @@ -6,6 +6,7 @@ import su.nightexpress.nightcore.menu.click.ClickAction; import su.nightexpress.nightcore.util.bukkit.NightItem; +@Deprecated public class MenuItem { protected NightItem item; @@ -92,7 +93,7 @@ public MenuItem setItem(@NotNull NightItem item) { @NotNull @Deprecated public ItemStack getItemStack() { - return this.itemStack == null ? this.item.getTranslated() : new ItemStack(this.itemStack); + return this.itemStack == null ? this.item.getItemStack() : new ItemStack(this.itemStack); //return new ItemStack(this.itemStack); } diff --git a/src/main/java/su/nightexpress/nightcore/menu/link/Linked.java b/src/main/java/su/nightexpress/nightcore/menu/link/Linked.java index ba5c94c..58dd244 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/link/Linked.java +++ b/src/main/java/su/nightexpress/nightcore/menu/link/Linked.java @@ -5,6 +5,7 @@ import su.nightexpress.nightcore.menu.MenuViewer; import su.nightexpress.nightcore.menu.api.Menu; +@Deprecated public interface Linked extends Menu { @NotNull ViewLink getLink(); diff --git a/src/main/java/su/nightexpress/nightcore/menu/link/ViewLink.java b/src/main/java/su/nightexpress/nightcore/menu/link/ViewLink.java index 3965a9d..dbd9a8d 100644 --- a/src/main/java/su/nightexpress/nightcore/menu/link/ViewLink.java +++ b/src/main/java/su/nightexpress/nightcore/menu/link/ViewLink.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.WeakHashMap; +@Deprecated public class ViewLink { private final Map map; diff --git a/src/main/java/su/nightexpress/nightcore/ui/UIListener.java b/src/main/java/su/nightexpress/nightcore/ui/UIListener.java new file mode 100644 index 0000000..3b92602 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/UIListener.java @@ -0,0 +1,126 @@ +package su.nightexpress.nightcore.ui; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.NightCore; +import su.nightexpress.nightcore.core.CoreConfig; +import su.nightexpress.nightcore.manager.AbstractListener; +import su.nightexpress.nightcore.ui.dialog.Dialog; +import su.nightexpress.nightcore.ui.dialog.DialogInput; +import su.nightexpress.nightcore.ui.dialog.DialogManager; +import su.nightexpress.nightcore.ui.menu.MenuRegistry; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.ui.menu.click.ClickResult; +import su.nightexpress.nightcore.util.NumberUtil; + +public class UIListener extends AbstractListener { + + public UIListener(@NotNull NightCore plugin) { + super(plugin); + } + + private void handleDialogInput(@NotNull Player player, @NotNull Dialog dialog, @NotNull DialogInput input) { + // Jump back to the main thread from async chat thread. + this.plugin.runTask(task -> { + if (input.getTextRaw().equalsIgnoreCase(DialogManager.EXIT) || dialog.getHandler().handle(input)) { + DialogManager.stopDialog(player); + } + }); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + DialogManager.stopDialog(player); + + MenuRegistry.closeMenu(player); + MenuRegistry.terminate(player); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onDialogChatText(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + Dialog dialog = DialogManager.getDialog(player); + if (dialog == null) return; + + event.getRecipients().clear(); + event.setCancelled(true); + + DialogInput input = new DialogInput(event.getMessage()); + this.handleDialogInput(player, dialog, input); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onDialogChatCommand(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + Dialog dialog = DialogManager.getDialog(player); + if (dialog == null) return; + + event.setCancelled(true); + + String raw = event.getMessage(); + String message = raw.substring(1); + if (message.startsWith(DialogManager.VALUES)) { + String[] split = message.split(" "); + int page = split.length >= 2 ? NumberUtil.getIntegerAbs(split[1]) : 1; + DialogManager.displaySuggestions(dialog, page); + return; + } + + DialogInput input = new DialogInput(message); + this.handleDialogInput(player, dialog, input); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onMenuItemClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + + MenuViewer viewer = MenuRegistry.getViewer(player); + if (viewer == null) return; + + if (!viewer.canClickAgain(CoreConfig.MENU_CLICK_COOLDOWN.get())) { + event.setCancelled(true); + return; + } + + Inventory inventory = event.getInventory(); + ItemStack item = event.getCurrentItem(); + + int slot = event.getRawSlot(); + boolean isMenu = slot < inventory.getSize(); + ClickResult result = new ClickResult(slot, item, isMenu); + + viewer.getMenu().onClick(viewer, result, event); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onMenuItemDrag(InventoryDragEvent event) { + Player player = (Player) event.getWhoClicked(); + + MenuViewer viewer = MenuRegistry.getViewer(player); + if (viewer == null) return; + + viewer.getMenu().onDrag(viewer, event); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onMenuClose(InventoryCloseEvent event) { + Player player = (Player) event.getPlayer(); + + MenuViewer viewer = MenuRegistry.getViewer(player); + if (viewer == null) return; + + viewer.getMenu().onClose(viewer, event); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/dialog/Dialog.java b/src/main/java/su/nightexpress/nightcore/ui/dialog/Dialog.java new file mode 100644 index 0000000..b8ca61f --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/dialog/Dialog.java @@ -0,0 +1,173 @@ +package su.nightexpress.nightcore.ui.dialog; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.core.CoreLang; +import su.nightexpress.nightcore.language.entry.LangString; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.util.TimeUtil; +import su.nightexpress.nightcore.util.text.TextRoot; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class Dialog { + + private static final int DEFAULT_TIMEOUT = 30; + + private final Player player; + private final String prompt; + private final DialogHandler handler; + private final List suggestions; + private final boolean suggestionAutoRun; + private final long timeoutDate; + + private Menu lastMenu; + private int lastPage; + + public Dialog(@NotNull Player player, + @NotNull String prompt, + @NotNull DialogHandler handler, + @Nullable List suggestions, + boolean suggestionAutoRun, + long timeoutDate) { + this.player = player; + this.prompt = prompt; + this.handler = handler; + this.suggestions = suggestions == null ? null : suggestions.stream().sorted(String::compareTo).collect(Collectors.toCollection(ArrayList::new)); + this.suggestionAutoRun = suggestionAutoRun; + this.timeoutDate = timeoutDate; + } + + public boolean isExpired() { + return TimeUtil.isPassed(this.timeoutDate); + } + + @NotNull + public Player getPlayer() { + return this.player; + } + + @NotNull + public DialogHandler getHandler() { + return this.handler; + } + + @NotNull + public String getPrompt() { + return this.prompt; + } + + @Nullable + public Menu getLastMenu() { + return this.lastMenu; + } + + @NotNull + public Dialog setLastMenu(@Nullable Menu lastMenu) { + this.lastMenu = lastMenu; + return this; + } + + public int getLastPage() { + return this.lastPage; + } + + public Dialog setLastPage(int lastPage) { + this.lastPage = Math.max(1, lastPage); + return this; + } + + @Nullable + public List getSuggestions() { + return this.suggestions; + } + + public boolean isSuggestionAutoRun() { + return this.suggestionAutoRun; + } + + public long getTimeoutDate() { + return this.timeoutDate; + } + + @NotNull + public static Builder builder(@NotNull MenuViewer viewer, @NotNull LangString prompt, @NotNull DialogHandler handler) { + return builder(viewer.getPlayer(), prompt, handler); + } + + @NotNull + public static Builder builder(@NotNull Player player, @NotNull LangString prompt, @NotNull DialogHandler handler) { + return builder(player, handler).setPrompt(prompt); + } + + @NotNull + public static Builder builder(@NotNull MenuViewer viewer, @NotNull DialogHandler handler) { + return builder(viewer.getPlayer(), handler); + } + + @NotNull + public static Builder builder(@NotNull Player player, @NotNull DialogHandler handler) { + return new Builder(player, handler); + } + + public static class Builder { + + private final Player player; + private final DialogHandler handler; + + private String prompt; + private List suggestions; + private boolean suggestionAutoRun; + private int timeout; + + public Builder(@NotNull Player player, @NotNull DialogHandler handler) { + this.player = player; + this.handler = handler; + this.setPrompt(CoreLang.DIALOG_DEFAULT_PROMPT.getString()); + this.setTimeout(DEFAULT_TIMEOUT); + } + + @NotNull + public Player getPlayer() { + return this.player; + } + + @NotNull + public Dialog build() { + return new Dialog(this.player, this.prompt, this.handler, this.suggestions, this.suggestionAutoRun, TimeUtil.createFutureTimestamp(this.timeout)); + } + + public void initialize() { + DialogManager.startDialog(this); + } + + public Builder setPrompt(@NotNull TextRoot text) { + return this.setPrompt(text.getString()); + } + + public Builder setPrompt(@NotNull LangString text) { + return this.setPrompt(text.getString()); + } + + public Builder setPrompt(@NotNull String prompt) { + this.prompt = prompt; + return this; + } + + public Builder setSuggestions(@NotNull Collection suggestions, boolean autoRun) { + this.suggestions = new ArrayList<>(suggestions); + this.suggestionAutoRun = autoRun; + return this; + } + + public Builder setTimeout(int timeout) { + this.timeout = timeout; + return this; + } + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogHandler.java b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogHandler.java new file mode 100644 index 0000000..f8fad39 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogHandler.java @@ -0,0 +1,8 @@ +package su.nightexpress.nightcore.ui.dialog; + +import org.jetbrains.annotations.NotNull; + +public interface DialogHandler { + + boolean handle(@NotNull DialogInput input); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogInput.java b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogInput.java new file mode 100644 index 0000000..fbd305d --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogInput.java @@ -0,0 +1,93 @@ +package su.nightexpress.nightcore.ui.dialog; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.util.NumberUtil; +import su.nightexpress.nightcore.util.StringUtil; +import su.nightexpress.nightcore.util.text.NightMessage; + +public class DialogInput { + + private final String text; + private final String textRaw; + private final String textLegacy; + +// public DialogInput(@NotNull AsyncPlayerChatEvent event) { +// this(event.getMessage()); +// } + + public DialogInput(@NotNull String text) { + this.text = text; + this.textRaw = NightMessage.stripAll(text); + this.textLegacy = NightMessage.asLegacy(text); + } + + public int asIntAbs() { + return this.asIntAbs(0); + } + + public int asIntAbs(int def) { + return NumberUtil.getIntegerAbs(this.textRaw, def); + } + + public int asInt(int def) { + return NumberUtil.getAnyInteger(this.textRaw, def); + } + + public double asDoubleAbs() { + return this.asDoubleAbs(0D); + } + + public double asDoubleAbs(double def) { + return NumberUtil.getDoubleAbs(this.textRaw, def); + } + + public double asDouble(double def) { + return NumberUtil.getAnyDouble(this.textRaw, def); + } + +// @NotNull +// public UniDouble asUniDouble() { +// return this.asUniDouble(0, 0); +// } +// +// @NotNull +// public UniDouble asUniDouble(double min, double max) { +// String[] split = this.textRaw.split(" "); +// return UniDouble.of(NumberUtil.getDoubleAbs(split[0], min), NumberUtil.getDoubleAbs(split.length >= 2 ? split[1] : split[0], max)); +// } +// +// @NotNull +// public UniInt asUniInt() { +// return this.asUniDouble().asInt(); +// } +// +// public double asAnyDouble(double def) { +// return NumberUtil.getAnyDouble(this.textRaw, def); +// } + + @Nullable + public > E asEnum(@NotNull Class clazz) { + return StringUtil.getEnum(this.textRaw, clazz).orElse(null); + } + + @NotNull + public > E asEnum(@NotNull Class clazz, @NotNull E def) { + return StringUtil.getEnum(this.textRaw, clazz).orElse(def); + } + + @NotNull + public String getText() { + return this.text; + } + + @NotNull + public String getTextRaw() { + return this.textRaw; + } + + @NotNull + public String getTextLegacy() { + return this.textLegacy; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogManager.java b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogManager.java new file mode 100644 index 0000000..c261394 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/dialog/DialogManager.java @@ -0,0 +1,157 @@ +package su.nightexpress.nightcore.ui.dialog; + +import net.md_5.bungee.api.chat.ClickEvent; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.core.CoreLang; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.MenuRegistry; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.util.Placeholders; +import su.nightexpress.nightcore.util.Players; +import su.nightexpress.nightcore.util.TimeUtil; +import su.nightexpress.nightcore.util.text.NightMessage; + +import java.util.*; + +import static su.nightexpress.nightcore.util.text.tag.Tags.*; + +public class DialogManager { + + private static final Map DIALOG_MAP = new HashMap<>(); + + public static final String EXIT = "#exit"; + public static final String VALUES = "#values"; + + public static void shutdown() { + DIALOG_MAP.clear(); + } + + @NotNull + public static Set

getDialogs() { + return new HashSet<>(DIALOG_MAP.values()); + } + + public static void tickDialogs() { + getDialogs().forEach(DialogManager::tickDialog); + } + + private static void tickDialog(@NotNull Dialog dialog) { + if (dialog.isExpired()) { + stopDialog(dialog); + return; + } + + displayPrompt(dialog, 0); + } + + private static void displayPrompt(@NotNull Dialog dialog, int fade) { + Player player = dialog.getPlayer(); + String title = NightMessage.asLegacy(CoreLang.DIALOG_HEADER.getString().replace(Placeholders.GENERIC_TIME, TimeUtil.formatDuration(dialog.getTimeoutDate()))); + String sub = NightMessage.asLegacy(dialog.getPrompt()); + + player.sendTitle(title, sub, fade, 40, 20); + } + + public static boolean isInDialog(@NotNull Player player) { + return getDialog(player) != null; + } + + @Nullable + public static Dialog getDialog(@NotNull Player player) { + return DIALOG_MAP.get(player.getUniqueId()); + } + + public static void startDialog(@NotNull Dialog.Builder builder) { + startDialog(builder.build()); + } + + public static void startDialog(@NotNull Dialog dialog) { + Player player = dialog.getPlayer(); + + MenuViewer viewer = MenuRegistry.getViewer(player); + if (viewer != null) { + dialog.setLastMenu(viewer.getMenu()); + dialog.setLastPage(viewer.getPage()); + } + + displaySuggestions(dialog, 1); + displayPrompt(dialog, 20); + + DIALOG_MAP.put(player.getUniqueId(), dialog); + CoreLang.DIALOG_INFO_EXIT.getMessage().send(player); + } + + public static void stopDialog(@NotNull Player player) { + Dialog dialog = getDialog(player); + if (dialog == null) return; + + stopDialog(dialog); + } + + public static void stopDialog(@NotNull Dialog dialog) { + Player player = dialog.getPlayer(); + Menu menu = dialog.getLastMenu(); + if (menu != null) { + menu.flush(player, viewer -> viewer.setPage(dialog.getLastPage())); + } + + DIALOG_MAP.remove(player.getUniqueId()); + } + + public static void displaySuggestions(@NotNull Dialog dialog, int page) { + List suggestions = dialog.getSuggestions(); + if (suggestions == null || suggestions.isEmpty()) return; + + boolean autoRun = dialog.isSuggestionAutoRun(); + + int perPage = 10; + int pages = (int) Math.ceil((double) suggestions.size() / (double) perPage); + if (page < 1) page = 1; + else if (page > pages) page = pages; + int skip = (page - 1) * perPage; + + boolean isLastPage = page == pages; + boolean isFirstPage = page == 1; + List items = suggestions.stream().skip(skip).limit(perPage).toList(); + ClickEvent.Action action = autoRun ? ClickEvent.Action.RUN_COMMAND : ClickEvent.Action.SUGGEST_COMMAND; + + StringBuilder builder = new StringBuilder() + .append(ORANGE.enclose("=".repeat(8) + "[ " + YELLOW.enclose("Value Helper") + " ]" + "=".repeat(8))) + .append(Placeholders.TAG_LINE_BREAK); + + items.forEach(element -> { + String hoverHint = GRAY.enclose("Click me to select " + CYAN.enclose(element) + "."); + String clickCommand = element.charAt(0) == '/' ? element : '/' + element; + + builder.append(DARK_GRAY.enclose("> ")).append(GREEN.enclose(HOVER.encloseHint(CLICK.enclose(element, action, clickCommand), hoverHint))); + builder.append(Placeholders.TAG_LINE_BREAK); + }); + + builder.append(ORANGE.enclose("=".repeat(9))).append(" "); + + if (isFirstPage) { + builder.append(GRAY.enclose("[<]")); + } + else { + builder.append(LIGHT_RED.enclose(HOVER.encloseHint(CLICK.encloseRun("[<]", "/" + VALUES + " " + (page - 1)), GRAY.enclose("Previous Page")))); + } + + builder.append(YELLOW.enclose(" " + page)); + builder.append(ORANGE.enclose("/")); + builder.append(YELLOW.enclose(pages + " ")); + + if (isLastPage) { + builder.append(GRAY.enclose("[>]")); + } + else { + builder.append(LIGHT_RED.enclose(HOVER.encloseHint(CLICK.encloseRun("[>]", "/" + VALUES + " " + (page + 1)), GRAY.enclose("Next Page")))); + + } + + builder.append(ORANGE.enclose(" " + "=".repeat(9))); + + Players.sendModernMessage(dialog.getPlayer(), builder.toString()); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/Menu.java b/src/main/java/su/nightexpress/nightcore/ui/menu/Menu.java new file mode 100644 index 0000000..99ee4d9 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/Menu.java @@ -0,0 +1,96 @@ +package su.nightexpress.nightcore.ui.menu; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.MenuType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.ui.dialog.Dialog; +import su.nightexpress.nightcore.ui.menu.click.ClickResult; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +@SuppressWarnings("UnstableApiUsage") +public interface Menu { + + void clear(); + + void flush(); + + void flush(@NotNull MenuViewer viewer); + + void flush(@NotNull Player player); + + void flush(@NotNull Player player, @NotNull Consumer consumer); + + boolean isViewer(@NotNull Player player); + + void close(); + + void close(@NotNull Player player); + + void runNextTick(@NotNull Runnable runnable); + + void onClick(@NotNull MenuViewer viewer, @NotNull ClickResult result, @NotNull InventoryClickEvent event); + + void onDrag(@NotNull MenuViewer viewer, @NotNull InventoryDragEvent event); + + void onClose(@NotNull MenuViewer viewer, @NotNull InventoryCloseEvent event); + + void handleInput(@NotNull Dialog.Builder builder); + + boolean canOpen(@NotNull Player player); + + boolean isPersistent(); + + @NotNull Set getViewers(); + + @Nullable MenuViewer getViewer(@NotNull Player player); + + //@NotNull MenuViewer getViewerOrCreate(@NotNull Player player); + + @NotNull List getItems(@NotNull MenuViewer viewer); + + @Nullable MenuItem getItem(int slot); + + @Nullable MenuItem getItem(@NotNull MenuViewer viewer, int slot); + + + void addItem(@NotNull MenuViewer viewer, @NotNull MenuItem.Builder builder); + + void addItem(@NotNull MenuViewer viewer, @NotNull MenuItem menuItem); + + void addItem(@NotNull MenuItem.Builder builder); + + void addItem(@NotNull MenuItem menuItem); + + + @NotNull Set getItems(); + + @NotNull MenuType getMenuType(); + + void setMenuType(@NotNull MenuType menuType); + + @NotNull String getTitle(); + + void setTitle(@NotNull String title); + + int getAutoRefreshInterval(); + + void setAutoRefreshInterval(int autoRefreshInterval); + + boolean isReadyToRefresh(); + + long getAutoRefreshDate(); + + void setAutoRefreshDate(long autoRefreshDate); + + boolean isApplyPlaceholderAPI(); + + void setApplyPlaceholderAPI(boolean applyPlaceholderAPI); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/MenuRegistry.java b/src/main/java/su/nightexpress/nightcore/ui/menu/MenuRegistry.java new file mode 100644 index 0000000..94f3cfa --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/MenuRegistry.java @@ -0,0 +1,52 @@ +package su.nightexpress.nightcore.ui.menu; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +public class MenuRegistry { + + private static final Map VIEWER_BY_ID = new HashMap<>(); + + @NotNull + public static Set getViewers() { + return new HashSet<>(VIEWER_BY_ID.values()); + } + + @NotNull + public static Set getViewers(@NotNull Menu menu) { + return getViewers().stream().filter(viewer -> viewer.isMenu(menu)).collect(Collectors.toSet()); + } + + public static void closeMenu(@NotNull Player player) { + if (isViewer(player)) { + player.closeInventory(); + } + } + + public static void assign(@NotNull MenuViewer viewer) { + VIEWER_BY_ID.put(viewer.getPlayer().getUniqueId(), viewer); + } + + public static void terminate(@NotNull Player player) { + VIEWER_BY_ID.remove(player.getUniqueId()); + } + + @Nullable + public static Menu getMenu(@NotNull Player player) { + MenuViewer viewer = getViewer(player); + return viewer == null ? null : viewer.getMenu(); + } + + @Nullable + public static MenuViewer getViewer(@NotNull Player player) { + return VIEWER_BY_ID.get(player.getUniqueId()); + } + + public static boolean isViewer(@NotNull Player player) { + return getViewer(player) != null; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/MenuViewer.java b/src/main/java/su/nightexpress/nightcore/ui/menu/MenuViewer.java new file mode 100644 index 0000000..3da7d49 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/MenuViewer.java @@ -0,0 +1,113 @@ +package su.nightexpress.nightcore.ui.menu; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; + +import java.util.HashSet; +import java.util.Set; + +public class MenuViewer { + + private final Menu menu; + private final Player player; + private final Set items; + + private InventoryView view; + private int page; + private int pages; + private long lastClickTime; + private boolean updateTitle; + + public MenuViewer(@NotNull Menu menu, @NotNull Player player) { + this.menu = menu; + this.player = player; + this.items = new HashSet<>(); + this.setPage(1); + this.setPages(1); + } + + public void assignInventory(@NotNull InventoryView view) { + this.view = view; + } + + public boolean canClickAgain(long cooldown) { + return this.getLastClickTime() == 0 || (System.currentTimeMillis() - this.getLastClickTime()) > cooldown; + } + + public boolean isMenu(@NotNull Menu menu) { + return this.menu == menu; + } + + public void addItem(@NotNull MenuItem menuItem) { + this.items.add(menuItem); + } + + public void removeItem(@NotNull MenuItem menuItem) { + this.items.remove(menuItem); + } + + public void removeItems() { + this.items.clear(); + } + + @NotNull + public Menu getMenu() { + return this.menu; + } + + @NotNull + public Player getPlayer() { + return this.player; + } + + @NotNull + public Set getItems() { + return this.items; + } + + @Nullable + public Inventory getInventory() { + return this.view == null ? null : this.view.getTopInventory(); + } + + @Nullable + public InventoryView getView() { + return this.view; + } + + public int getPage() { + return this.page; + } + + public void setPage(int page) { + this.page = Math.max(1, page); + } + + public int getPages() { + return this.pages; + } + + public void setPages(int pages) { + this.pages = Math.max(1, pages); + } + + public long getLastClickTime() { + return this.lastClickTime; + } + + public void setLastClickTime(long lastClickTime) { + this.lastClickTime = lastClickTime; + } + + public boolean isUpdateTitle() { + return this.updateTitle; + } + + public void setUpdateTitle(boolean updateTitle) { + this.updateTitle = updateTitle; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickKey.java b/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickKey.java new file mode 100644 index 0000000..303fca0 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickKey.java @@ -0,0 +1,23 @@ +package su.nightexpress.nightcore.ui.menu.click; + +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; + +public enum ClickKey { + + LEFT, RIGHT, SHIFT_LEFT, SHIFT_RIGHT, + DROP_KEY, SWAP_KEY, + ; + + @NotNull + public static ClickKey from(@NotNull InventoryClickEvent event) { + if (event.getClick() == ClickType.DROP) return DROP_KEY; + if (event.getClick() == ClickType.SWAP_OFFHAND) return SWAP_KEY; + + if (event.isShiftClick()) { + return event.isRightClick() ? SHIFT_RIGHT : SHIFT_LEFT; + } + return event.isRightClick() ? RIGHT : LEFT; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickResult.java b/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickResult.java new file mode 100644 index 0000000..c9158cb --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/click/ClickResult.java @@ -0,0 +1,38 @@ +package su.nightexpress.nightcore.ui.menu.click; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class ClickResult { + + private final int slot; + private final ItemStack itemStack; + private final boolean isMenu; + + public ClickResult(int slot, @Nullable ItemStack itemStack, boolean isMenu) { + this.slot = slot; + this.itemStack = itemStack; + this.isMenu = isMenu; + } + + public int getSlot() { + return slot; + } + + @Nullable + public ItemStack getItemStack() { + return itemStack; + } + + public boolean isEmptySlot() { + return this.itemStack == null || this.itemStack.getType().isAir(); + } + + public boolean isMenu() { + return isMenu; + } + + public boolean isInventory() { + return !this.isMenu(); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/ConfigBased.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/ConfigBased.java new file mode 100644 index 0000000..728e6e7 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/ConfigBased.java @@ -0,0 +1,19 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.ui.menu.Menu; + +public interface ConfigBased extends Menu { + + default void load(@NotNull FileConfig config) { + MenuLoader loader = new MenuLoader(this, config); + loader.loadSettings(); + this.loadConfiguration(config, loader); + loader.loadItems(); + loader.loadComments(); + config.saveChanges(); + } + + void loadConfiguration(@NotNull FileConfig config, @NotNull MenuLoader loader); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/Filled.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/Filled.java new file mode 100644 index 0000000..b88cc0b --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/Filled.java @@ -0,0 +1,13 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.MenuViewer; + +public interface Filled { + + @NotNull MenuFiller createFiller(@NotNull MenuViewer viewer); + + default void autoFill(@NotNull MenuViewer viewer) { + this.createFiller(viewer).addItems(viewer); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkCache.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkCache.java new file mode 100644 index 0000000..066f5cc --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkCache.java @@ -0,0 +1,60 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.MenuViewer; + +import java.util.*; + +public class LinkCache { + + private final Map map; + private final Set anchors; + + public LinkCache() { + this.map = new HashMap<>(); + this.anchors = new HashSet<>(); + } + + public boolean hasAnchor(@NotNull Player player) { + return this.anchors.contains(player.getUniqueId()); + } + + public void addAnchor(@NotNull Player player) { + this.anchors.add(player.getUniqueId()); + } + + public void removeAnchor(@NotNull Player player) { + this.anchors.remove(player.getUniqueId()); + } + + public void set(@NotNull MenuViewer viewer, @NotNull T object) { + this.set(viewer.getPlayer(), object); + } + + public void set(@NotNull Player player, @NotNull T object) { + this.map.put(player.getUniqueId(), object); + } + + public T get(@NotNull MenuViewer viewer) { + return this.get(viewer.getPlayer()); + } + + public T get(@NotNull Player player) { + return this.map.get(player.getUniqueId()); + } + + public void clear(@NotNull MenuViewer viewer) { + this.clear(viewer.getPlayer()); + } + + public void clear(@NotNull Player player) { + this.map.remove(player.getUniqueId()); + this.removeAnchor(player); + } + + public void clear() { + this.map.clear(); + this.anchors.clear(); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkHandler.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkHandler.java new file mode 100644 index 0000000..ada8016 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/LinkHandler.java @@ -0,0 +1,10 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.MenuViewer; + +public interface LinkHandler { + + void handle(@NotNull MenuViewer viewer, @NotNull InventoryClickEvent event, @NotNull T obj); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/Linked.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/Linked.java new file mode 100644 index 0000000..db6f1d9 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/Linked.java @@ -0,0 +1,38 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.language.entry.LangItem; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.ui.menu.item.ItemClick; +import su.nightexpress.nightcore.ui.menu.item.ItemOptions; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +public interface Linked extends Menu { + + boolean open(@NotNull Player player, @NotNull T obj); + + @NotNull LinkCache getCache(); + + T getLink(@NotNull MenuViewer viewer); + + T getLink(@NotNull Player player); + + ItemClick manageLink(@NotNull LinkHandler handler); + + void addItem(@NotNull Material material, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler); + + void addItem(@NotNull Material material, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options); + + void addItem(@NotNull ItemStack itemStack, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler); + + void addItem(@NotNull ItemStack itemStack, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options); + + void addItem(@NotNull NightItem item, int slot, @NotNull LinkHandler handler); + + void addItem(@NotNull NightItem item, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuFiller.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuFiller.java new file mode 100644 index 0000000..d99148c --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuFiller.java @@ -0,0 +1,117 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.ui.menu.item.ItemClick; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +public class MenuFiller { + + private final int[] slots; + private final Collection items; + private final Function itemCreator; + private final Function itemClick; + + public MenuFiller(int[] slots, + @NotNull Collection items, + @NotNull Function itemCreator, + @NotNull Function itemClick) { + this.slots = slots; + this.items = items; + this.itemCreator = itemCreator; + this.itemClick = itemClick; + } + + @NotNull + public static Builder builder(@NotNull Filled menu) { + return new Builder<>(); + } + + public void addItems(@NotNull MenuViewer viewer) { + int limit = this.slots.length; + int pages = (int) Math.ceil((double) items.size() / (double) limit); + viewer.setPages(pages); + viewer.setPage(Math.min(viewer.getPage(), viewer.getPages())); + + int skip = (viewer.getPage() - 1) * limit; + + List list = items.stream().skip(skip).limit(limit).toList(); + + int count = 0; + for (I object : list) { + NightItem item = this.itemCreator.apply(object); + MenuItem menuItem = MenuItem.builder(item).setPriority(MenuItem.HIGH_PRIORITY).setSlots(this.slots[count++]).setHandler(this.itemClick.apply(object)).build(); + + viewer.addItem(menuItem); + } + } + +// public int[] getSlots() { +// return slots; +// } +// +// @NotNull +// public Collection getItems() { +// return items; +// } +// +// @NotNull +// public Function getItemCreator() { +// return itemCreator; +// } +// +// @NotNull +// public Function getItemClick() { +// return itemClick; +// } + + public static class Builder { + + private int[] slots; + private Collection items; + private Function itemCreator; + private Function itemClick; + + public Builder() { + this.slots = new int[0]; + this.items = Collections.emptyList(); + this.itemCreator = obj -> new NightItem(Material.AIR); + this.itemClick = obj -> (viewer, event) -> {}; + } + + public MenuFiller build() { + return new MenuFiller<>(this.slots, this.items, this.itemCreator, this.itemClick); + } + + public Builder setSlots(int... slots) { + this.slots = slots; + return this; + } + +// public Builder setItems(@NotNull Collection items) { +// return this.setItems(new ArrayList<>(items)); +// } + + public Builder setItems(@NotNull Collection items) { + this.items = items; + return this; + } + + public Builder setItemCreator(@NotNull Function itemCreator) { + this.itemCreator = itemCreator; + return this; + } + + public Builder setItemClick(@NotNull Function itemClick) { + this.itemClick = itemClick; + return this; + } + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuLoader.java b/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuLoader.java new file mode 100644 index 0000000..573a992 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/data/MenuLoader.java @@ -0,0 +1,215 @@ +package su.nightexpress.nightcore.ui.menu.data; + +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.MenuType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.config.ConfigValue; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.click.ClickKey; +import su.nightexpress.nightcore.ui.menu.item.ItemClick; +import su.nightexpress.nightcore.ui.menu.item.ItemHandler; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; +import su.nightexpress.nightcore.util.*; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +import java.util.*; + +@SuppressWarnings("UnstableApiUsage") +public class MenuLoader { + + private static final String ITEM_SECTION = "Content"; + + private final Menu menu; + private final FileConfig config; + + private final Map defaultItems; + private final Map handlerMap; + + public MenuLoader(@NotNull Menu menu, @NotNull FileConfig config) { + this.menu = menu; + this.config = config; + this.defaultItems = new LinkedHashMap<>(); + this.handlerMap = new LinkedHashMap<>(); + + this.addHandler(ItemHandler.forClose(menu)); + if (menu instanceof Filled) { + this.addHandler(ItemHandler.forNextPage(menu)); + this.addHandler(ItemHandler.forPreviousPage(menu)); + } + } + + public void addDefaultItem(@NotNull MenuItem.Builder builder) { + this.addDefaultItem(builder.build()); + } + + public void addDefaultItem(@NotNull MenuItem menuItem) { + String name = BukkitThing.toString(menuItem.getItem().getMaterial()); + + ItemHandler handler = menuItem.getHandler(); + if (handler != null) { + name = handler.getName(); + this.addHandler(handler); + } + + name = name.toLowerCase(); + + if (this.defaultItems.containsKey(name)) { + name += "_" + UUID.randomUUID().toString().substring(0, 8); + } + + this.defaultItems.put(name.toLowerCase(), menuItem); + } + + @NotNull + public ItemHandler addHandler(@NotNull String name, @NotNull ItemClick click) { + return this.addHandler(new ItemHandler(name, click)); + } + + @NotNull + public ItemHandler addHandler(@NotNull ItemHandler handler) { + this.handlerMap.put(handler.getName(), handler); + return handler; + } + + public void loadSettings() { + if (config.contains("Settings.Inventory_Type")) { + InventoryType oldType = config.getEnum("Settings.Inventory_Type", InventoryType.class, InventoryType.CHEST); + int oldSize = config.getInt("Settings.Size", 27); + + MenuType newType = getNewType(oldType, oldSize); + + config.set("Settings.MenuType", BukkitThing.toString(newType)); + config.remove("Settings.Inventory_Type"); + config.remove("Settings.Size"); + } + + MenuType menuType = BukkitThing.getMenuType(ConfigValue.create("Settings.MenuType", BukkitThing.toString(this.menu.getMenuType())).read(config)); + + this.menu.setMenuType(menuType == null ? MenuType.GENERIC_9X3 : menuType); + this.menu.setTitle(ConfigValue.create("Settings.Title", this.menu.getTitle()).read(config)); + this.menu.setAutoRefreshInterval(ConfigValue.create("Settings.Auto_Refresh", this.menu.getAutoRefreshInterval()).read(config)); + this.menu.setApplyPlaceholderAPI(ConfigValue.create("Settings.PlaceholderAPI.Enabled", this.menu.isApplyPlaceholderAPI()).read(config)); + } + + private static MenuType getNewType(InventoryType oldType, int oldSize) { + MenuType newType; + if (oldType == InventoryType.CHEST) { + if (oldSize == 54) newType = MenuType.GENERIC_9X6; + else if (oldSize == 45) newType = MenuType.GENERIC_9X5; + else if (oldSize == 36) newType = MenuType.GENERIC_9X4; + else if (oldSize == 27) newType = MenuType.GENERIC_9X3; + else if (oldSize == 18) newType = MenuType.GENERIC_9X2; + else newType = MenuType.GENERIC_9X1; + } + else { + newType = switch (oldType) { + case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE; + case ANVIL -> MenuType.ANVIL; + case LOOM -> MenuType.LOOM; + case SMITHING -> MenuType.SMITHING; + case GRINDSTONE -> MenuType.GRINDSTONE; + case STONECUTTER -> MenuType.STONECUTTER; + case SMOKER -> MenuType.SMOKER; + case BEACON -> MenuType.BEACON; + case HOPPER -> MenuType.HOPPER; + case BREWING -> MenuType.BREWING_STAND; + case DROPPER, DISPENSER -> MenuType.GENERIC_3X3; + case FURNACE -> MenuType.FURNACE; + case LECTERN -> MenuType.LECTERN; + case ENCHANTING -> MenuType.ENCHANTMENT; + case BLAST_FURNACE -> MenuType.BLAST_FURNACE; + default -> MenuType.GENERIC_9X3; + }; + } + return newType; + } + + public void loadItems() { + if (!this.config.contains(ITEM_SECTION)) { + this.defaultItems.forEach((id, menuItem) -> { + this.writeItem(menuItem, ITEM_SECTION + "." + id); + }); + } + + this.config.getSection(ITEM_SECTION).forEach(sId -> { + MenuItem menuItem = this.readItem(ITEM_SECTION + "." + sId); + this.menu.addItem(menuItem); + }); + } + + public void loadComments() { + List list = Lists.newList( + "=".repeat(20) + " [SECTION] SETTINGS " + "=".repeat(20), + "> [MenuType] : String | Represents the menu type. Allowed values: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/MenuType.html", + "> [Title] : String | Sets menu title.", + "> [Auto_Refresh] : Integer | Defines menu refresh rate in seconds. Set 0 to disable.", + "> [PlaceholderAPI -> Enabled] : Boolean | When enabled, applies " + Plugins.PLACEHOLDER_API + " placeholders for all items in the menu per player.", + " ", + "=".repeat(20) + " [SECTION] CONTENT " + "=".repeat(20), + "You can freely edit items in this section as you wish (add, remove, modify items).", + "> [Item] : Section | Item to display.", + " [*] Navigate to " + Placeholders.WIKI_ITEMS_URL + " for a list of available options.", + "> [Priority] : Integer | Item priority. Higher values will override other item(s) in the same slot(s).", + "> [Slots] : Int Array | Item slots, starts from 0. Split with commas.", + " Slots: '0,4,9,10'", + "> [Type] : String | Defines item click action.", + " [*] Available types: [" + String.join(", ", handlerMap.keySet().stream().map(str -> "'" + str + "'").toList()) + "]", + "> [Click_Commands] : Section | Executes commands on click with " + Plugins.PLACEHOLDER_API + " support.", + " [*] Works only if [Type] is not set (null).", + " [*] Available click types: [" + String.join(", ", Lists.getEnums(ClickKey.class)) + "]", + " [*] Use prefix '" + Players.PLAYER_COMMAND_PREFIX + "' to run command by a player.", + " Click_Commands:", + " " + ClickKey.LEFT.name() + ":", + " - say Hello", + " - give " + Placeholders.PLAYER_NAME + " diamond 1", + " - " + Players.PLAYER_COMMAND_PREFIX + " warp shop", + "=".repeat(50) + ); + + this.config.setComments("Settings", list); + } + + @NotNull + protected MenuItem readItem(@NotNull String path) { + NightItem item = config.getCosmeticItem(path + ".Item"); + int priority = config.getInt(path + ".Priority"); + int[] slots = config.getIntArray(path + ".Slots"); + String handlerName = config.getString(path + ".Type", Placeholders.DEFAULT); + + ItemHandler typedHandler = this.handlerMap.get(handlerName.toLowerCase()); + ItemHandler commandHandler; + + if (config.contains(path + ".Click_Commands")) { + Map> commandMap = new HashMap<>(); + for (String sType : config.getSection(path + ".Click_Commands")) { + ClickKey clickType = StringUtil.getEnum(sType, ClickKey.class).orElse(null); + if (clickType == null) continue; + + List commands = config.getStringList(path + ".Click_Commands." + sType); + if (commands.isEmpty()) continue; + + commandMap.put(clickType, commands); + } + + commandHandler = ItemHandler.forClick((viewer, event) -> { + List commands = commandMap.getOrDefault(ClickKey.from(event), Collections.emptyList()); + commands.forEach(command -> Players.dispatchCommand(viewer.getPlayer(), command)); + }); + } + else { + commandHandler = null; + } + + ItemHandler handler = typedHandler == null ? commandHandler : typedHandler; + + return new MenuItem(item, priority, slots, handler); + } + + protected void writeItem(@NotNull MenuItem menuItem, @NotNull String path) { + this.config.set(path + ".Priority", menuItem.getPriority()); + this.config.set(path + ".Item", menuItem.getItem()); + this.config.setIntArray(path + ".Slots", menuItem.getSlots()); + this.config.set(path + ".Type", menuItem.getHandler() == null ? "null" : menuItem.getHandler().getName()); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemClick.java b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemClick.java new file mode 100644 index 0000000..8ffef6e --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemClick.java @@ -0,0 +1,10 @@ +package su.nightexpress.nightcore.ui.menu.item; + +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.ui.menu.MenuViewer; + +public interface ItemClick { + + void onClick(@NotNull MenuViewer viewer, @NotNull InventoryClickEvent event); +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemHandler.java b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemHandler.java new file mode 100644 index 0000000..c2baed4 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemHandler.java @@ -0,0 +1,142 @@ +package su.nightexpress.nightcore.ui.menu.item; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.data.LinkHandler; +import su.nightexpress.nightcore.ui.menu.data.Linked; + +import java.util.UUID; + +public class ItemHandler { + + public static final String RETURN = "return"; + public static final String CLOSE = "close"; + public static final String NEXT_PAGE = "page_next"; + public static final String PREVIOUS_PAGE = "page_previous"; + + private final String name; + private final ItemClick click; + private final ItemOptions options; + + public ItemHandler(@NotNull String name) { + this(name, null, null); + } + + public ItemHandler(@NotNull String name, @Nullable ItemClick click) { + this(name, click, null); + } + + public ItemHandler(@NotNull String name, @Nullable ItemClick click, @Nullable ItemOptions options) { + this.name = name.toLowerCase(); + this.click = click; + this.options = options; + } + + @NotNull + private static String randomName() { + return UUID.randomUUID().toString(); + } + + /** + * The main purpose of this method is to quickly create ItemHandler object for non-configurable GUIs. + *

+ * Do NOT use this for items requiring specific handler name. + * @param click Click action + * @return ItemHandler with a random UUID as a name. + */ + @NotNull + public static ItemHandler forClick(@NotNull ItemClick click) { + return forClick(click, null); + } + + /** + * The main purpose of this method is to quickly create ItemHandler object for non-configurable GUIs. + *

+ * Do NOT use this for items requiring specific handler name. + * @param click Click action + * @return ItemHandler with a random UUID as a name. + */ + @NotNull + public static ItemHandler forClick(@NotNull ItemClick click, @Nullable ItemOptions options) { + return new ItemHandler(randomName(), click, options); + } + + @NotNull + public static ItemHandler forNextPage(@NotNull Menu menu) { + return new ItemHandler(NEXT_PAGE, + (viewer, event) -> { + if (viewer.getPage() < viewer.getPages()) { + viewer.setPage(viewer.getPage() + 1); + viewer.setUpdateTitle(true); + menu.flush(viewer.getPlayer()); + } + }, + ItemOptions.builder().setVisibilityPolicy(viewer -> viewer.getPage() < viewer.getPages()).build() + ); + } + + @NotNull + public static ItemHandler forPreviousPage(@NotNull Menu menu) { + return new ItemHandler(PREVIOUS_PAGE, + (viewer, event) -> { + if (viewer.getPage() > 1) { + viewer.setPage(viewer.getPage() - 1); + viewer.setUpdateTitle(true); + menu.flush(viewer.getPlayer()); + } + }, + ItemOptions.builder().setVisibilityPolicy(viewer -> viewer.getPage() > 1).build() + ); + } + + @NotNull + public static ItemHandler forClose(@NotNull Menu menu) { + return new ItemHandler(CLOSE, (viewer, event) -> menu.runNextTick(() -> viewer.getPlayer().closeInventory())); + } + + @NotNull + public static ItemHandler forReturn(@NotNull Menu menu, @NotNull ItemClick click) { + return forReturn(menu, click, null); + } + + @NotNull + public static ItemHandler forReturn(@NotNull Menu menu, @NotNull ItemClick click, @Nullable ItemOptions options) { + return new ItemHandler(RETURN, click, options); + } + + @NotNull + public static ItemHandler forLink(@NotNull Linked menu, @NotNull LinkHandler handler) { + return forLink(menu, handler, null); + } + + @NotNull + public static ItemHandler forLink(@NotNull Linked menu, @NotNull LinkHandler handler, @Nullable ItemOptions options) { + return new ItemHandler(randomName(), (viewer, event) -> menu.manageLink(handler), options); + } + + @NotNull + public static ItemHandler forLink(@NotNull String name, @NotNull Linked menu, @NotNull LinkHandler handler) { + return forLink(name, menu, handler, null); + } + + @NotNull + public static ItemHandler forLink(@NotNull String name, @NotNull Linked menu, @NotNull LinkHandler handler, @Nullable ItemOptions options) { + return new ItemHandler(name, menu.manageLink(handler), options); + } + + @NotNull + public String getName() { + return this.name; + } + + @NotNull + public ItemClick getClick() { + return this.click; + } + + @Nullable + public ItemOptions getOptions() { + return this.options; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemOptions.java b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemOptions.java new file mode 100644 index 0000000..6a0789c --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/item/ItemOptions.java @@ -0,0 +1,73 @@ +package su.nightexpress.nightcore.ui.menu.item; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +public class ItemOptions { + + private final Predicate visibilityPolicy; + private final BiConsumer displayModifier; + + public ItemOptions(@Nullable Predicate visibilityPolicy, + @Nullable BiConsumer displayModifier) { + this.visibilityPolicy = visibilityPolicy; + this.displayModifier = displayModifier; + } + + public boolean canSee(@NotNull MenuViewer viewer) { + return this.visibilityPolicy == null || this.visibilityPolicy.test(viewer); + } + + public void modifyDisplay(@NotNull MenuViewer viewer, @NotNull NightItem item) { + if (this.displayModifier != null) { + this.displayModifier.accept(viewer, item); + } + } + + @NotNull + public static Builder builder() { + return new Builder(); + } + + @Nullable + public Predicate getVisibilityPolicy() { + return this.visibilityPolicy; + } + + @Nullable + public BiConsumer getDisplayModifier() { + return this.displayModifier; + } + + public static class Builder { + + private Predicate visibilityPolicy; + private BiConsumer displayModifier; + + public Builder() { + + } + + @NotNull + public ItemOptions build() { + return new ItemOptions(this.visibilityPolicy, this.displayModifier); + } + + @NotNull + public Builder setVisibilityPolicy(@Nullable Predicate visibilityPolicy) { + this.visibilityPolicy = visibilityPolicy; + return this; + } + + @NotNull + public Builder setDisplayModifier(@Nullable BiConsumer displayModifier) { + this.displayModifier = displayModifier; + return this; + } + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/item/MenuItem.java b/src/main/java/su/nightexpress/nightcore/ui/menu/item/MenuItem.java new file mode 100644 index 0000000..a6e161e --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/item/MenuItem.java @@ -0,0 +1,140 @@ +package su.nightexpress.nightcore.ui.menu.item; + +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.core.CoreLang; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.util.Placeholders; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +public class MenuItem { + + public static final int HIGH_PRIORITY = 100; + + protected final NightItem item; + protected final int priority; + protected final int[] slots; + protected final ItemHandler handler; + + public MenuItem(@NotNull NightItem item, int priority, int[] slots, @Nullable ItemHandler handler) { + this.item = item; + this.priority = priority; + this.slots = slots; + this.handler = handler; + } + + @NotNull + public static Builder builder(@NotNull NightItem item) { + return new Builder().setItem(item); + } + + @NotNull + public static Builder buildNextPage(@NotNull Menu menu, int slot) { + return builder(NightItem.asCustomHead(Placeholders.SKIN_ARROW_RIGHT).localized(CoreLang.EDITOR_ITEM_NEXT_PAGE)) + .setHandler(ItemHandler.forNextPage(menu)) + .setSlots(slot); + } + + @NotNull + public static Builder buildPreviousPage(@NotNull Menu menu, int slot) { + return builder(NightItem.asCustomHead(Placeholders.SKIN_ARROW_LEFT).localized(CoreLang.EDITOR_ITEM_PREVIOUS_PAGE)) + .setHandler(ItemHandler.forPreviousPage(menu)) + .setSlots(slot); + } + + @NotNull + public static Builder buildExit(@NotNull Menu menu, int slot) { + return builder(NightItem.asCustomHead(Placeholders.SKIN_WRONG_MARK).localized(CoreLang.EDITOR_ITEM_CLOSE)) + .setHandler(ItemHandler.forClose(menu)) + .setSlots(slot); + } + + @NotNull + public static Builder buildReturn(@NotNull Menu menu, int slot, @NotNull ItemClick click) { + return buildReturn(menu, slot, click, null); + } + + @NotNull + public static Builder buildReturn(@NotNull Menu menu, int slot, @NotNull ItemClick click, @Nullable ItemOptions options) { + return builder(NightItem.asCustomHead(Placeholders.SKIN_ARROW_DOWN).localized(CoreLang.EDITOR_ITEM_RETURN)) + .setHandler(ItemHandler.forReturn(menu, click, options)) + .setSlots(slot); + } + + @NotNull + @Deprecated + public MenuItem copy() { + return new MenuItem(this.getItem().copy(), this.getPriority(), this.getSlots(), this.getHandler()); + } + + public boolean canSee(@NotNull MenuViewer viewer) { + return this.handler == null || this.handler.getOptions() == null || this.handler.getOptions().canSee(viewer); + } + + public void click(@NotNull MenuViewer viewer, @NotNull InventoryClickEvent event) { + if (this.handler == null) return; + + this.handler.getClick().onClick(viewer, event); + } + + @NotNull + public NightItem getItem() { + return this.item; + } + + public int getPriority() { + return this.priority; + } + + public int[] getSlots() { + return this.slots; + } + + @Nullable + public ItemHandler getHandler() { + return this.handler; + } + + public static class Builder { + + private NightItem item; + private int priority; + private int[] slots; + private ItemHandler handler; + + @NotNull + public MenuItem build() { + return new MenuItem(this.item, this.priority, this.slots, this.handler); + } + + public Builder setItem(@NotNull NightItem item) { + this.item = item; + return this; + } + + @NotNull + public Builder setPriority(int priority) { + this.priority = priority; + return this; + } + + @NotNull + public Builder setSlots(int... slots) { + this.slots = slots; + return this; + } + + @NotNull + public Builder setHandler(@Nullable ItemHandler handler) { + this.handler = handler; + return this; + } + + @NotNull + public Builder setHandler(@NotNull ItemClick click) { + return this.setHandler(ItemHandler.forClick(click)); + } + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/type/AbstractMenu.java b/src/main/java/su/nightexpress/nightcore/ui/menu/type/AbstractMenu.java new file mode 100644 index 0000000..702974b --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/type/AbstractMenu.java @@ -0,0 +1,363 @@ +package su.nightexpress.nightcore.ui.menu.type; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MenuType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.NightPlugin; +import su.nightexpress.nightcore.api.event.MenuOpenEvent; +import su.nightexpress.nightcore.ui.dialog.Dialog; +import su.nightexpress.nightcore.ui.dialog.DialogManager; +import su.nightexpress.nightcore.ui.menu.Menu; +import su.nightexpress.nightcore.ui.menu.MenuRegistry; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.ui.menu.click.ClickResult; +import su.nightexpress.nightcore.ui.menu.item.ItemHandler; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; +import su.nightexpress.nightcore.util.Lists; +import su.nightexpress.nightcore.util.TimeUtil; +import su.nightexpress.nightcore.util.bukkit.NightItem; +import su.nightexpress.nightcore.util.text.NightMessage; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +@SuppressWarnings("UnstableApiUsage") +public abstract class AbstractMenu

implements Menu { + + protected final P plugin; + protected final Set items; + + protected MenuType menuType; + protected String title; + protected boolean persistent; + protected int autoRefreshInterval; + protected long autoRefreshDate; + protected boolean applyPlaceholderAPI; + + public AbstractMenu(@NotNull P plugin, @NotNull MenuType menuType, @NotNull String title) { + this.plugin = plugin; + this.items = new HashSet<>(); + + this.setMenuType(menuType); + this.setTitle(title); + this.setPersistent(true); + this.setApplyPlaceholderAPI(false); + this.setAutoRefreshInterval(-1); + } + + @Override + public void clear() { + this.close(); + this.items.clear(); + } + + @Override + public void close() { + MenuRegistry.getViewers(this).forEach(this::closeFully); + } + + @Override + public void close(@NotNull Player player) { + MenuViewer viewer = this.getViewer(player); + if (viewer == null) return; + + this.closeFully(viewer); + } + + protected void closeFully(@NotNull MenuViewer viewer) { + viewer.getPlayer().closeInventory(); + this.onClose(viewer); + } + + @Override + public void runNextTick(@NotNull Runnable runnable) { + this.plugin.runTask(task -> runnable.run()); + } + + public void flush() { + this.getViewers().forEach(this::flush); + } + + public void flush(@NotNull MenuViewer viewer) { + this.flush(viewer.getPlayer()); + } + + @Override + public void flush(@NotNull Player player) { + this.flush(player, viewer -> {}); + } + + @Override + public void flush(@NotNull Player player, @NotNull Consumer consumer) { + this.open(player, consumer); + } + + @Override + public boolean canOpen(@NotNull Player player) { + return !player.isSleeping(); + } + + protected boolean open(@NotNull Player player, @NotNull Consumer onViewSet) { + if (!this.canOpen(player)) { + this.close(player); + return false; + } + + MenuOpenEvent event = new MenuOpenEvent(player, this); + this.plugin.getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.close(player); + return false; + } + + MenuViewer viewer = this.getViewerOrCreate(player); + viewer.removeItems(); + onViewSet.accept(viewer); + + String title = NightMessage.asLegacy(this.getTitle(viewer)); + + InventoryView view = viewer.getView(); + if (view == null) { + view = this.menuType.typed().create(player, title); + viewer.assignInventory(view); + player.openInventory(view); + } + else { + view.getTopInventory().clear(); + if (viewer.isUpdateTitle()) view.setTitle(title); + } + + this.onPrepare(viewer, view); + + Inventory inventory = view.getTopInventory(); + + this.getItems(viewer).forEach(menuItem -> { + NightItem item = menuItem.getItem().copy(); + ItemHandler handler = menuItem.getHandler(); + + if (this.isApplyPlaceholderAPI()) { + item.replacement(replacer -> replacer.replacePlaceholderAPI(player)); + } + + if (handler != null && handler.getOptions() != null) { + handler.getOptions().modifyDisplay(viewer, item); + } + + ItemStack itemStack = item.getItemStack(); + + for (int slot : menuItem.getSlots()) { + if (slot < 0 || slot >= inventory.getSize()) continue; + inventory.setItem(slot, itemStack); + } + }); + + this.onReady(viewer, inventory); + + MenuRegistry.assign(viewer); + return true; + } + + protected abstract void onPrepare(@NotNull MenuViewer viewer, @NotNull InventoryView view); + + protected abstract void onReady(@NotNull MenuViewer viewer, @NotNull Inventory inventory); + + @Override + public void onClick(@NotNull MenuViewer viewer, @NotNull ClickResult result, @NotNull InventoryClickEvent event) { + event.setCancelled(true); + + if (result.isInventory()) return; + + MenuItem menuItem = this.getItem(viewer, result.getSlot()); + if (menuItem == null) return; + + menuItem.click(viewer, event); + viewer.setLastClickTime(System.currentTimeMillis()); + } + + @Override + public void onDrag(@NotNull MenuViewer viewer, @NotNull InventoryDragEvent event) { + event.setCancelled(true); + } + + @Override + public void onClose(@NotNull MenuViewer viewer, @NotNull InventoryCloseEvent event) { + this.onClose(viewer); + } + + protected void onClose(@NotNull MenuViewer viewer) { + viewer.removeItems(); + + MenuRegistry.terminate(viewer.getPlayer()); + + if (this.getViewers().isEmpty() && !this.isPersistent()) { + this.clear(); + } + } + + @Override + @NotNull + public Set getViewers() { + return MenuRegistry.getViewers(this); + } + + @Override + public boolean isViewer(@NotNull Player player) { + return this.getViewer(player) != null; + } + + @Override + @Nullable + public MenuViewer getViewer(@NotNull Player player) { + MenuViewer viewer = MenuRegistry.getViewer(player); + return viewer != null && viewer.isMenu(this) ? viewer : null; + } + + @NotNull + private MenuViewer getViewerOrCreate(@NotNull Player player) { + MenuViewer viewer = this.getViewer(player); + return viewer == null ? new MenuViewer(this, player) : viewer; + } + + @Override + @NotNull + public List getItems(@NotNull MenuViewer viewer) { + Set items = new HashSet<>(viewer.getItems()); + items.addAll(this.getItems()); + + return items.stream() + .filter(menuItem -> menuItem.canSee(viewer)) + .sorted(Comparator.comparingInt(MenuItem::getPriority)).toList(); + } + + @Override + @Nullable + public MenuItem getItem(int slot) { + return this.getItems().stream() + .filter(item -> Lists.contains(item.getSlots(), slot)) + .max(Comparator.comparingInt(MenuItem::getPriority)).orElse(null); + } + + @Override + @Nullable + public MenuItem getItem(@NotNull MenuViewer viewer, int slot) { + return this.getItems(viewer).stream() + .filter(menuItem -> Lists.contains(menuItem.getSlots(), slot)) + .max(Comparator.comparingInt(MenuItem::getPriority)).orElse(null); + } + + @Override + public void addItem(@NotNull MenuViewer viewer, @NotNull MenuItem.Builder builder) { + this.addItem(viewer, builder.build()); + } + + @Override + public void addItem(@NotNull MenuViewer viewer, @NotNull MenuItem menuItem) { + viewer.addItem(menuItem); + } + + @Override + public void addItem(@NotNull MenuItem.Builder builder) { + this.addItem(builder.build()); + } + + @Override + public void addItem(@NotNull MenuItem menuItem) { + this.items.add(menuItem); + } + + @Override + public void handleInput(@NotNull Dialog.Builder builder) { + Dialog dialog = builder.build(); + DialogManager.startDialog(dialog); + Player player = dialog.getPlayer(); + + this.runNextTick(player::closeInventory); + } + + @Override + @NotNull + public Set getItems() { + return this.items; + } + + @NotNull + protected String getTitle(@NotNull MenuViewer viewer) { + return this.title; + } + + @Override + @NotNull + public MenuType getMenuType() { + return this.menuType; + } + + @Override + public void setMenuType(@NotNull MenuType menuType) { + this.menuType = menuType; + } + + @Override + @NotNull + public String getTitle() { + return this.title; + } + + @Override + public void setTitle(@NotNull String title) { + this.title = title; + } + + @Override + public boolean isPersistent() { + return this.persistent; + } + + public void setPersistent(boolean persistent) { + this.persistent = persistent; + } + + @Override + public int getAutoRefreshInterval() { + return this.autoRefreshInterval; + } + + @Override + public void setAutoRefreshInterval(int autoRefreshInterval) { + this.autoRefreshInterval = autoRefreshInterval; + } + + @Override + public long getAutoRefreshDate() { + return this.autoRefreshDate; + } + + @Override + public void setAutoRefreshDate(long autoRefreshDate) { + this.autoRefreshDate = autoRefreshDate; + } + + @Override + public boolean isReadyToRefresh() { + return this.autoRefreshInterval > 0 && TimeUtil.isPassed(this.autoRefreshDate); + } + + @Override + public boolean isApplyPlaceholderAPI() { + return applyPlaceholderAPI; + } + + @Override + public void setApplyPlaceholderAPI(boolean applyPlaceholderAPI) { + this.applyPlaceholderAPI = applyPlaceholderAPI; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/ui/menu/type/LinkedMenu.java b/src/main/java/su/nightexpress/nightcore/ui/menu/type/LinkedMenu.java new file mode 100644 index 0000000..4621a39 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/ui/menu/type/LinkedMenu.java @@ -0,0 +1,121 @@ +package su.nightexpress.nightcore.ui.menu.type; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MenuType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.NightPlugin; +import su.nightexpress.nightcore.language.entry.LangItem; +import su.nightexpress.nightcore.ui.dialog.Dialog; +import su.nightexpress.nightcore.ui.menu.MenuViewer; +import su.nightexpress.nightcore.ui.menu.data.LinkCache; +import su.nightexpress.nightcore.ui.menu.data.LinkHandler; +import su.nightexpress.nightcore.ui.menu.data.Linked; +import su.nightexpress.nightcore.ui.menu.item.ItemClick; +import su.nightexpress.nightcore.ui.menu.item.ItemHandler; +import su.nightexpress.nightcore.ui.menu.item.ItemOptions; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; +import su.nightexpress.nightcore.util.bukkit.NightItem; + +@SuppressWarnings("UnstableApiUsage") +public abstract class LinkedMenu

extends AbstractMenu

implements Linked { + + protected final LinkCache cache; + + public LinkedMenu(@NotNull P plugin, @NotNull MenuType menuType, @NotNull String title) { + super(plugin, menuType, title); + + this.cache = new LinkCache<>(); + } + + @Override + public void clear() { + super.clear(); + this.cache.clear(); + } + + @NotNull + @Override + public LinkCache getCache() { + return this.cache; + } + + @Override + public T getLink(@NotNull MenuViewer viewer) { + return this.getLink(viewer.getPlayer()); + } + + @Override + public T getLink(@NotNull Player player) { + return this.cache.get(player); + } + + @Override + public ItemClick manageLink(@NotNull LinkHandler handler) { + return (viewer, event) -> { + handler.handle(viewer, event, this.getLink(viewer)); + }; + } + + @Override + public boolean open(@NotNull Player player, @NotNull T obj) { + return this.open(player, viewer -> { + this.cache.set(player, obj); + }); + } + + @Override + public void handleInput(@NotNull Dialog.Builder builder) { + this.cache.addAnchor(builder.getPlayer()); + super.handleInput(builder); + } + + @Override + protected void onClose(@NotNull MenuViewer viewer) { + if (!this.cache.hasAnchor(viewer.getPlayer())) { + this.cache.clear(viewer); + } + else this.cache.removeAnchor(viewer.getPlayer()); + + super.onClose(viewer); + } + + @Override + public void addItem(@NotNull Material material, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler) { + this.addItem(material, locale, slot, handler, null); + } + + @Override + public void addItem(@NotNull Material material, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options) { + this.addItem(new NightItem(material).localized(locale), slot, handler, options); + } + + @Override + public void addItem(@NotNull ItemStack itemStack, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler) { + this.addItem(itemStack, locale, slot, handler, null); + } + + @Override + public void addItem(@NotNull ItemStack itemStack, @NotNull LangItem locale, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options) { + this.addItem(new NightItem(itemStack).localized(locale), slot, handler, options); + } + + @Override + public void addItem(@NotNull NightItem item, int slot, @NotNull LinkHandler handler) { + this.addItem(item, slot, handler, null); + } + + @Override + public void addItem(@NotNull NightItem item, int slot, @NotNull LinkHandler handler, @Nullable ItemOptions options) { + MenuItem menuItem = item.setHideComponents(true) + .toMenuItem() + .setPriority(MenuItem.HIGH_PRIORITY) + .setSlots(slot) + .setHandler(ItemHandler.forLink(this, handler, options)) + .build(); + + this.addItem(menuItem); + } +} diff --git a/src/main/java/su/nightexpress/nightcore/util/BukkitThing.java b/src/main/java/su/nightexpress/nightcore/util/BukkitThing.java index e78c25a..8043573 100644 --- a/src/main/java/su/nightexpress/nightcore/util/BukkitThing.java +++ b/src/main/java/su/nightexpress/nightcore/util/BukkitThing.java @@ -5,6 +5,7 @@ import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.generator.WorldInfo; +import org.bukkit.inventory.MenuType; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; import org.jetbrains.annotations.NotNull; @@ -115,4 +116,10 @@ public static Sound getSound(@NotNull String name) { public static Particle getParticle(@NotNull String name) { return fromRegistry(Registry.PARTICLE_TYPE, name); } + + @SuppressWarnings("UnstableApiUsage") + @Nullable + public static MenuType getMenuType(@NotNull String name) { + return fromRegistry(Registry.MENU, name); + } } diff --git a/src/main/java/su/nightexpress/nightcore/util/ItemUtil.java b/src/main/java/su/nightexpress/nightcore/util/ItemUtil.java index 601db10..5c4f954 100644 --- a/src/main/java/su/nightexpress/nightcore/util/ItemUtil.java +++ b/src/main/java/su/nightexpress/nightcore/util/ItemUtil.java @@ -33,12 +33,6 @@ public static String getItemName(@NotNull ItemStack item) { } public static void editMeta(@NotNull ItemStack item, @NotNull Consumer function) { -// ItemMeta meta = item.getItemMeta(); -// if (meta == null) return; -// -// function.accept(meta); -// item.setItemMeta(meta); - editMeta(item, ItemMeta.class, function); } @@ -91,10 +85,9 @@ public static ItemStack getSkinHead(@NotNull String texture) { return item; } - public static void setHeadSkin(@NotNull ItemStack item, @NotNull String urlData) { - if (urlData.isBlank()) return; - if (item.getType() != Material.PLAYER_HEAD) return; - if (!(item.getItemMeta() instanceof SkullMeta meta)) return; + @Nullable + public static PlayerProfile createSkinProfile(@NotNull String urlData) { + if (urlData.isBlank()) return null; String name = urlData.substring(0, 16); @@ -113,14 +106,47 @@ public static void setHeadSkin(@NotNull ItemStack item, @NotNull String urlData) textures.setSkin(url); profile.setTextures(textures); - meta.setOwnerProfile(profile); - item.setItemMeta(meta); + return profile; } catch (Exception exception) { exception.printStackTrace(); + return null; } } + public static void setHeadSkin(@NotNull ItemStack item, @NotNull String urlData) { + editMeta(item, SkullMeta.class, meta -> { + meta.setOwnerProfile(createSkinProfile(urlData)); + }); +// if (urlData.isBlank()) return; +// if (item.getType() != Material.PLAYER_HEAD) return; +// if (!(item.getItemMeta() instanceof SkullMeta meta)) return; +// +// String name = urlData.substring(0, 16); +// +// if (!urlData.startsWith(TEXTURES_HOST)) { +// urlData = TEXTURES_HOST + urlData; +// } +// +// try { +// UUID uuid = UUID.nameUUIDFromBytes(urlData.getBytes()); +// // If no name, then meta#getOwnerProfile will return 'null' (wtf?) +// // sometimes swtiching to "new" spigot api is a pain. +// // why the hell i have to dig into nms to learn that... +// PlayerProfile profile = Bukkit.createPlayerProfile(uuid, name); +// URL url = URI.create(urlData).toURL();//new URL(urlData); +// PlayerTextures textures = profile.getTextures(); +// +// textures.setSkin(url); +// profile.setTextures(textures); +// meta.setOwnerProfile(profile); +// item.setItemMeta(meta); +// } +// catch (Exception exception) { +// exception.printStackTrace(); +// } + } + @Nullable public static String getHeadSkin(@NotNull ItemStack item) { if (item.getType() != Material.PLAYER_HEAD) return null; diff --git a/src/main/java/su/nightexpress/nightcore/util/Placeholders.java b/src/main/java/su/nightexpress/nightcore/util/Placeholders.java index 87e6799..1c166f8 100644 --- a/src/main/java/su/nightexpress/nightcore/util/Placeholders.java +++ b/src/main/java/su/nightexpress/nightcore/util/Placeholders.java @@ -28,6 +28,7 @@ public class Placeholders { public static final String GENERIC_VALUE = "%value%"; public static final String GENERIC_AMOUNT = "%amount%"; public static final String GENERIC_ENTRY = "%entry%"; + public static final String GENERIC_TIME = "%time%"; public static final String TAG_NO_PREFIX = MessageTags.NO_PREFIX.getBracketsName(); public static final String TAG_LINE_BREAK = Tags.LINE_BREAK.getBracketsName(); diff --git a/src/main/java/su/nightexpress/nightcore/util/bukkit/NightItem.java b/src/main/java/su/nightexpress/nightcore/util/bukkit/NightItem.java index 767459b..28a621f 100644 --- a/src/main/java/su/nightexpress/nightcore/util/bukkit/NightItem.java +++ b/src/main/java/su/nightexpress/nightcore/util/bukkit/NightItem.java @@ -1,29 +1,25 @@ package su.nightexpress.nightcore.util.bukkit; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; +import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; -import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import su.nightexpress.nightcore.config.FileConfig; import su.nightexpress.nightcore.config.Writeable; import su.nightexpress.nightcore.core.CoreLogger; import su.nightexpress.nightcore.language.entry.LangItem; -import su.nightexpress.nightcore.util.*; +import su.nightexpress.nightcore.ui.menu.item.MenuItem; +import su.nightexpress.nightcore.util.BukkitThing; import su.nightexpress.nightcore.util.placeholder.Replacer; -import su.nightexpress.nightcore.util.text.NightMessage; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; /** * Utility class to create cosmetic items only.
@@ -31,326 +27,138 @@ */ public class NightItem implements Writeable { - private Material material; - private int amount; - - private int damage; - private boolean unbreakable; - - private String itemName; - private String displayName; - private List lore; - - private String skinURL; - private Color color; - - private Integer modelData; - private NamespacedKey modelPath; - private NamespacedKey tooltipStyle; - - private boolean enchantGlint; - private boolean hideComponents; - private boolean hideTooltip; + private final ItemStack itemStack; + private final NightMeta meta; public NightItem(@NotNull Material material) { this(material, 1); } public NightItem(@NotNull Material material, int amount) { - this.setMaterial(material); - this.setAmount(amount); + this(new ItemStack(material, amount)); + } + + public NightItem(@NotNull ItemStack itemStack) { + this(itemStack, NightMeta.fromItemStack(itemStack)); + } + + private NightItem(@NotNull ItemStack itemStack, @NotNull NightMeta meta) { + this.itemStack = new ItemStack(itemStack); + this.meta = meta; } @NotNull - public NightItem copy() { - return new NightItem(this.material, this.amount) - .setDamage(this.damage) - .setUnbreakable(this.unbreakable) - .setItemName(this.itemName) - .setDisplayName(this.displayName) - .setLore(this.lore == null ? null : new ArrayList<>(this.lore)) - .setSkinURL(this.skinURL) - .setColor(this.color) - .setModelData(this.modelData) - .setModelPath(this.modelPath) - .setTooltipStyle(this.tooltipStyle) - .setEnchantGlint(this.enchantGlint) - .setHideComponents(this.hideComponents) - .setHideTooltip(this.hideTooltip); + public static NightItem fromType(@NotNull Material material) { + return new NightItem(material); } + /** + * Wraps ItemStack as NightItem for further modifications. Retains its original ItemMeta, modifications will override specific components only. + * @param itemStack ItemStack to wrap. + * @return NighItem wrapper backed by the provided ItemStack. + */ @NotNull public static NightItem fromItemStack(@NotNull ItemStack itemStack) { - NightItem nightItem = new NightItem(itemStack.getType()); - - nightItem.setAmount(itemStack.getAmount()); - - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) return nightItem; - - if (meta instanceof Damageable damageable) { - nightItem.setDamage(damageable.getDamage()); - } - - nightItem.setDisplayName(meta.getDisplayName()); - nightItem.setLore(meta.getLore()); - nightItem.setUnbreakable(meta.isUnbreakable()); - nightItem.setSkinURL(ItemUtil.getHeadSkin(itemStack)); - nightItem.setModelData(meta.hasCustomModelData() ? meta.getCustomModelData() : null); - if (Version.isAtLeast(Version.MC_1_21)) { - nightItem.setItemName(meta.getItemName()); - nightItem.setEnchantGlint((meta.hasEnchantmentGlintOverride() && meta.getEnchantmentGlintOverride()) || meta.hasEnchants()); - nightItem.setHideTooltip(meta.isHideTooltip()); - } - if (Version.isAtLeast(Version.MC_1_21_3)) { - nightItem.setModelPath(meta.getItemModel()); - nightItem.setTooltipStyle(meta.getTooltipStyle()); - } - nightItem.setHideComponents(!meta.getItemFlags().isEmpty()); - - if (meta instanceof LeatherArmorMeta armorMeta) { - nightItem.setColor(armorMeta.getColor()); - } - else if (meta instanceof PotionMeta potionMeta) { - nightItem.setColor(potionMeta.getColor()); - } + return new NightItem(itemStack); + } - return nightItem; + /** + * Creates a new NightItem wrapper for the PLAYER_HEAD ItemStack with provided texture. + * @param skinURL Skin texture URL. + * @return NightItem wrapper backed by the PLAYER_HEAD with custom texture. + */ + @NotNull + public static NightItem asCustomHead(@NotNull String skinURL) { + return new NightItem(Material.PLAYER_HEAD).setSkinURL(skinURL); } @NotNull public static NightItem read(@NotNull FileConfig config, @NotNull String path) { - // -------- UPDATE OLD FIELDS - START -------- - String headTexture = config.getString(path + ".Head_Texture"); - if (headTexture != null && !headTexture.isEmpty()) { - try { - byte[] decoded = Base64.getDecoder().decode(headTexture); - String decodedStr = new String(decoded, StandardCharsets.UTF_8); - JsonElement element = JsonParser.parseString(decodedStr); - - String url = element.getAsJsonObject().getAsJsonObject("textures").getAsJsonObject("SKIN").get("url").getAsString(); - url = url.substring(ItemUtil.TEXTURES_HOST.length()); - - config.set(path + ".SkinURL", url); - config.remove(path + ".Head_Texture"); - } - catch (Exception exception) { - exception.printStackTrace(); - } - } - - if (config.contains(path + ".Durability")) { - int oldDurability = config.getInt(path + ".Durability"); - config.set(path + ".Durabilities.Damage", oldDurability); - config.remove(path + ".Durability"); - } - - if (config.contains(path + ".Unbreakable")) { - boolean oldUnbreakable = config.getBoolean(path + ".Unbreakable"); - config.set(path + ".Durabilities.Unbreakable", oldUnbreakable); - config.remove(path + ".Unbreakable"); - } - - if (config.contains(path + ".Name")) { - String oldName = config.getString(path + ".Name"); - config.set(path + ".Display_Name", oldName); - } - - if (config.contains(path + ".Enchants")) { - config.set(path + ".Enchant_Glint", true); - config.remove(path + ".Enchants"); - } - - if (config.contains(path + ".Item_Flags")) { - config.set(path + ".Hide_Components", true); - config.remove(path + ".Item_Flags"); - } - - if (config.contains(path + ".Custom_Model_Data")) { - int oldModel = config.getInt(path + ".Custom_Model_Data"); - config.set(path + ".Model.Data", oldModel); - config.remove(path + ".Custom_Model_Data"); - } - // -------- UPDATE OLD FIELDS - END -------- - String materialName = config.getString(path + ".Material"); + int amount = config.getInt(path + ".Amount", 1); Material material = BukkitThing.getMaterial(String.valueOf(materialName)); if (material == null) { CoreLogger.error("Invalid material '" + materialName + "'. Caused by '" + config.getFile().getAbsolutePath() + "'."); material = Material.AIR; } - NightItem nightItem = new NightItem(material); - - nightItem.setAmount(config.getInt(path + ".Amount", 1)); - nightItem.setItemName(config.getString(path + ".Item_Name")); - nightItem.setDisplayName(config.getString(path + ".Display_Name")); - nightItem.setLore(config.getStringList(path + ".Lore")); - - if (material == Material.PLAYER_HEAD) { - nightItem.setSkinURL(config.getString(path + ".SkinURL")); - } - if (config.contains(path + ".Model.Data")) { - nightItem.setModelData(config.getInt(path + ".Model.Data")); - } - if (config.contains(path + ".Model.Path")) { - String packPath = config.getString(path + ".Model.Path"); - NamespacedKey key = packPath == null ? null : NamespacedKey.minecraft(packPath); - nightItem.setModelPath(key); - } - if (config.contains(path + ".Tooltip.Style")) { - String packPath = config.getString(path + ".Tooltip.Style"); - NamespacedKey key = packPath == null ? null : NamespacedKey.minecraft(packPath); - nightItem.setTooltipStyle(key); - } - - nightItem.setDamage(config.getInt(path + ".Durabilities.Damage", 0)); - nightItem.setUnbreakable(config.getBoolean(path + ".Durabilities.Unbreakable", false)); - - nightItem.setEnchantGlint(config.getBoolean(path + ".Enchant_Glint", false)); - nightItem.setHideComponents(config.getBoolean(path + ".Hide_Components", false)); - nightItem.setHideTooltip(config.getBoolean(path + ".Hide_Tooltip", false)); - String rawColor = config.getString(path + ".Color"); - Color color = rawColor == null ? null : StringUtil.getColor(rawColor); - nightItem.setColor(color); + ItemStack itemStack = new ItemStack(material, amount); + NightMeta displayMeta = NightMeta.read(config, path); - return nightItem; + return new NightItem(itemStack, displayMeta); } @Override public void write(@NotNull FileConfig config, @NotNull String path) { - config.set(path + ".Material", BukkitThing.toString(this.material)); - config.set(path + ".Amount", this.amount == 1 ? null : this.amount); - config.set(path + ".Item_Name", this.itemName); - config.set(path + ".Display_Name", this.displayName); - config.set(path + ".Lore", this.lore); - config.set(path + ".SkinURL", this.skinURL); - config.set(path + ".Model.Data", this.modelData); - config.set(path + ".Model.Path", this.modelPath == null ? null : this.modelPath.getKey()); - config.set(path + ".Tooltip.Style", this.tooltipStyle == null ? null : this.tooltipStyle.getKey()); - config.set(path + ".Durabilities.Damage", this.damage == 0 ? null : this.damage); - config.set(path + ".Durabilities.Unbreakable", this.unbreakable ? true : null); - config.set(path + ".Enchant_Glint", this.enchantGlint ? true : null); - config.set(path + ".Hide_Components", this.hideComponents ? true : null); - config.set(path + ".Hide_Tooltip", this.hideTooltip ? true : null); - config.set(path + ".Color", this.color == null ? null : color.getRed() + "," + color.getBlue() + "," + color.getGreen()); + config.set(path + ".Material", BukkitThing.toString(this.itemStack.getType())); + config.set(path + ".Amount", this.itemStack.getAmount() == 1 ? null : this.itemStack.getAmount()); + this.meta.write(config, path); } @NotNull - public ItemStack getPlain() { - return this.getPlain(null); + public NightItem copy() { + return new NightItem(this.itemStack, this.meta.copy()); } @NotNull - public ItemStack getPlain(@Nullable Replacer replacer) { - return this.build(false, replacer); + public NightItem inherit(@NotNull NightItem other) { + this.itemStack.setType(other.getMaterial()); + this.itemStack.setAmount(other.getAmount()); + this.meta.inherit(other.meta); + + return this; } + /** + * Quickly wraps NightItem as MenuItem builder. + * @return MenuItem builder. + */ @NotNull - public ItemStack getTranslated() { - return this.getTranslated(null); + public MenuItem.Builder toMenuItem() { + return MenuItem.builder(this); } + /** + * Builds new ItemStack instance. + * @return New ItemStack instance. + */ @NotNull - public ItemStack getTranslated(@Nullable Replacer replacer) { - return this.build(true, replacer); + public ItemStack getItemStack() { + ItemStack stack = new ItemStack(this.itemStack); + this.meta.apply(stack, true); + return stack; } -// @NotNull -// private ItemStack build(boolean legacy, @NotNull Consumer consumer) { -// Replacer replacer = new Replacer(); -// consumer.accept(replacer); -// -// return this.build(legacy, replacer); -// } - @NotNull - private ItemStack build(boolean legacy, @Nullable Replacer replacer) { - ItemStack itemStack = new ItemStack(this.material, this.amount); - - if (this.skinURL != null) ItemUtil.setHeadSkin(itemStack, this.skinURL); - - ItemUtil.editMeta(itemStack, meta -> { - String replacedItemName = replacer == null || this.itemName == null ? this.itemName : replacer.apply(this.itemName); - String replacedDisplayName = replacer == null || this.displayName == null ? this.displayName : replacer.apply(this.displayName); - List replacedLore = replacer == null || this.lore == null ? this.lore : replacer.apply(this.lore); - - meta.setDisplayName(replacedDisplayName == null ? null : (legacy ? NightMessage.asLegacy(replacedDisplayName) : replacedDisplayName)); - meta.setLore(replacedLore == null ? null : (legacy ? NightMessage.asLegacy(this.addEmptyLines(replacedLore)) : this.addEmptyLines(replacedLore))); - meta.setCustomModelData(this.modelData); - meta.setUnbreakable(this.unbreakable); - - if (this.hideComponents) ItemUtil.hideAttributes(meta, this.material); - - if (Version.isAtLeast(Version.MC_1_21)) { - meta.setItemName(replacedItemName == null ? null : (legacy ? NightMessage.asLegacy(replacedItemName) : replacedItemName)); - if (this.enchantGlint) meta.setEnchantmentGlintOverride(true); - meta.setHideTooltip(this.hideTooltip); - } - if (Version.isAtLeast(Version.MC_1_21_3)) { - meta.setItemModel(this.modelPath); - meta.setTooltipStyle(this.tooltipStyle); - } - - if (meta instanceof Damageable damageable) { - damageable.setDamage(this.damage); - } - - switch (meta) { - case LeatherArmorMeta armorMeta -> armorMeta.setColor(this.color); - case PotionMeta potionMeta -> potionMeta.setColor(this.color); - default -> {} - } - }); - - return itemStack; + public NightMeta getMeta() { + return this.meta; } @NotNull - private List addEmptyLines(@NotNull List lore) { - for (int index = 0; index < lore.size(); index++) { - String line = lore.get(index); - if (line.equalsIgnoreCase(Placeholders.EMPTY_IF_ABOVE)) { - if (index == 0 || this.isEmpty(lore.get(index - 1))) { - lore.remove(index); - } - else lore.set(index, ""); - - return addEmptyLines(lore); - } - else if (line.equalsIgnoreCase(Placeholders.EMPTY_IF_BELOW)) { - if (index == lore.size() - 1 || this.isEmpty(lore.get(index + 1))) { - lore.remove(index); - } - else lore.set(index, ""); - - return addEmptyLines(lore); - } - } - - return lore; + public Material getMaterial() { + return this.itemStack.getType(); } - private boolean isEmpty(@NotNull String line) { - return line.isBlank() || line.equalsIgnoreCase(Placeholders.EMPTY_IF_ABOVE) || line.equalsIgnoreCase(Placeholders.EMPTY_IF_BELOW); + @NotNull + public NightItem setMaterial(@NotNull Material material) { + this.itemStack.setType(material); + return this; } - @NotNull - @Deprecated - public NightItem removeNameAndLore() { - return this.ignoreNameAndLore(); + public int getAmount() { + return this.itemStack.getAmount(); } @NotNull - @Deprecated - public NightItem singleAmount() { - return this.ignoreAmount(); + public NightItem setAmount(int amount) { + this.itemStack.setAmount(amount); + return this; } @NotNull - public NightItem localized(@NotNull LangItem langItem) { - langItem.apply(this); + public NightItem replacement(@NotNull Consumer consumer) { + this.meta.replacement(consumer); return this; } @@ -368,144 +176,125 @@ public NightItem ignoreAmount() { return this; } - public Material getMaterial() { - return material; - } - - public NightItem setMaterial(Material material) { - this.material = material; - return this; - } - - public int getAmount() { - return amount; - } - - public NightItem setAmount(int amount) { - this.amount = Math.max(1, Math.min(64, amount)); - return this; - } - - public int getDamage() { - return damage; - } - - public NightItem setDamage(int damage) { - this.damage = damage; + @NotNull + public NightItem localized(@NotNull LangItem langItem) { + this.setDisplayName(langItem.getLocalizedName()); + this.setLore(langItem.getLocalizedLore()); return this; } @Nullable public String getItemName() { - return this.itemName; + return this.meta.getItemName(); } + @NotNull public NightItem setItemName(@Nullable String itemName) { - this.itemName = itemName; + this.meta.setItemName(itemName); return this; } @Nullable public String getDisplayName() { - return this.displayName; + return this.meta.getDisplayName(); } + @NotNull public NightItem setDisplayName(@Nullable String displayName) { - this.displayName = displayName; + this.meta.setDisplayName(displayName); return this; } @Nullable public List getLore() { - return this.lore; + return this.meta.getLore(); } + @NotNull public NightItem setLore(@Nullable List lore) { - this.lore = lore; + this.meta.setLore(lore); return this; } - public String getSkinURL() { - return skinURL; - } - - public NightItem setSkinURL(@Nullable String skinURL) { - this.skinURL = skinURL; + @NotNull + public NightItem setDamage(int damage) { + this.meta.setDamage(damage); return this; } - @Nullable - public Integer getModelData() { - return this.modelData; + @NotNull + public NightItem setEnchants(@NotNull Map enchants) { + this.meta.setEnchants(enchants); + return this; } - public NightItem setModelData(@Nullable Integer modelData) { - this.modelData = modelData; + @NotNull + public NightItem setSkinURL(@Nullable String skinURL) { + this.meta.setSkinURL(skinURL); return this; } @Nullable - public NamespacedKey getModelPath() { - return this.modelPath; + public PlayerProfile getSkullOwner() { + return this.meta.getSkullOwner(); } - public NightItem setModelPath(@Nullable NamespacedKey modelPath) { - this.modelPath = modelPath; - return this; + @NotNull + public NightItem setSkullOwner(@Nullable OfflinePlayer owner) { + return this.setSkullOwner(owner == null ? null : owner.getPlayerProfile()); } - @Nullable - public NamespacedKey getTooltipStyle() { - return this.tooltipStyle; + @NotNull + public NightItem setSkullOwner(@Nullable PlayerProfile skullOwner) { + this.meta.setSkullOwner(skullOwner); + return this; } - public NightItem setTooltipStyle(@Nullable NamespacedKey tooltipStyle) { - this.tooltipStyle = tooltipStyle; + @NotNull + public NightItem setModelData(@Nullable Integer modelData) { + this.meta.setModelData(modelData); return this; } - public Color getColor() { - return color; + @NotNull + public NightItem setModelPath(@Nullable NamespacedKey modelPath) { + this.meta.setModelPath(modelPath); + return this; } - public NightItem setColor(Color color) { - this.color = color; + @NotNull + public NightItem setTooltipStyle(@Nullable NamespacedKey tooltipStyle) { + this.meta.setTooltipStyle(tooltipStyle); return this; } - public boolean isUnbreakable() { - return unbreakable; + @NotNull + public NightItem setColor(@NotNull Color color) { + this.meta.setColor(color); + return this; } + @NotNull public NightItem setUnbreakable(boolean unbreakable) { - this.unbreakable = unbreakable; + this.meta.setUnbreakable(unbreakable); return this; } - public boolean isEnchantGlint() { - return enchantGlint; - } - + @NotNull public NightItem setEnchantGlint(boolean enchantGlint) { - this.enchantGlint = enchantGlint; + this.meta.setEnchantGlint(enchantGlint); return this; } - public boolean isHideComponents() { - return hideComponents; - } - + @NotNull public NightItem setHideComponents(boolean hideComponents) { - this.hideComponents = hideComponents; + this.meta.setHideComponents(hideComponents); return this; } - public boolean isHideTooltip() { - return this.hideTooltip; - } - + @NotNull public NightItem setHideTooltip(boolean hideTooltip) { - this.hideTooltip = hideTooltip; + this.meta.setHideTooltip(hideTooltip); return this; } } diff --git a/src/main/java/su/nightexpress/nightcore/util/bukkit/NightMeta.java b/src/main/java/su/nightexpress/nightcore/util/bukkit/NightMeta.java new file mode 100644 index 0000000..07c2279 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/util/bukkit/NightMeta.java @@ -0,0 +1,514 @@ +package su.nightexpress.nightcore.util.bukkit; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.bukkit.Color; +import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.*; +import org.bukkit.profile.PlayerProfile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.config.Writeable; +import su.nightexpress.nightcore.language.entry.LangItem; +import su.nightexpress.nightcore.util.*; +import su.nightexpress.nightcore.util.placeholder.Replacer; +import su.nightexpress.nightcore.util.text.NightMessage; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.function.Consumer; + +public class NightMeta implements Writeable { + + private String itemName; + private String displayName; + private List lore; + private Map enchants; + + private int damage; + private String skinURL; + private PlayerProfile skullOwner; + private Color color; + + private Integer modelData; + private NamespacedKey modelPath; + private NamespacedKey tooltipStyle; + + private boolean unbreakable; + private boolean enchantGlint; + private boolean hideComponents; + private boolean hideTooltip; + + private Replacer replacer; + + public NightMeta() { + + } + + @NotNull + public NightMeta copy() { + return new NightMeta() + .setDamage(this.damage) + .setUnbreakable(this.unbreakable) + .setItemName(this.itemName) + .setDisplayName(this.displayName) + .setLore(this.lore == null ? null : new ArrayList<>(this.lore)) + .setEnchants(this.enchants) + .setSkinURL(this.skinURL) + .setSkullOwner(this.skullOwner) + .setColor(this.color) + .setModelData(this.modelData) + .setModelPath(this.modelPath) + .setTooltipStyle(this.tooltipStyle) + .setEnchantGlint(this.enchantGlint) + .setHideComponents(this.hideComponents) + .setHideTooltip(this.hideTooltip) + .setReplacer(this.replacer == null ? null : new Replacer(this.replacer)); + } + + @NotNull + public NightMeta inherit(@NotNull NightMeta other) { + return this + .setDamage(other.damage) + .setUnbreakable(other.unbreakable) + .setItemName(other.itemName) + .setDisplayName(other.displayName) + .setLore(other.lore == null ? null : new ArrayList<>(other.lore)) + .setEnchants(other.enchants) + .setSkinURL(other.skinURL) + .setSkullOwner(other.skullOwner) + .setColor(other.color) + .setModelData(other.modelData) + .setModelPath(other.modelPath) + .setTooltipStyle(other.tooltipStyle) + .setEnchantGlint(other.enchantGlint) + .setHideComponents(other.hideComponents) + .setHideTooltip(other.hideTooltip) + .setReplacer(other.replacer == null ? null : new Replacer(other.replacer)); + } + + @NotNull + public static NightMeta fromItemStack(@NotNull ItemStack itemStack) { + NightMeta displayMeta = new NightMeta(); + + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) return displayMeta; + + if (meta instanceof Damageable damageable) { + displayMeta.setDamage(damageable.getDamage()); + } + if (meta instanceof SkullMeta skullMeta) { + displayMeta.setSkullOwner(skullMeta.getOwnerProfile()); + } + + displayMeta.setDisplayName(meta.hasDisplayName() ? meta.getDisplayName() : null); + displayMeta.setLore(meta.getLore()); + displayMeta.setEnchants(meta.getEnchants()); + displayMeta.setUnbreakable(meta.isUnbreakable()); + displayMeta.setSkinURL(ItemUtil.getHeadSkin(itemStack)); + displayMeta.setModelData(meta.hasCustomModelData() ? meta.getCustomModelData() : null); + if (Version.isAtLeast(Version.MC_1_21)) { + displayMeta.setItemName(meta.hasItemName() ? meta.getItemName() : null); + displayMeta.setEnchantGlint((meta.hasEnchantmentGlintOverride() && meta.getEnchantmentGlintOverride())/* || meta.hasEnchants()*/); + displayMeta.setHideTooltip(meta.isHideTooltip()); + } + if (Version.isAtLeast(Version.MC_1_21_3)) { + displayMeta.setModelPath(meta.getItemModel()); + displayMeta.setTooltipStyle(meta.getTooltipStyle()); + } + displayMeta.setHideComponents(!meta.getItemFlags().isEmpty()); + + if (meta instanceof LeatherArmorMeta armorMeta) { + displayMeta.setColor(armorMeta.getColor()); + } + else if (meta instanceof PotionMeta potionMeta) { + displayMeta.setColor(potionMeta.getColor()); + } + + return displayMeta; + } + + @NotNull + public static NightMeta read(@NotNull FileConfig config, @NotNull String path) { + // -------- UPDATE OLD FIELDS - START -------- + String headTexture = config.getString(path + ".Head_Texture"); + if (headTexture != null && !headTexture.isEmpty()) { + try { + byte[] decoded = Base64.getDecoder().decode(headTexture); + String decodedStr = new String(decoded, StandardCharsets.UTF_8); + JsonElement element = JsonParser.parseString(decodedStr); + + String url = element.getAsJsonObject().getAsJsonObject("textures").getAsJsonObject("SKIN").get("url").getAsString(); + url = url.substring(ItemUtil.TEXTURES_HOST.length()); + + config.set(path + ".SkinURL", url); + config.remove(path + ".Head_Texture"); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + + if (config.contains(path + ".Durability")) { + int oldDurability = config.getInt(path + ".Durability"); + config.set(path + ".Durabilities.Damage", oldDurability); + config.remove(path + ".Durability"); + } + + if (config.contains(path + ".Unbreakable")) { + boolean oldUnbreakable = config.getBoolean(path + ".Unbreakable"); + config.set(path + ".Durabilities.Unbreakable", oldUnbreakable); + config.remove(path + ".Unbreakable"); + } + + if (config.contains(path + ".Name")) { + String oldName = config.getString(path + ".Name"); + config.set(path + ".Display_Name", oldName); + } + + if (config.contains(path + ".Item_Flags")) { + config.set(path + ".Hide_Components", true); + config.remove(path + ".Item_Flags"); + } + + if (config.contains(path + ".Custom_Model_Data")) { + int oldModel = config.getInt(path + ".Custom_Model_Data"); + config.set(path + ".Model.Data", oldModel); + config.remove(path + ".Custom_Model_Data"); + } + // -------- UPDATE OLD FIELDS - END -------- + + NightMeta displayMeta = new NightMeta(); + + displayMeta.setItemName(config.getString(path + ".Item_Name")); + displayMeta.setDisplayName(config.getString(path + ".Display_Name")); + displayMeta.setLore(config.getStringList(path + ".Lore")); + displayMeta.setSkinURL(config.getString(path + ".SkinURL")); + + Map enchants = new HashMap<>(); + config.getSection(path + ".Enchants").forEach(sId -> { + Enchantment enchantment = BukkitThing.getEnchantment(sId); + if (enchantment == null) return; + + int level = config.getInt(path + ".Enchants." + sId); + enchants.put(enchantment, level); + }); + displayMeta.setEnchants(enchants); + + if (config.contains(path + ".Model.Data")) { + displayMeta.setModelData(config.getInt(path + ".Model.Data")); + } + if (config.contains(path + ".Model.Path")) { + String packPath = config.getString(path + ".Model.Path"); + NamespacedKey key = packPath == null ? null : NamespacedKey.fromString(packPath, null); + displayMeta.setModelPath(key); + } + if (config.contains(path + ".Tooltip.Style")) { + String packPath = config.getString(path + ".Tooltip.Style"); + NamespacedKey key = packPath == null ? null : NamespacedKey.fromString(packPath, null); + displayMeta.setTooltipStyle(key); + } + + displayMeta.setDamage(config.getInt(path + ".Durabilities.Damage", 0)); + displayMeta.setUnbreakable(config.getBoolean(path + ".Durabilities.Unbreakable", false)); + + displayMeta.setEnchantGlint(config.getBoolean(path + ".Enchant_Glint", false)); + displayMeta.setHideComponents(config.getBoolean(path + ".Hide_Components", false)); + displayMeta.setHideTooltip(config.getBoolean(path + ".Hide_Tooltip", false)); + + String rawColor = config.getString(path + ".Color"); + Color color = rawColor == null ? null : StringUtil.getColor(rawColor); + displayMeta.setColor(color); + + return displayMeta; + } + + @Override + public void write(@NotNull FileConfig config, @NotNull String path) { + config.remove(path + ".Enchants"); + + config.set(path + ".Item_Name", /*this.itemName == null || this.itemName.isBlank() ? null :*/ this.itemName); + config.set(path + ".Display_Name", /*this.displayName == null || this.displayName.isBlank() ? null :*/ this.displayName); + config.set(path + ".Lore", this.lore); + if (this.enchants != null) { + this.enchants.forEach((enchantment, level) -> config.set(path + ".Enchants." + BukkitThing.toString(enchantment), level)); + } + config.set(path + ".SkinURL", this.skinURL); + config.set(path + ".Model.Data", this.modelData); + config.set(path + ".Model.Path", this.modelPath == null ? null : this.modelPath.getKey()); + config.set(path + ".Tooltip.Style", this.tooltipStyle == null ? null : this.tooltipStyle.getKey()); + config.set(path + ".Durabilities.Damage", this.damage == 0 ? null : this.damage); + config.set(path + ".Durabilities.Unbreakable", this.unbreakable ? true : null); + config.set(path + ".Enchant_Glint", this.enchantGlint ? true : null); + config.set(path + ".Hide_Components", this.hideComponents ? true : null); + config.set(path + ".Hide_Tooltip", this.hideTooltip ? true : null); + config.set(path + ".Color", this.color == null ? null : color.getRed() + "," + color.getBlue() + "," + color.getGreen()); + } + + public void apply(@NotNull ItemStack itemStack, boolean legacy) { + if (this.skinURL != null) ItemUtil.setHeadSkin(itemStack, this.skinURL); + + ItemUtil.editMeta(itemStack, meta -> { + if (this.displayName != null) { + String name = this.replacer == null ? this.displayName : this.replacer.apply(this.displayName); + meta.setDisplayName(legacy ? NightMessage.asLegacy(name) : name); + } + if (this.itemName != null && Version.isAtLeast(Version.MC_1_21)) { + String name = this.replacer == null ? this.itemName : this.replacer.apply(this.itemName); + meta.setItemName(name == null ? null : (legacy ? NightMessage.asLegacy(name) : name)); + } + if (this.lore != null) { + List lore = this.replacer == null ? this.lore : this.replacer.apply(this.lore); + meta.setLore(legacy ? NightMessage.asLegacy(this.addEmptyLines(lore)) : this.addEmptyLines(lore)); + } + if (this.modelData != null) { + meta.setCustomModelData(this.modelData); + } + if (this.enchants != null) { + meta.getEnchants().keySet().forEach(meta::removeEnchant); + this.enchants.forEach((enchantment, level) -> meta.addEnchant(enchantment, level, true)); + } + + meta.setUnbreakable(this.unbreakable); + + if (this.hideComponents) { + ItemUtil.hideAttributes(meta, itemStack.getType()); + } + + if (Version.isAtLeast(Version.MC_1_21)) { + if (this.enchantGlint) meta.setEnchantmentGlintOverride(true); + if (this.hideTooltip) meta.setHideTooltip(true); + } + if (Version.isAtLeast(Version.MC_1_21_3)) { + if (this.modelPath != null) meta.setItemModel(this.modelPath); + if (this.tooltipStyle != null) meta.setTooltipStyle(this.tooltipStyle); + } + + if (meta instanceof Damageable damageable) { + damageable.setDamage(this.damage); + } + if (meta instanceof SkullMeta skullMeta) { + PlayerProfile profile = this.skinURL != null ? ItemUtil.createSkinProfile(this.skinURL) : this.skullOwner; + skullMeta.setOwnerProfile(profile); + } + + if (this.color != null) { + switch (meta) { + case LeatherArmorMeta armorMeta -> armorMeta.setColor(this.color); + case PotionMeta potionMeta -> potionMeta.setColor(this.color); + default -> {} + } + } + }); + } + + @NotNull + private List addEmptyLines(@NotNull List lore) { + for (int index = 0; index < lore.size(); index++) { + String line = lore.get(index); + if (line.equalsIgnoreCase(Placeholders.EMPTY_IF_ABOVE)) { + if (index == 0 || this.isEmpty(lore.get(index - 1))) { + lore.remove(index); + } + else lore.set(index, ""); + + return addEmptyLines(lore); + } + else if (line.equalsIgnoreCase(Placeholders.EMPTY_IF_BELOW)) { + if (index == lore.size() - 1 || this.isEmpty(lore.get(index + 1))) { + lore.remove(index); + } + else lore.set(index, ""); + + return addEmptyLines(lore); + } + } + + return lore; + } + + private boolean isEmpty(@NotNull String line) { + return line.isBlank() || line.equalsIgnoreCase(Placeholders.EMPTY_IF_ABOVE) || line.equalsIgnoreCase(Placeholders.EMPTY_IF_BELOW); + } + + @NotNull + public NightMeta localized(@NotNull LangItem langItem) { + this.setDisplayName(langItem.getLocalizedName()); + this.setLore(langItem.getLocalizedLore()); + return this; + } + + @NotNull + public NightMeta ignoreNameAndLore() { + this.setItemName(null); + this.setDisplayName(null); + this.setLore(null); + return this; + } + + public int getDamage() { + return damage; + } + + public NightMeta setDamage(int damage) { + this.damage = damage; + return this; + } + + @Nullable + public String getItemName() { + return this.itemName; + } + + public NightMeta setItemName(@Nullable String itemName) { + this.itemName = itemName; + return this; + } + + @Nullable + public String getDisplayName() { + return this.displayName; + } + + public NightMeta setDisplayName(@Nullable String displayName) { + this.displayName = displayName; + return this; + } + + @Nullable + public List getLore() { + return this.lore; + } + + public NightMeta setLore(@Nullable List lore) { + this.lore = lore; + return this; + } + + @Nullable + public Map getEnchants() { + return this.enchants; + } + + public NightMeta setEnchants(@Nullable Map enchants) { + this.enchants = enchants == null ? null : new HashMap<>(enchants); + return this; + } + + public String getSkinURL() { + return skinURL; + } + + public NightMeta setSkinURL(@Nullable String skinURL) { + this.skinURL = skinURL; + return this; + } + + @Nullable + public PlayerProfile getSkullOwner() { + return this.skullOwner; + } + + @NotNull + public NightMeta setSkullOwner(@Nullable OfflinePlayer owner) { + return this.setSkullOwner(owner == null ? null : owner.getPlayerProfile()); + } + + @NotNull + public NightMeta setSkullOwner(@Nullable PlayerProfile skullOwner) { + this.skullOwner = skullOwner; + return this; + } + + @Nullable + public Integer getModelData() { + return this.modelData; + } + + public NightMeta setModelData(@Nullable Integer modelData) { + this.modelData = modelData; + return this; + } + + @Nullable + public NamespacedKey getModelPath() { + return this.modelPath; + } + + public NightMeta setModelPath(@Nullable NamespacedKey modelPath) { + this.modelPath = modelPath; + return this; + } + + @Nullable + public NamespacedKey getTooltipStyle() { + return this.tooltipStyle; + } + + public NightMeta setTooltipStyle(@Nullable NamespacedKey tooltipStyle) { + this.tooltipStyle = tooltipStyle; + return this; + } + + public Color getColor() { + return color; + } + + public NightMeta setColor(Color color) { + this.color = color; + return this; + } + + public boolean isUnbreakable() { + return unbreakable; + } + + public NightMeta setUnbreakable(boolean unbreakable) { + this.unbreakable = unbreakable; + return this; + } + + public boolean isEnchantGlint() { + return enchantGlint; + } + + public NightMeta setEnchantGlint(boolean enchantGlint) { + this.enchantGlint = enchantGlint; + return this; + } + + public boolean isHideComponents() { + return hideComponents; + } + + public NightMeta setHideComponents(boolean hideComponents) { + this.hideComponents = hideComponents; + return this; + } + + public boolean isHideTooltip() { + return this.hideTooltip; + } + + public NightMeta setHideTooltip(boolean hideTooltip) { + this.hideTooltip = hideTooltip; + return this; + } + + public NightMeta setReplacer(@Nullable Replacer replacer) { + this.replacer = replacer; + return this; + } + + @NotNull + public NightMeta replacement(@NotNull Consumer consumer) { + if (this.replacer == null) this.replacer = Replacer.create(); + + consumer.accept(this.replacer); + return this; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/util/geodata/BlockPos.java b/src/main/java/su/nightexpress/nightcore/util/geodata/BlockPos.java new file mode 100644 index 0000000..9babd7c --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/util/geodata/BlockPos.java @@ -0,0 +1,126 @@ +package su.nightexpress.nightcore.util.geodata; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.util.NumberUtil; + +import java.util.Objects; + +public class BlockPos { + + private final int x,y,z; + + public BlockPos(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public boolean isEmpty() { + return this.x == 0 && this.y == 0 && this.z == 0; + } + + @NotNull + public static BlockPos empty() { + return new BlockPos(0, 0, 0); + } + + @NotNull + public static BlockPos from(@NotNull Block block) { + return new BlockPos(block.getX(), block.getY(), block.getZ()); + } + + @NotNull + public static BlockPos from(@NotNull Location location) { + return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + @NotNull + public static BlockPos read(@NotNull FileConfig config, @NotNull String path) { + String str = config.getString(path, ""); + return deserialize(str); + } + + public void write(@NotNull FileConfig config, @NotNull String path) { + config.set(path, this.serialize()); + } + + @NotNull + public static BlockPos deserialize(@NotNull String str) { + String[] split = str.split(","); + if (split.length < 3) return empty(); + + int x = NumberUtil.getAnyInteger(split[0], 0); + int y = NumberUtil.getAnyInteger(split[1], 0); + int z = NumberUtil.getAnyInteger(split[2], 0); + + return new BlockPos(x, y, z); + } + + @NotNull + public String serialize() { + return this.x + "," + this.y + "," + this.z; + } + + @NotNull + public BlockPos copy() { + return new BlockPos(this.x, this.y, this.z); + } + + @NotNull + public Chunk toChunk(@NotNull World world) { + int chunkX = this.x >> 4; + int chunkZ = this.z >> 4; + + return world.getChunkAt(chunkX, chunkZ); + } + + public boolean isChunkLoaded(@NotNull World world) { + int chunkX = this.x >> 4; + int chunkZ = this.z >> 4; + + return world.isChunkLoaded(chunkX, chunkZ); + } + + @NotNull + public Location toLocation(@NotNull World world) { + return new Location(world, this.x, this.y, this.z); + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof BlockPos other)) return false; + return x == other.x && y == other.y && z == other.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public String toString() { + return "BlockPos{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/util/geodata/ChunkPos.java b/src/main/java/su/nightexpress/nightcore/util/geodata/ChunkPos.java new file mode 100644 index 0000000..da378d6 --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/util/geodata/ChunkPos.java @@ -0,0 +1,128 @@ +package su.nightexpress.nightcore.util.geodata; + +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.util.NumberUtil; + +import java.util.Objects; + +public class ChunkPos { + + private final int x,z; + + public ChunkPos(int x, int z) { + this.x = x; + this.z = z; + } + + @NotNull + public static ChunkPos empty() { + return new ChunkPos(0, 0); + } + + @NotNull + public static ChunkPos from(@NotNull Location location) { + return from(location.getBlockX(), location.getBlockZ()); + } + + @NotNull + public static ChunkPos from(@NotNull Block block) { + return from(block.getX(), block.getZ()); + } + + @NotNull + public static ChunkPos from(@NotNull BlockPos blockPos) { + return from(blockPos.getX(), blockPos.getZ()); + } + + @NotNull + public static ChunkPos from(int x, int z) { + int chunkX = x >> 4; + int chunkZ = z >> 4; + + return new ChunkPos(chunkX, chunkZ); + } + + @NotNull + public static ChunkPos from(@NotNull ChunkSnapshot snapshot) { + return new ChunkPos(snapshot.getX(), snapshot.getZ()); + } + + @NotNull + public static ChunkPos from(@NotNull Chunk chunk) { + return new ChunkPos(chunk.getX(), chunk.getZ()); + } + + @NotNull + public static ChunkPos read(@NotNull FileConfig config, @NotNull String path) { + String str = config.getString(path, ""); + return deserialize(str); + } + + public void write(@NotNull FileConfig config, @NotNull String path) { + config.set(path, this.serialize()); + } + + @NotNull + public static ChunkPos deserialize(@NotNull String str) { + String[] split = str.split(","); + if (split.length < 2) return empty(); + + int x = NumberUtil.getAnyInteger(split[0], 0); + int z = NumberUtil.getAnyInteger(split[1], 0); + + return new ChunkPos(x, z); + } + + public boolean isLoaded(@NotNull World world) { + return world.isChunkLoaded(this.x, this.z); + } + + @NotNull + public Chunk getChunk(@NotNull World world) { + return world.getChunkAt(this.x, this.z, false); + } + + @NotNull + public String serialize() { + return this.x + "," + this.z; + } + + @NotNull + public ChunkPos copy() { + return new ChunkPos(this.x, this.z); + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ChunkPos other)) return false; + return x == other.x && z == other.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, z); + } + + @Override + public String toString() { + return "ChunkPos{" + + "x=" + x + + ", z=" + z + + '}'; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/util/geodata/ExactPos.java b/src/main/java/su/nightexpress/nightcore/util/geodata/ExactPos.java new file mode 100644 index 0000000..13612ac --- /dev/null +++ b/src/main/java/su/nightexpress/nightcore/util/geodata/ExactPos.java @@ -0,0 +1,154 @@ +package su.nightexpress.nightcore.util.geodata; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.nightcore.config.FileConfig; +import su.nightexpress.nightcore.util.NumberUtil; + +import java.util.Objects; + +public class ExactPos { + + private final double x,y,z; + private final float yaw, pitch; + + public ExactPos(double x, double y, double z, float yaw, float pitch) { + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + public boolean isEmpty() { + return this.x == 0 && this.y == 0 && this.z == 0 && this.yaw == 0 && this.pitch == 0; + } + + @NotNull + public static ExactPos empty() { + return new ExactPos(0, 0, 0, 0F, 0F); + } + + @NotNull + public static ExactPos from(@NotNull Block block) { + return from(BlockPos.from(block)); + } + + @NotNull + public static ExactPos from(@NotNull BlockPos pos) { + return new ExactPos(pos.getX(), pos.getY(), pos.getZ(), 0F, 0F); + } + + @NotNull + public static ExactPos from(@NotNull Location location) { + return new ExactPos(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getYaw(), location.getPitch()); + } + + @NotNull + public static ExactPos read(@NotNull FileConfig cfg, @NotNull String path) { + String str = cfg.getString(path, ""); + return deserialize(str); + } + + public void write(@NotNull FileConfig config, @NotNull String path) { + config.set(path, this.serialize()); + } + + @NotNull + public static ExactPos deserialize(@NotNull String str) { + String[] split = str.split(","); + if (split.length < 3) return empty(); + + int x = (int) NumberUtil.getAnyDouble(split[0], 0); + int y = (int) NumberUtil.getAnyDouble(split[1], 0); + int z = (int) NumberUtil.getAnyDouble(split[2], 0); + float pitch = (float) NumberUtil.getAnyDouble(split[3], 0); + float yaw = (float) NumberUtil.getAnyDouble(split[4], 0); + + return new ExactPos(x, y, z, yaw, pitch); + } + + @NotNull + public String serialize() { + return this.x + "," + this.y + "," + this.z + "," + this.pitch + "," + this.yaw; + } + + @NotNull + public Chunk toChunk(@NotNull World world) { + int chunkX = (int) this.x >> 4; + int chunkZ = (int) this.z >> 4; + + return world.getChunkAt(chunkX, chunkZ); + } + + public boolean isChunkLoaded(@NotNull World world) { + int chunkX = (int) this.x >> 4; + int chunkZ = (int) this.z >> 4; + + return world.isChunkLoaded(chunkX, chunkZ); + } + + @NotNull + public Location toLocation(@NotNull World world) { + Location location = new Location(world, this.x, this.y, this.z); + location.setPitch(this.pitch); + location.setYaw(this.yaw); + return location; + } + + @NotNull + public ExactPos copy() { + return new ExactPos(this.x, this.y, this.z, this.yaw, this.pitch); + } + + @NotNull + public BlockPos toBlockPos() { + return new BlockPos((int) this.x, (int) this.y, (int) this.z); + } + + public double getX() { + return this.x; + } + + public double getY() { + return this.y; + } + + public double getZ() { + return this.z; + } + + public float getYaw() { + return this.yaw; + } + + public float getPitch() { + return this.pitch; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ExactPos other)) return false; + return x == other.x && y == other.y && z == other.z && Float.compare(yaw, other.yaw) == 0 && Float.compare(pitch, other.pitch) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z, yaw, pitch); + } + + @Override + public String toString() { + return "ExactPos{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + ", yaw=" + yaw + + ", pitch=" + pitch + + '}'; + } +} diff --git a/src/main/java/su/nightexpress/nightcore/util/placeholder/Replacer.java b/src/main/java/su/nightexpress/nightcore/util/placeholder/Replacer.java index 1f2cd6a..4ab5598 100644 --- a/src/main/java/su/nightexpress/nightcore/util/placeholder/Replacer.java +++ b/src/main/java/su/nightexpress/nightcore/util/placeholder/Replacer.java @@ -8,6 +8,7 @@ import su.nightexpress.nightcore.util.ItemUtil; import su.nightexpress.nightcore.util.Placeholders; import su.nightexpress.nightcore.util.Plugins; +import su.nightexpress.nightcore.util.Version; import su.nightexpress.nightcore.util.text.NightMessage; import su.nightexpress.nightcore.util.text.TextRoot; @@ -28,6 +29,11 @@ public Replacer() { this.replacers = new ArrayList<>(); } + public Replacer(@NotNull Replacer other) { + this.placeholders = new PlaceholderList<>(other.placeholders); + this.replacers = new ArrayList<>(other.replacers); + } + @NotNull public static Replacer create() { return new Replacer(); @@ -90,10 +96,6 @@ public String apply(@NotNull String source) { @NotNull public List apply(@NotNull List list) { -// List replaced = new ArrayList<>(); -// list.forEach(line -> replaced.add(this.apply(line))); -// return replaced; - List result = new ArrayList<>(list); for (UnaryOperator operator : this.getReplacers()) { result = replaceList(result, operator); @@ -110,11 +112,13 @@ public ItemStack apply(@NotNull ItemStack itemStack) { @NotNull public ItemMeta apply(@NotNull ItemMeta meta) { - String displayName = meta.hasDisplayName() ? meta.getDisplayName() : null; List lore = meta.getLore(); - if (displayName != null) { - meta.setDisplayName(this.apply(displayName)); + if (Version.isAtLeast(Version.MC_1_21) && meta.hasItemName()) { + meta.setItemName(this.apply(meta.getItemName())); + } + if (meta.hasDisplayName()) { + meta.setDisplayName(this.apply(meta.getDisplayName())); } if (lore != null) { meta.setLore(this.apply(lore)); diff --git a/src/main/java/su/nightexpress/nightcore/util/rankmap/IntRankMap.java b/src/main/java/su/nightexpress/nightcore/util/rankmap/IntRankMap.java index b975a9a..5f45032 100644 --- a/src/main/java/su/nightexpress/nightcore/util/rankmap/IntRankMap.java +++ b/src/main/java/su/nightexpress/nightcore/util/rankmap/IntRankMap.java @@ -16,6 +16,13 @@ public IntRankMap(@NotNull Mode mode, @NotNull String permissionPrefix, int defa super(mode, permissionPrefix, defaultValue, values); } + @Override + @NotNull + public IntRankMap addValue(@NotNull String key, @NotNull Integer value) { + super.addValue(key, value); + return this; + } + @NotNull public static IntRankMap ranked(int defaultValue) { return ranked(CREATOR, defaultValue);