From 40eb331bfbd8fd1017d42d49566ea5449f8c9a65 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Fri, 27 Jun 2025 15:12:57 +0200 Subject: [PATCH 1/6] Added MiniMessage support for item names & lores and menu titles --- build.gradle.kts | 6 +- gradle/libs.versions.toml | 6 +- .../extendedclip/deluxemenus/DeluxeMenus.java | 4 +- .../deluxemenus/action/ClickActionTask.java | 8 +- .../extendedclip/deluxemenus/menu/Menu.java | 10 ++- .../deluxemenus/menu/MenuHolder.java | 10 ++- .../deluxemenus/menu/MenuItem.java | 73 ++++++++++--------- .../deluxemenus/utils/AdventureUtils.java | 19 +++-- .../deluxemenus/utils/VersionHelper.java | 2 +- 9 files changed, 79 insertions(+), 59 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 643c40c2..de5ba461 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) @@ -45,14 +46,13 @@ dependencies { implementation(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") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78b673a3..d76ef5da 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..fb334904 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -169,7 +169,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 +243,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); } diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index d0feb75c..355fe7c5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -10,11 +10,9 @@ 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 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; @@ -120,11 +118,11 @@ public void run() { break; case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + plugin.audiences().player(player).sendMessage(AdventureUtils.fromString(executable)); break; case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + plugin.audiences().all().sendMessage(AdventureUtils.fromString(executable)); break; case MESSAGE: @@ -276,7 +274,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/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index dc0c1593..4d224fcd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -4,6 +4,7 @@ import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; import com.extendedclip.deluxemenus.menu.options.MenuOptions; import com.extendedclip.deluxemenus.requirement.RequirementList; +import com.extendedclip.deluxemenus.utils.AdventureUtils; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.StringUtils; @@ -11,6 +12,7 @@ import java.util.Map.Entry; import java.util.logging.Level; +import com.extendedclip.deluxemenus.utils.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; @@ -334,9 +336,13 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map 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); + setMenuItemLore(holder, this.options.lore(), itemMeta); if (this.options.unbreakable()) { itemMeta.setUnbreakable(true); @@ -534,15 +509,47 @@ 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) { - return lore.stream() + @SuppressWarnings("unchecked") + protected void setMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List configLore, @NotNull final ItemMeta meta) { + List parsedConfigLore = configLore.stream() .map(holder::setPlaceholdersAndArguments) .map(StringUtils::color) .map(line -> line.split("\n")) .flatMap(Arrays::stream) .map(line -> line.split("\\\\n")) .flatMap(Arrays::stream) - .collect(Collectors.toList()); + .map(line -> VersionHelper.IS_PAPER + ? AdventureUtils.fromString(line) + : line + ).collect(Collectors.toList()); + + 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(VersionHelper.IS_PAPER ? meta.lore() : 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(parsedConfigLore); + lore.addAll(itemLore); + break; + case BOTTOM: // DM lore is bottom at the bottom + lore.addAll(itemLore); + lore.addAll(parsedConfigLore); + break; + case OVERRIDE: // Lore from DM overrides the lore from the item + lore.addAll(parsedConfigLore); + break; + } + if (VersionHelper.IS_PAPER) { + meta.lore((List) (List) lore); + } + else meta.setLore((List) (List) lore); } private @NotNull org.bukkit.inventory.meta.components.CustomModelDataComponent parseCustomModelDataComponent( diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index c4773ff9..09ea0461 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -1,23 +1,28 @@ package com.extendedclip.deluxemenus.utils; -import com.extendedclip.deluxemenus.DeluxeMenus; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public final class AdventureUtils { private final static GsonComponentSerializer gson = GsonComponentSerializer.gson(); + private final static MiniMessage mm = MiniMessage.miniMessage(); + private final static LegacyComponentSerializer legacy = LegacyComponentSerializer.legacySection(); 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) { + // support strings with § (placeholders parsing colors or users using it directly + if (string.contains(LegacyComponentSerializer.SECTION_CHAR + "")) { + string = mm.serialize(legacy.deserialize(string)); + } + return mm.deserialize(string); + } } \ 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 From c195df08f0c98a2e9b59a91208a4358f5881dace Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 20 Jul 2025 10:40:42 +0200 Subject: [PATCH 2/6] Use plugin.yml libraries to use MiniMessage on Spigot --- build.gradle.kts | 10 +++++++--- src/main/resources/plugin.yml | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index de5ba461..8d4c80ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,8 +42,8 @@ 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(libs.annotations) @@ -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/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 From 7e4cd3c534f8f62c774cc4ac187469e6cf41f90b Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 20 Jul 2025 11:31:10 +0200 Subject: [PATCH 3/6] Fixed MiniMessage not working when using legacy colors --- .../deluxemenus/menu/MenuHolder.java | 4 ++-- .../deluxemenus/menu/MenuItem.java | 4 ++-- .../deluxemenus/utils/AdventureUtils.java | 23 +++++++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 7c744de7..d6d7b64d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -298,9 +298,9 @@ public void run() { ItemMeta meta = i.getItemMeta(); if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - String displayName = setPlaceholdersAndArguments(item.options().displayName().get()); + String displayName = StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get())); if (VersionHelper.IS_PAPER) meta.displayName(AdventureUtils.fromString(displayName)); - else meta.setDisplayName(StringUtils.color(displayName)); + else meta.setDisplayName(displayName); } if (item.options().loreHasPlaceholders()) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 2abd6641..e069dbd1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -233,9 +233,9 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } if (this.options.displayName().isPresent()) { - final String displayName = holder.setPlaceholdersAndArguments(this.options.displayName().get()); + final String displayName = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.displayName().get())); if (VersionHelper.IS_PAPER) itemMeta.displayName(AdventureUtils.fromString(displayName)); - else itemMeta.setDisplayName(StringUtils.color(displayName)); + else itemMeta.setDisplayName(displayName); } setMenuItemLore(holder, this.options.lore(), itemMeta); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index 09ea0461..2a4332d0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -4,11 +4,14 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.ChatColor; + +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 LegacyComponentSerializer legacy = LegacyComponentSerializer.legacySection(); + 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"); @@ -18,10 +21,26 @@ public static Component fromJson(String json) { return gson.deserialize(json); } + @SuppressWarnings("deprecation") public static Component fromString(String string) { // support strings with § (placeholders parsing colors or users using it directly if (string.contains(LegacyComponentSerializer.SECTION_CHAR + "")) { - string = mm.serialize(legacy.deserialize(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",""); + string = string.replace("",""); } return mm.deserialize(string); } From b327184c73f95485d0eca443a50f19a09a0af1e3 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 20 Jul 2025 13:26:12 +0200 Subject: [PATCH 4/6] Remove legacy & MM colors compatibility in the same string & added MM tag --- .../deluxemenus/action/ClickActionTask.java | 4 +- .../extendedclip/deluxemenus/menu/Menu.java | 4 +- .../deluxemenus/menu/MenuHolder.java | 2 +- .../deluxemenus/menu/MenuItem.java | 4 +- .../deluxemenus/utils/AdventureUtils.java | 103 ++++++++++++++---- 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 355fe7c5..940e4fa4 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -118,11 +118,11 @@ public void run() { break; case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(AdventureUtils.fromString(executable)); + plugin.audiences().player(player).sendMessage(AdventureUtils.fromString(executable, player)); break; case MINI_BROADCAST: - plugin.audiences().all().sendMessage(AdventureUtils.fromString(executable)); + plugin.audiences().all().sendMessage(AdventureUtils.fromString(executable, player)); break; case MESSAGE: diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 4d224fcd..0fe43c9d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -337,11 +337,11 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map line.split("\\\\n")) .flatMap(Arrays::stream) .map(line -> VersionHelper.IS_PAPER - ? AdventureUtils.fromString(line) + ? AdventureUtils.fromString(line, holder.getPlaceholderPlayer()) : line ).collect(Collectors.toList()); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index 2a4332d0..b6d091ce 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -1,16 +1,24 @@ package com.extendedclip.deluxemenus.utils; +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.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.md_5.bungee.api.ChatColor; +import org.bukkit.OfflinePlayer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; 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 LegacyComponentSerializer legacy = LegacyComponentSerializer.legacySection(); 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() { @@ -21,27 +29,84 @@ public static Component fromJson(String json) { return gson.deserialize(json); } - @SuppressWarnings("deprecation") - public static Component fromString(String string) { - // support strings with § (placeholders parsing colors or users using it directly + public static Component fromString(String string, OfflinePlayer player) { if (string.contains(LegacyComponentSerializer.SECTION_CHAR + "")) { - 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 + ">"); + return legacy.deserialize(string); + } + return mm.deserialize(string, createPlaceholderAPITag(player)); + } + + @SuppressWarnings("deprecation") + public 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)); } - string = string.replace("&u",""); - string = string.replace("",""); + return ""; + }); + + for (ChatColor c : ChatColor.values()) { + String color = c.name().toLowerCase(); + if (string.equals("underline")) color+="d"; + string = string.replace(c.toString(), "<" + color + ">"); } - return mm.deserialize(string); + string = string.replace("&u",""); + return string.replace("",""); + } + + private static TagResolver createPlaceholderAPITag(OfflinePlayer player) { + return TagResolver.resolver("papi", (argumentQueue, context) -> { + 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 arguments = new ArrayList<>(); + if (append) { + arguments.add(next); + } + + while (argumentQueue.hasNext()) { + arguments.add(argumentQueue.pop().value()); + } + + final var placeholder = String.join(":", arguments); + 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); + System.out.println(parsedPlaceholder + " | " + kyorifiedPlaceholder + " | " + componentPlaceholder); + + return inserting ? Tag.inserting(componentPlaceholder) : Tag.selfClosingInserting(componentPlaceholder); + }); } } \ No newline at end of file From d0dcf49637f10b657fa3de3c185a1a7484e98fa9 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:31:07 +0300 Subject: [PATCH 5/6] Added simple bstats chart displaying if server is Paper or not. --- src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index fb334904..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; @@ -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(); From 84bdde0d70da2c00b4f5cf452e260099b4b24174 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:55:51 +0300 Subject: [PATCH 6/6] Add new menu option `use_mini_messages`. Enable MiniMessages only for menus that have the option set to true (false by default) and only for Paper servers. Added a new tag for MiniMessages that replaces the old pattern. MiniMessages works in menu title, item display name and item lore. and tags work for menu title, item display name, item lore but also for [minimessage] and [minibroadcast] action. For the actions, it works even if the menu does not have mini messages enabled. --- .../deluxemenus/action/ClickActionTask.java | 25 ++-- .../command/subcommand/ExecuteCommand.java | 9 +- .../deluxemenus/config/DeluxeMenusConfig.java | 3 +- .../extendedclip/deluxemenus/menu/Menu.java | 108 +++++++------- .../deluxemenus/menu/MenuHolder.java | 79 ++++++---- .../deluxemenus/menu/MenuItem.java | 107 ++++++++++---- .../menu/options/MenuItemOptions.java | 19 --- .../deluxemenus/menu/options/MenuOptions.java | 13 ++ .../deluxemenus/utils/AdventureUtils.java | 139 +++++++++++------- 9 files changed, 303 insertions(+), 199 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 940e4fa4..ce07fb9c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -10,6 +10,7 @@ import com.extendedclip.deluxemenus.utils.SoundUtils; 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.Sound; import org.bukkit.entity.Player; @@ -30,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; @@ -41,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 ) { @@ -49,6 +54,7 @@ public ClickActionTask( this.actionType = actionType; this.exec = exec; this.arguments = arguments; + this.tagResolvers = tagResolvers; this.parsePlaceholdersInArguments = parsePlaceholdersInArguments; this.parsePlaceholdersAfterArguments = parsePlaceholdersAfterArguments; } @@ -60,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, @@ -117,14 +132,6 @@ public void run() { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable); break; - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(AdventureUtils.fromString(executable, player)); - break; - - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(AdventureUtils.fromString(executable, player)); - break; - case MESSAGE: player.sendMessage(StringUtils.color(executable)); break; 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<>(); @@ -88,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)) { @@ -221,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.*"); @@ -276,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, @@ -317,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 = VersionHelper.IS_PAPER - ? Bukkit.createInventory(holder, this.options.type(), AdventureUtils.fromString(title, holder.getPlaceholderPlayer())) + 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 = VersionHelper.IS_PAPER - ? Bukkit.createInventory(holder, this.options.size(), AdventureUtils.fromString(title, holder.getPlaceholderPlayer())) - : 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); @@ -390,7 +390,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - if(options.refresh()) { + if (options.refresh()) { holder.startRefreshTask(); } @@ -401,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 6bc32148..300d9bda 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -5,6 +5,7 @@ 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; @@ -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,24 +292,19 @@ public void run() { } } - ItemMeta meta = i.getItemMeta(); - - if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - String displayName = StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get())); - if (VersionHelper.IS_PAPER) meta.displayName(AdventureUtils.fromString(displayName, getPlaceholderPlayer())); - else meta.setDisplayName(displayName); + final ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + continue; } - if (item.options().loreHasPlaceholders()) { - item.setMenuItemLore(getHolder(), item.options().lore(), meta); - } + 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) @@ -353,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; } @@ -361,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 9add047b..69204b60 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -266,13 +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 = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.displayName().get())); - if (VersionHelper.IS_PAPER) itemMeta.displayName(AdventureUtils.fromString(displayName, holder.getPlaceholderPlayer())); - else itemMeta.setDisplayName(displayName); - } - - setMenuItemLore(holder, this.options.lore(), itemMeta); + setMenuItemDisplayName(holder, itemMeta); + setMenuItemLore(holder, itemMeta); if (this.options.unbreakable()) { itemMeta.setUnbreakable(true); @@ -543,47 +538,101 @@ private boolean isHeadItem(@NotNull final String material) { return plugin.getItemHook(hookName).map(itemHook -> itemHook.getItem(args)); } - @SuppressWarnings("unchecked") - protected void setMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List configLore, @NotNull final ItemMeta meta) { - List parsedConfigLore = configLore.stream() - .map(holder::setPlaceholdersAndArguments) - .map(StringUtils::color) - .map(line -> line.split("\n")) - .flatMap(Arrays::stream) - .map(line -> line.split("\\\\n")) - .flatMap(Arrays::stream) - .map(line -> VersionHelper.IS_PAPER - ? AdventureUtils.fromString(line, holder.getPlaceholderPlayer()) - : line - ).collect(Collectors.toList()); + 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); + } + } - List lore = new ArrayList<>(); + 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(VersionHelper.IS_PAPER ? meta.lore() : meta.getLore(), new ArrayList<>()); + 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; + 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(parsedConfigLore); + 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(parsedConfigLore); + lore.addAll(getMenuItemLore(holder, this.options.lore())); break; case OVERRIDE: // Lore from DM overrides the lore from the item - lore.addAll(parsedConfigLore); + lore.addAll(getMenuItemLore(holder, this.options.lore())); break; } - if (VersionHelper.IS_PAPER) { - meta.lore((List) (List) lore); + + 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; } - else meta.setLore((List) (List) lore); + + meta.lore(lore); + } + + private List getMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List lore) { + return lore.stream() + .map(holder::setPlaceholdersAndArguments) + .map(StringUtils::color) + .map(line -> line.split("\n")) + .flatMap(Arrays::stream) + .map(line -> line.split("\\\\n")) + .flatMap(Arrays::stream) + .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( 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 b6d091ce..70dafeb3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -4,21 +4,23 @@ 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 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 LegacyComponentSerializer legacy = LegacyComponentSerializer.legacySection(); 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() { @@ -29,68 +31,43 @@ public static Component fromJson(String json) { return gson.deserialize(json); } - public static Component fromString(String string, OfflinePlayer player) { - if (string.contains(LegacyComponentSerializer.SECTION_CHAR + "")) { - return legacy.deserialize(string); - } - return mm.deserialize(string, createPlaceholderAPITag(player)); + public static Component fromString(String string, TagResolver... tagResolvers) { + return mm.deserialize(string, tagResolvers); } - @SuppressWarnings("deprecation") - public 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)); + public static TagResolver createArgumentTagResolver(final Map menuArguments) { + return TagResolver.resolver("arg", (argumentQueue, context) -> { + if (menuArguments.isEmpty()) { + return null; } - 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("",""); - } - - private static TagResolver createPlaceholderAPITag(OfflinePlayer player) { - return TagResolver.resolver("papi", (argumentQueue, context) -> { - if (!argumentQueue.hasNext()) { + final Pair tagArguments = buildTagArguments(argumentQueue); + if (tagArguments == null) { 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 String argument = tagArguments.getKey(); + final boolean inserting = tagArguments.getValue() != null || tagArguments.getValue(); - final List arguments = new ArrayList<>(); - if (append) { - arguments.add(next); + if (argument.isBlank() || !menuArguments.containsKey(argument)) { + return null; } - while (argumentQueue.hasNext()) { - arguments.add(argumentQueue.pop().value()); + 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 var placeholder = String.join(":", arguments); + final String placeholder = tagArguments.getKey(); + final boolean inserting = tagArguments.getValue() != null || tagArguments.getValue(); + if (placeholder.isBlank() || !placeholder.contains("_")) { return null; } @@ -101,12 +78,68 @@ private static TagResolver createPlaceholderAPITag(OfflinePlayer player) { return null; } - final var kyorifiedPlaceholder = kyorify(parsedPlaceholder); final var componentPlaceholder = mm.deserialize(kyorifiedPlaceholder); - System.out.println(parsedPlaceholder + " | " + kyorifiedPlaceholder + " | " + componentPlaceholder); 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