diff --git a/build.gradle.kts b/build.gradle.kts index 643c40c2..8d4c80ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ version = "$majorVersion-$minorVersion" repositories { mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") maven("https://repo.glaremasters.me/repository/public/") @@ -24,7 +25,7 @@ repositories { } dependencies { - compileOnly(libs.spigot) + compileOnly(libs.paper) compileOnly(libs.vault) compileOnly(libs.authlib) @@ -41,18 +42,17 @@ dependencies { compileOnly(libs.papi) implementation(libs.nashorn) - implementation(libs.adventure.platform) - implementation(libs.adventure.minimessage) + compileOnly(libs.adventure.platform) + compileOnly(libs.adventure.minimessage) implementation(libs.bstats) - compileOnly("org.jetbrains:annotations:23.0.0") + compileOnly(libs.annotations) } tasks { shadowJar { relocate("org.objectweb.asm", "com.extendedclip.deluxemenus.libs.asm") relocate("org.openjdk.nashorn", "com.extendedclip.deluxemenus.libs.nashorn") - relocate("net.kyori", "com.extendedclip.deluxemenus.libs.adventure") relocate("org.bstats", "com.extendedclip.deluxemenus.libs.bstats") archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } @@ -64,7 +64,11 @@ tasks { processResources { filesMatching("plugin.yml") { - expand("version" to rootProject.version) + expand( + "version" to rootProject.version, + "adventurePlatform" to libs.adventure.platform.get().let { "${it.group}:${it.name}:${it.version}" }, + "adventureMiniMessage" to libs.adventure.minimessage.get().let { "${it.group}:${it.name}:${it.version}" } + ) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f479961..7268d476 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Compile only -spigot = "1.21.5-R0.1-SNAPSHOT" +paper = "1.21.6-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" @@ -13,6 +13,7 @@ papi = "2.11.6" score = "4.24.3.5" sig = "1.5.0" bstats = "3.1.0" +annotations = "23.0.0" # Implementation nashorn = "15.6" @@ -21,7 +22,7 @@ adventure-minimessage = "4.21.0" [libraries] # Compile only -spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } +paper = { module = "io.papermc.paper:paper-api", version.ref = "paper" } vault = { module = "com.github.milkbowl:VaultAPI", version.ref = "vault" } authlib = { module = "com.mojang:authlib", version.ref = "authlib" } headdb = { module = "com.arcaniax:HeadDatabase-API", version.ref = "headdb" } @@ -33,6 +34,7 @@ mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } papi = { module = "me.clip:placeholderapi", version.ref = "papi" } score = { module = "com.github.Ssomar-Developement:SCore", version.ref = "score" } sig = { module = "io.github.valerashimchuck:simpleitemgenerator-api", version.ref = "sig" } +annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } # Implementation nashorn = { module = "org.openjdk.nashorn:nashorn-core", version.ref = "nashorn" } diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index f812f937..58340c98 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -25,6 +25,7 @@ import net.kyori.adventure.text.Component; import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; +import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -169,7 +170,7 @@ public void sms(CommandSender s, Component msg) { } public void sms(CommandSender s, Messages msg) { - audiences().sender(s).sendMessage(msg.message()); + sms(s, msg.message()); } public void debug(@NotNull final DebugLevel messageDebugLevel, @NotNull final Level level, @NotNull final String... messages) { @@ -243,11 +244,11 @@ private void hookIntoVault() { "DeluxeMenus will continue to work but some features (such as the 'has money' requirement) may not be available."); } - @SuppressWarnings("deprecation") private void setUpItemHooks() { if (!VersionHelper.IS_ITEM_LEGACY) { this.head = new ItemStack(Material.PLAYER_HEAD, 1); } else { + //noinspection deprecation this.head = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3); } @@ -327,6 +328,8 @@ private void setUpMetrics() { .map(MenuOptions::type) .collect(Collectors.groupingBy(Enum::name, Collectors.summingInt(type -> 1))))); + metrics.addCustomChart(new SimplePie("is_paper", () -> VersionHelper.IS_PAPER ? "true" : "false")); + // added for 1.21 usage metrics.addCustomChart(new AdvancedPie("nbt_usage", () -> { final var results = new HashMap(); diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index d0feb75c..ce07fb9c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -10,11 +10,10 @@ import com.extendedclip.deluxemenus.utils.SoundUtils; import com.extendedclip.deluxemenus.utils.StringUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; -import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; @@ -32,8 +31,11 @@ public class ClickActionTask extends BukkitRunnable { private final UUID uuid; private final ActionType actionType; private final String exec; + // Ugly hack to get around the fact that arguments are not available at task execution time private final Map arguments; + private final TagResolver tagResolvers; + private final boolean parsePlaceholdersInArguments; private final boolean parsePlaceholdersAfterArguments; @@ -43,6 +45,7 @@ public ClickActionTask( @NotNull final ActionType actionType, @NotNull final String exec, @NotNull final Map arguments, + @NotNull final TagResolver tagResolvers, final boolean parsePlaceholdersInArguments, final boolean parsePlaceholdersAfterArguments ) { @@ -51,6 +54,7 @@ public ClickActionTask( this.actionType = actionType; this.exec = exec; this.arguments = arguments; + this.tagResolvers = tagResolvers; this.parsePlaceholdersInArguments = parsePlaceholdersInArguments; this.parsePlaceholdersAfterArguments = parsePlaceholdersAfterArguments; } @@ -62,12 +66,21 @@ public void run() { return; } + switch (actionType) { // Handle MiniMessage cases to prevent unnecessary executable parsing + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(AdventureUtils.fromString(this.exec, tagResolvers)); + return; + + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(AdventureUtils.fromString(this.exec, tagResolvers)); + return; + } + final Optional holder = Menu.getMenuHolder(player); final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null ? holder.get().getPlaceholderPlayer() : player; - final String executable = StringUtils.replacePlaceholdersAndArguments( this.exec, this.arguments, @@ -119,14 +132,6 @@ public void run() { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable); break; - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; - - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; - case MESSAGE: player.sendMessage(StringUtils.color(executable)); break; @@ -276,7 +281,7 @@ public void run() { break; case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); + plugin.audiences().sender(player).sendMessage(AdventureUtils.fromJson(executable)); break; case JSON_BROADCAST: diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java index c1ce0da1..ffb06a82 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class ExecuteCommand extends SubCommand { @@ -84,7 +83,13 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List menus = new HashMap<>(); @@ -86,16 +94,6 @@ public static void unload(final @NotNull DeluxeMenus plugin) { lastOpenedMenus.clear(); } - private void unregisterCommand() { - if (this.command != null) { - this.command.unregister(); - } - - // WARNING! A reference to the command is stored by CraftBukkit for their `/help` command. There is currently - // no way to remove this reference! - this.command = null; - } - public static void unloadForShutdown(final @NotNull DeluxeMenus plugin) { for (Player player : Bukkit.getOnlinePlayers()) { if (isInMenu(player)) { @@ -219,6 +217,16 @@ public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull P closeMenu(plugin, player, close, false); } + private void unregisterCommand() { + if (this.command != null) { + this.command.unregister(); + } + + // WARNING! A reference to the command is stored by CraftBukkit for their `/help` command. There is currently + // no way to remove this reference! + this.command = null; + } + private boolean hasOpenBypassPerm(final @NotNull Player viewer) { return viewer.hasPermission("deluxemenus.openrequirement.bypass." + this.options.name()) || viewer.hasPermission("deluxemenus.openrequirement.bypass.*"); @@ -274,17 +282,19 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - - Set activeItems = new HashSet<>(); - - for (Entry> entry : items.entrySet()) { - - for (MenuItem item : entry.getValue().values()) { - + final Set activeItems = new HashSet<>(); + for (final Entry> entry : items.entrySet()) { + for (final MenuItem item : entry.getValue().values()) { int slot = item.options().slot(); - if (slot >= this.options.size()) { plugin.debug( DebugLevel.HIGHEST, @@ -315,14 +320,11 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map h.onClick(holder)); + final Inventory inventory; - String title = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.title())); - - Inventory inventory; - - if (this.options.type() != InventoryType.CHEST) { - inventory = Bukkit.createInventory(holder, this.options.type(), title); + if (holder.useMiniMessages()) { + final Component title = AdventureUtils.fromString(this.options.title(), holder.getTagResolvers()); + inventory = this.options.type() == InventoryType.CHEST + ? Bukkit.createInventory(holder, this.options.size(), title) + : Bukkit.createInventory(holder, this.options.type(), title); } else { - inventory = Bukkit.createInventory(holder, this.options.size(), title); + final String title = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.title())); + // noinspection deprecation + inventory = this.options.type() == InventoryType.CHEST + ? Bukkit.createInventory(holder, this.options.size(), title) + : Bukkit.createInventory(holder, this.options.type(), title); } holder.setInventory(inventory); @@ -384,7 +390,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - if(options.refresh()) { + if (options.refresh()) { holder.startRefreshTask(); } @@ -395,17 +401,17 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - DeluxeMenusOpenMenuEvent openEvent = new DeluxeMenusOpenMenuEvent(viewer, holder); - Bukkit.getPluginManager().callEvent(openEvent); - }); - }); - } + if (updatePlaceholders) { + holder.startUpdatePlaceholdersTask(); + } + }); + + Bukkit.getScheduler().runTask(plugin, () -> { + DeluxeMenusOpenMenuEvent openEvent = new DeluxeMenusOpenMenuEvent(viewer, holder); + Bukkit.getPluginManager().callEvent(openEvent); + }); + }); + } public void refreshForAll() { menuHolders.stream().filter(menuHolder -> menuHolder.getMenuName().equalsIgnoreCase(options.name())).forEach(MenuHolder::refreshMenu); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index adc23192..300d9bda 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -2,7 +2,10 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.options.MenuOptions; +import com.extendedclip.deluxemenus.utils.AdventureUtils; import com.extendedclip.deluxemenus.utils.StringUtils; +import com.extendedclip.deluxemenus.utils.VersionHelper; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; @@ -13,9 +16,7 @@ import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -35,6 +36,8 @@ public class MenuHolder implements InventoryHolder { private boolean updating; private boolean parsePlaceholdersInArguments; private boolean parsePlaceholdersAfterArguments; + private boolean useMiniMessages; + private TagResolver tagResolvers = TagResolver.empty(); private Map typedArgs; public MenuHolder(final @NotNull DeluxeMenus plugin, final @NotNull Player viewer) { @@ -201,7 +204,7 @@ public void refreshMenu() { if (update && updateTask == null) { startUpdatePlaceholdersTask(); - } else if(!update && updateTask != null) { + } else if (!update && updateTask != null) { stopPlaceholderUpdate(); } @@ -221,7 +224,7 @@ public void stopPlaceholderUpdate() { } public void stopRefreshTask() { - if(refreshTask != null) { + if (refreshTask != null) { try { refreshTask.cancel(); } catch (Exception ignored) { @@ -231,7 +234,7 @@ public void stopRefreshTask() { } public void startRefreshTask() { - if(refreshTask != null) { + if (refreshTask != null) { stopRefreshTask(); } @@ -257,34 +260,28 @@ public void startUpdatePlaceholdersTask() { @Override public void run() { - if (updating) { return; } - Set items = getActiveItems(); - + final Set items = getActiveItems(); if (items == null) { return; } for (MenuItem item : items) { - if (item.options().updatePlaceholders()) { - - ItemStack i = inventory.getItem(item.options().slot()); - - if (i == null) { + final ItemStack itemStack = inventory.getItem(item.options().slot()); + if (itemStack == null) { continue; } - int amt = i.getAmount(); - + int amount = itemStack.getAmount(); if (item.options().dynamicAmount().isPresent()) { try { - amt = Integer.parseInt(setPlaceholdersAndArguments(item.options().dynamicAmount().get())); - if (amt <= 0) { - amt = 1; + amount = Integer.parseInt(setPlaceholdersAndArguments(item.options().dynamicAmount().get())); + if (amount <= 0) { + amount = 1; } } catch (Exception exception) { plugin.printStacktrace( @@ -295,22 +292,19 @@ public void run() { } } - ItemMeta meta = i.getItemMeta(); - - if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - meta.setDisplayName(StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get()))); + final ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + continue; } - if (item.options().loreHasPlaceholders()) { - meta.setLore(item.getMenuItemLore(getHolder(), item.options().lore())); - } + item.setMenuItemDisplayName(getHolder(), meta); + item.setMenuItemLore(getHolder(), meta); - i.setItemMeta(meta); - i.setAmount(amt); + itemStack.setItemMeta(meta); + itemStack.setAmount(amount); } } } - }.runTaskTimerAsynchronously(plugin, 20L, 20L * Menu.getMenuByName(menuName) .map(Menu::options) @@ -351,6 +345,29 @@ public void parsePlaceholdersAfterArguments(final boolean parsePlaceholdersAfter this.parsePlaceholdersAfterArguments = parsePlaceholdersAfterArguments; } + public void useMiniMessages(final boolean useMiniMessages) { + this.useMiniMessages = useMiniMessages; + } + + public boolean useMiniMessages() { + return VersionHelper.IS_PAPER && this.useMiniMessages; + } + + public void loadTagResolvers() { + final Player target = getPlaceholderPlayer() != null + ? getPlaceholderPlayer() + : getViewer(); + + this.tagResolvers = TagResolver.resolver( + AdventureUtils.createPlaceholderAPITagResolver(target), + AdventureUtils.createArgumentTagResolver(typedArgs) + ); + } + + public TagResolver getTagResolvers() { + return this.tagResolvers; + } + public boolean parsePlaceholdersInArguments() { return parsePlaceholdersInArguments; } @@ -359,14 +376,14 @@ public boolean parsePlaceholdersAfterArguments() { return parsePlaceholdersAfterArguments; } - public void setPlaceholderPlayer(Player placeholderPlayer) { - this.placeholderPlayer = placeholderPlayer; - } - public Player getPlaceholderPlayer() { return placeholderPlayer; } + public void setPlaceholderPlayer(Player placeholderPlayer) { + this.placeholderPlayer = placeholderPlayer; + } + public @NotNull DeluxeMenus getPlugin() { return plugin; } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 2e49d5cd..69204b60 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -7,11 +7,9 @@ import com.extendedclip.deluxemenus.menu.options.MenuItemOptions; import com.extendedclip.deluxemenus.menu.options.CustomModelDataComponent; import com.extendedclip.deluxemenus.nbt.NbtProvider; -import com.extendedclip.deluxemenus.utils.DebugLevel; -import com.extendedclip.deluxemenus.utils.ItemUtils; -import com.extendedclip.deluxemenus.utils.StringUtils; -import com.extendedclip.deluxemenus.utils.VersionHelper; +import com.extendedclip.deluxemenus.utils.*; import com.google.common.collect.ImmutableMultimap; +import net.kyori.adventure.text.Component; import org.bukkit.Color; import org.bukkit.FireworkEffect; import org.bukkit.Material; @@ -268,36 +266,8 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemMeta.setCustomModelDataComponent(parseCustomModelDataComponent(this.options.customModelDataComponent().get(), itemMeta.getCustomModelDataComponent(), holder)); } - if (this.options.displayName().isPresent()) { - final String displayName = holder.setPlaceholdersAndArguments(this.options.displayName().get()); - itemMeta.setDisplayName(StringUtils.color(displayName)); - } - - List lore = new ArrayList<>(); - // This checks if a lore should be kept from the hooked item, and then if a lore exists on the item - // ItemMeta.getLore is nullable. In that case, we just create a new ArrayList so we don't add stuff to a null list. - List itemLore = Objects.requireNonNullElse(itemMeta.getLore(), new ArrayList<>()); - // Ensures backwards compatibility with how hooked items are currently handled - LoreAppendMode mode = this.options.loreAppendMode().orElse(LoreAppendMode.OVERRIDE); - if (!this.options.hasLore() && this.options.loreAppendMode().isEmpty()) mode = LoreAppendMode.IGNORE; - switch (mode) { - case IGNORE: // DM lore is not added at all - lore.addAll(itemLore); - break; - case TOP: // DM lore is added at the top - lore.addAll(getMenuItemLore(holder, this.options.lore())); - lore.addAll(itemLore); - break; - case BOTTOM: // DM lore is bottom at the bottom - lore.addAll(itemLore); - lore.addAll(getMenuItemLore(holder, this.options.lore())); - break; - case OVERRIDE: // Lore from DM overrides the lore from the item - lore.addAll(getMenuItemLore(holder, this.options.lore())); - break; - } - - itemMeta.setLore(lore); + setMenuItemDisplayName(holder, itemMeta); + setMenuItemLore(holder, itemMeta); if (this.options.unbreakable()) { itemMeta.setUnbreakable(true); @@ -568,7 +538,87 @@ private boolean isHeadItem(@NotNull final String material) { return plugin.getItemHook(hookName).map(itemHook -> itemHook.getItem(args)); } - protected List getMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List lore) { + protected void setMenuItemDisplayName(@NotNull final MenuHolder holder, @NotNull final ItemMeta itemMeta) { + if (this.options.displayName().isPresent()) { + if (holder.useMiniMessages()) { + itemMeta.displayName(AdventureUtils.fromString(this.options.displayName().get(), holder.getTagResolvers())); + } else { + String displayName = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.displayName().get())); + //noinspection deprecation + itemMeta.setDisplayName(displayName); + } + } + } + + protected void setMenuItemLore(@NotNull final MenuHolder holder, @NotNull final ItemMeta meta) { + if (holder.useMiniMessages()) { + this.setMenuItemLoreUsingMM(holder, meta); + } else { + this.setMenuItemLoreWithoutMM(holder, meta); + } + } + + private void setMenuItemLoreWithoutMM(@NotNull final MenuHolder holder, @NotNull final ItemMeta meta) { + List lore = new ArrayList<>(); + // This checks if a lore should be kept from the hooked item, and then if a lore exists on the item + // ItemMeta.getLore is nullable. In that case, we just create a new ArrayList so we don't add stuff to a null list. + List itemLore = Objects.requireNonNullElse(meta.getLore(), new ArrayList<>()); + // Ensures backwards compatibility with how hooked items are currently handled + LoreAppendMode mode = this.options.loreAppendMode().orElse(LoreAppendMode.OVERRIDE); + if (!this.options.hasLore() && this.options.loreAppendMode().isEmpty()) { + mode = LoreAppendMode.IGNORE; + } + switch (mode) { + case IGNORE: // DM lore is not added at all + lore.addAll(itemLore); + break; + case TOP: // DM lore is added at the top + lore.addAll(getMenuItemLore(holder, this.options.lore())); + lore.addAll(itemLore); + break; + case BOTTOM: // DM lore is bottom at the bottom + lore.addAll(itemLore); + lore.addAll(getMenuItemLore(holder, this.options.lore())); + break; + case OVERRIDE: // Lore from DM overrides the lore from the item + lore.addAll(getMenuItemLore(holder, this.options.lore())); + break; + } + + meta.setLore(lore); + } + + private void setMenuItemLoreUsingMM(@NotNull final MenuHolder holder, @NotNull final ItemMeta meta) { + List lore = new ArrayList<>(); + // This checks if a lore should be kept from the hooked item, and then if a lore exists on the item + // ItemMeta.getLore is nullable. In that case, we just create a new ArrayList so we don't add stuff to a null list. + List itemLore = Objects.requireNonNullElse(meta.lore(), new ArrayList<>()); + // Ensures backwards compatibility with how hooked items are currently handled + LoreAppendMode mode = this.options.loreAppendMode().orElse(LoreAppendMode.OVERRIDE); + if (!this.options.hasLore() && this.options.loreAppendMode().isEmpty()) { + mode = LoreAppendMode.IGNORE; + } + switch (mode) { + case IGNORE: // DM lore is not added at all + lore.addAll(itemLore); + break; + case TOP: // DM lore is added at the top + lore.addAll(getMenuItemLoreUsingMM(holder, this.options.lore())); + lore.addAll(itemLore); + break; + case BOTTOM: // DM lore is bottom at the bottom + lore.addAll(itemLore); + lore.addAll(getMenuItemLoreUsingMM(holder, this.options.lore())); + break; + case OVERRIDE: // Lore from DM overrides the lore from the item + lore.addAll(getMenuItemLoreUsingMM(holder, this.options.lore())); + break; + } + + meta.lore(lore); + } + + private List getMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List lore) { return lore.stream() .map(holder::setPlaceholdersAndArguments) .map(StringUtils::color) @@ -579,6 +629,12 @@ protected List getMenuItemLore(@NotNull final MenuHolder holder, @NotNul .collect(Collectors.toList()); } + private List getMenuItemLoreUsingMM(@NotNull final MenuHolder holder, @NotNull final List lore) { + return lore.stream() + .map(line -> AdventureUtils.fromString(line, holder.getTagResolvers())) + .collect(Collectors.toList()); + } + private @NotNull org.bukkit.inventory.meta.components.CustomModelDataComponent parseCustomModelDataComponent( @NotNull final CustomModelDataComponent unparsedComponent, @NotNull final org.bukkit.inventory.meta.components.CustomModelDataComponent component, diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java index f0b4bbd8..354e9330 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java @@ -1,7 +1,6 @@ package com.extendedclip.deluxemenus.menu.options; import com.extendedclip.deluxemenus.action.ClickHandler; -import com.extendedclip.deluxemenus.config.DeluxeMenusConfig; import com.extendedclip.deluxemenus.requirement.RequirementList; import org.bukkit.DyeColor; import org.bukkit.block.banner.Pattern; @@ -50,8 +49,6 @@ public class MenuItemOptions { private final boolean unbreakable; - private final boolean displayNameHasPlaceholders; - private final boolean loreHasPlaceholders; private final boolean hasLore; private final LoreAppendMode loreAppendMode; @@ -110,8 +107,6 @@ private MenuItemOptions(final @NotNull MenuItemOptionsBuilder builder) { this.bannerMeta = builder.bannerMeta; this.itemFlags.addAll(builder.itemFlags); this.unbreakable = builder.unbreakable; - this.displayNameHasPlaceholders = builder.displayNameHasPlaceholders; - this.loreHasPlaceholders = builder.loreHasPlaceholders; this.nbtString = builder.nbtString; this.nbtByte = builder.nbtByte; this.nbtShort = builder.nbtShort; @@ -242,14 +237,6 @@ public boolean unbreakable() { return unbreakable; } - public boolean displayNameHasPlaceholders() { - return displayNameHasPlaceholders; - } - - public boolean loreHasPlaceholders() { - return loreHasPlaceholders; - } - public boolean hasLore() { return hasLore; } @@ -439,8 +426,6 @@ public static class MenuItemOptionsBuilder { private boolean unbreakable; - private boolean displayNameHasPlaceholders; - private boolean loreHasPlaceholders; private boolean hasLore; private LoreAppendMode loreAppendMode; @@ -512,15 +497,11 @@ public MenuItemOptionsBuilder lightLevel(final @Nullable String lightLevel) { public MenuItemOptionsBuilder displayName(final @Nullable String configDisplayName) { this.displayName = configDisplayName; - if (this.displayName != null) { - this.displayNameHasPlaceholders = DeluxeMenusConfig.containsPlaceholders(this.displayName); - } return this; } public MenuItemOptionsBuilder lore(final @NotNull List configLore) { this.lore = configLore; - this.loreHasPlaceholders = configLore.stream().anyMatch(DeluxeMenusConfig::containsPlaceholders); return this; } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java index 26c5b039..8e55be73 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java @@ -20,6 +20,7 @@ public class MenuOptions { private final boolean refresh; private final boolean parsePlaceholdersInArguments; private final boolean parsePlaceholdersAfterArguments; + private final boolean useMiniMessages; private final List commands; private final boolean registerCommands; @@ -41,6 +42,7 @@ private MenuOptions(final @NotNull MenuOptionsBuilder builder) { this.refresh = builder.refresh; this.parsePlaceholdersInArguments = builder.parsePlaceholdersInArguments; this.parsePlaceholdersAfterArguments = builder.parsePlaceholdersAfterArguments; + this.useMiniMessages = builder.useMiniMessages; this.commands = builder.commands; this.registerCommands = builder.registerCommands; @@ -93,6 +95,10 @@ public boolean parsePlaceholdersAfterArguments() { return this.parsePlaceholdersAfterArguments; } + public boolean useMiniMessages() { + return this.useMiniMessages; + } + public @NotNull List<@NotNull String> commands() { return this.commands; } @@ -134,6 +140,7 @@ public boolean registerCommands() { .refresh(this.refresh) .parsePlaceholdersInArguments(this.parsePlaceholdersInArguments) .parsePlaceholdersAfterArguments(this.parsePlaceholdersAfterArguments) + .useMiniMessages(this.useMiniMessages) .commands(this.commands) .registerCommands(this.registerCommands) .arguments(this.arguments) @@ -155,6 +162,7 @@ public static class MenuOptionsBuilder { private boolean refresh; private boolean parsePlaceholdersInArguments = false; private boolean parsePlaceholdersAfterArguments = false; + private boolean useMiniMessages = false; private List commands = List.of(); private boolean registerCommands = false; @@ -216,6 +224,11 @@ public MenuOptionsBuilder parsePlaceholdersAfterArguments(final boolean parsePla return this; } + public MenuOptionsBuilder useMiniMessages(final boolean useMiniMessages) { + this.useMiniMessages = useMiniMessages; + return this; + } + public MenuOptionsBuilder commands(final @NotNull List<@NotNull String> commands) { this.commands = commands; return this; diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index c4773ff9..70dafeb3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -1,23 +1,145 @@ package com.extendedclip.deluxemenus.utils; -import com.extendedclip.deluxemenus.DeluxeMenus; +import me.clip.placeholderapi.PlaceholderAPI; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import org.bukkit.command.CommandSender; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + public final class AdventureUtils { private final static GsonComponentSerializer gson = GsonComponentSerializer.gson(); + private final static MiniMessage mm = MiniMessage.miniMessage(); + private final static Pattern HEX_PATTERN = Pattern.compile(LegacyComponentSerializer.SECTION_CHAR + "x(?:" + LegacyComponentSerializer.SECTION_CHAR +"[a-fA-F0-9]){6}", Pattern.CASE_INSENSITIVE); private AdventureUtils() { throw new AssertionError("Util classes should not be initialized"); } - public static void sendJson(@NotNull final DeluxeMenus plugin, CommandSender sender, String json) { - plugin.audiences().sender(sender).sendMessage(fromJson(json)); - } - public static Component fromJson(String json) { return gson.deserialize(json); } + + public static Component fromString(String string, TagResolver... tagResolvers) { + return mm.deserialize(string, tagResolvers); + } + + public static TagResolver createArgumentTagResolver(final Map menuArguments) { + return TagResolver.resolver("arg", (argumentQueue, context) -> { + if (menuArguments.isEmpty()) { + return null; + } + + final Pair tagArguments = buildTagArguments(argumentQueue); + if (tagArguments == null) { + return null; + } + + final String argument = tagArguments.getKey(); + final boolean inserting = tagArguments.getValue() != null || tagArguments.getValue(); + + if (argument.isBlank() || !menuArguments.containsKey(argument)) { + return null; + } + + final var componentPlaceholder = mm.deserialize(menuArguments.get(argument)); + return inserting ? Tag.inserting(componentPlaceholder) : Tag.selfClosingInserting(componentPlaceholder); + }); + } + + public static TagResolver createPlaceholderAPITagResolver(OfflinePlayer player) { + return TagResolver.resolver("papi", (argumentQueue, context) -> { + final Pair tagArguments = buildTagArguments(argumentQueue); + if (tagArguments == null) { + return null; + } + + final String placeholder = tagArguments.getKey(); + final boolean inserting = tagArguments.getValue() != null || tagArguments.getValue(); + + if (placeholder.isBlank() || !placeholder.contains("_")) { + return null; + } + + String parsedPlaceholder = PlaceholderAPI.setPlaceholders(player, '%' + placeholder + '%'); + + if (parsedPlaceholder.equals("%" + placeholder + '%')) { + return null; + } + + final var kyorifiedPlaceholder = kyorify(parsedPlaceholder); + final var componentPlaceholder = mm.deserialize(kyorifiedPlaceholder); + + return inserting ? Tag.inserting(componentPlaceholder) : Tag.selfClosingInserting(componentPlaceholder); + }); + } + + private static Pair buildTagArguments(@NotNull final ArgumentQueue argumentQueue) { + if (!argumentQueue.hasNext()) { + return null; + } + + final String next = argumentQueue.pop().value(); + final boolean inserting; + final boolean append; + switch (next.toLowerCase(Locale.ROOT)) { + case "closing": + inserting = false; + append = false; + break; + case "inserting": + inserting = true; + append = false; + break; + default: + inserting = false; + append = true; + break; + } + + final List parts = new ArrayList<>(); + if (append) { + parts.add(next); + } + + while (argumentQueue.hasNext()) { + parts.add(argumentQueue.pop().value()); + } + + final var argument = String.join(":", parts); + + return Pair.of(argument, inserting); + } + + + @SuppressWarnings("deprecation") + private static String kyorify(String string) { + string = HEX_PATTERN.matcher(string).replaceAll(result -> { + String rgb = result.group(); + StringBuilder output = new StringBuilder(); + for (int i = 3; i < rgb.length(); i+=2) { + output.append(rgb.charAt(i)); + } + return ""; + }); + + for (ChatColor c : ChatColor.values()) { + String color = c.name().toLowerCase(); + if (string.equals("underline")) color+="d"; + string = string.replace(c.toString(), "<" + color + ">"); + } + string = string.replace("&u",""); + return string.replace("",""); + } } \ No newline at end of file diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 87e9a943..ae55f42d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -46,7 +46,7 @@ public final class VersionHelper { public static final int CURRENT_VERSION = getCurrentVersion(); - private static final boolean IS_PAPER = checkPaper(); + public static final boolean IS_PAPER = checkPaper(); /** * Checks if the current version includes the setTooltipStyle and setItemModel diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 22d79f63..372ca13c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,9 @@ version: ${version} authors: [ HelpChat ] softdepend: [ PlaceholderAPI, Vault, HeadDatabase, ItemsAdder, Nexo, Oraxen, ExecutableItems, ExecutableBlocks, Score, SimpleItemGenerator, MMOItems ] description: All in one inventory menu system +libraries: + - "${adventurePlatform}" + - "${adventureMiniMessage}" commands: deluxemenus: description: DeluxeMenus main commands