From 0eac799e797bf6add23e01f19d84890a27955eac Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:37:30 +0300 Subject: [PATCH 01/16] Moved to Paper-API, Upgraded to Java 17, Upgraded libraries --- build.gradle.kts | 12 +++++++----- gradle/libs.versions.toml | 10 ++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 643c40c2..fc23bda9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { java - id("com.gradleup.shadow") version("8.3.5") - id("com.github.ben-manes.versions") version("0.51.0") + id("com.gradleup.shadow") version("8.3.8") + id("com.github.ben-manes.versions") version("0.52.0") } // Change to true when releasing @@ -20,11 +20,12 @@ repositories { maven("https://nexus.phoenixdevt.fr/repository/maven-public/") maven("https://repo.nexomc.com/releases/") maven("https://repo.oraxen.com/releases") + maven("https://maven.devs.beer/") maven("https://jitpack.io") } dependencies { - compileOnly(libs.spigot) + compileOnly(libs.paper) compileOnly(libs.vault) compileOnly(libs.authlib) @@ -56,9 +57,10 @@ tasks { relocate("org.bstats", "com.extendedclip.deluxemenus.libs.bstats") archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } + java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 disableAutoTargetJvm() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f479961..a778d501 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,15 @@ [versions] # Compile only spigot = "1.21.5-R0.1-SNAPSHOT" +paper = "1.21.7-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" -itemsadder = "3.6.3-beta-14" -nexo = "1.1.0" +itemsadder = "4.0.10" +nexo = "1.8.0" oraxen = "1.190.0" mythiclib = "1.7.1-SNAPSHOT" -mmoitems = "6.10-SNAPSHOT" +mmoitems = "6.10.1-SNAPSHOT" papi = "2.11.6" score = "4.24.3.5" sig = "1.5.0" @@ -22,10 +23,11 @@ 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" } -itemsadder = { module = "com.github.LoneDev6:api-itemsadder", version.ref = "itemsadder" } +itemsadder = { module = "dev.lone:api-itemsadder", version.ref = "itemsadder" } nexo = { module = "com.nexomc:nexo", version.ref = "nexo" } oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxen" } mythiclib = { module = "io.lumine:MythicLib-dist", version.ref = "mythiclib"} From ce1a5596540c139120a02c02b850d2036ce334e4 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:22:54 +0300 Subject: [PATCH 02/16] Upgrade to Java 21, Make use of some Java 21 syntax sugar, Add some notes for later (regarding some API features that might break the plugin on older versions of paper), Make use of the paper-plugin.yml file --- build.gradle.kts | 6 +++--- gradle/libs.versions.toml | 2 +- .../deluxemenus/menu/command/RegistrableMenuCommand.java | 8 ++++---- .../deluxemenus/persistentmeta/PersistentMetaHandler.java | 1 + .../com/extendedclip/deluxemenus/utils/ItemUtils.java | 2 +- src/main/resources/paper-plugin.yml | 5 +++++ 6 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 src/main/resources/paper-plugin.yml diff --git a/build.gradle.kts b/build.gradle.kts index fc23bda9..29c06eaf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,13 +59,13 @@ tasks { } java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 disableAutoTargetJvm() } processResources { - filesMatching("plugin.yml") { + filesMatching(listOf("plugin.yml", "paper-plugin.yml")) { expand("version" to rootProject.version) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a778d501..9fadf790 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] # Compile only spigot = "1.21.5-R0.1-SNAPSHOT" +#paper = "1.17.1-R0.1-SNAPSHOT" paper = "1.21.7-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" @@ -22,7 +23,6 @@ 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" } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 3edf522b..6e315be0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -32,7 +32,7 @@ public class RegistrableMenuCommand extends Command { public RegistrableMenuCommand(final @NotNull DeluxeMenus plugin, final @NotNull Menu menu) { - super(menu.options().commands().isEmpty() ? menu.options().name() : menu.options().commands().get(0)); + super(menu.options().commands().isEmpty() ? menu.options().name() : menu.options().commands().getFirst()); this.plugin = plugin; this.menu = menu; @@ -42,12 +42,12 @@ public RegistrableMenuCommand(final @NotNull DeluxeMenus plugin, } @Override - public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String[] typedArgs) { + public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String @NotNull [] typedArgs) { if (this.unregistered) { throw new IllegalStateException("This command was unregistered!"); } - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { Msg.msg(sender, "Menus can only be opened by players!"); return true; } @@ -77,7 +77,6 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin } } - Player player = (Player) sender; plugin.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + menu.options().name()); menu.openMenu(player, argMap, null); return true; @@ -138,6 +137,7 @@ public void unregister() { knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommands.setAccessible(true); + //noinspection unchecked final Map knownCommandsMap = (Map) knownCommands.get(cMap.get(Bukkit.getServer())); // We need to remove every single alias because CommandMap#register() adds them all to the map. diff --git a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java index bab7d857..11182d55 100644 --- a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java +++ b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java @@ -145,6 +145,7 @@ public Map getMetaValues( return OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; } + // TODO: Blitz: It seems that PersistentDataContainer#has(NamespacedKey) does not exist in 1.17.1. if (player.getPersistentDataContainer().has(key) && (!player.getPersistentDataContainer().has(key, type.getPDType()) || !type.isSupported(player.getPersistentDataContainer().get(key, type.getPDType())))) { return OperationResult.EXISTENT_VALUE_IS_DIFFERENT_TYPE; diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java index 5c404299..6732a370 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java @@ -94,7 +94,7 @@ public static boolean hasPotionMeta(@NotNull final ItemStack itemStack) { final PotionMeta itemMeta = (PotionMeta) itemStack.getItemMeta(); if (itemMeta != null) { - itemMeta.setBasePotionType(PotionType.WATER); + itemMeta.setBasePotionType(PotionType.WATER); // TODO: Blitz: Check if this works in 1.17.1 (seems that the setBasePotionType method was only added later) itemStack.setItemMeta(itemMeta); } diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml new file mode 100644 index 00000000..0dd00eba --- /dev/null +++ b/src/main/resources/paper-plugin.yml @@ -0,0 +1,5 @@ +name: DeluxeMenus +version: '${version}' +main: com.extendedclip.deluxemenus.DeluxeMenus +description: All in one inventory menu system +api-version: '1.13' \ No newline at end of file From ce612da50bb61b1ac514e5105d49db6f6f209647 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:25:05 +0300 Subject: [PATCH 03/16] Removed unused maven repositories, and added Paper official maven repository --- build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 29c06eaf..27c11b5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,9 +14,8 @@ version = "$majorVersion-$minorVersion" repositories { mavenCentral() - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") - maven("https://repo.glaremasters.me/repository/public/") maven("https://nexus.phoenixdevt.fr/repository/maven-public/") maven("https://repo.nexomc.com/releases/") maven("https://repo.oraxen.com/releases") From d9e0108a64ccab2bfabb401eb8ad0357c0a1c0a0 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:08:00 +0300 Subject: [PATCH 04/16] Update to latest paper api version --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d86c2dd3..ee047783 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] # Compile only spigot = "26.2-R0.1-SNAPSHOT" -#paper = "1.17.1-R0.1-SNAPSHOT" -paper = "1.21.7-R0.1-SNAPSHOT" +#paper = "1.20.1-R0.1-SNAPSHOT" +paper = "26.2.build.+" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" From 4b663c13ccffca17e763efcb1e82d9a337c0d4f5 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:04:16 +0300 Subject: [PATCH 05/16] Migrating to paper - phase 0 - setup --- .github/workflows/build.yml | 8 +- build.gradle.kts | 19 +++-- gradle/libs.versions.toml | 4 +- .../command/DeluxeMenusCommand.java | 2 +- .../deluxemenus/config/DeluxeMenusConfig.java | 2 +- .../deluxemenus/placeholder/Expansion.java | 4 +- .../updatechecker/UpdateChecker.java | 6 +- .../deluxemenus/utils/DumpUtils.java | 2 +- src/main/resources/paper-plugin.yml | 85 ++++++++++++++++++- src/main/resources/plugin.yml | 36 -------- 10 files changed, 109 insertions(+), 59 deletions(-) delete mode 100644 src/main/resources/plugin.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f1111d1..ad850c92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,10 +15,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'temurin' # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. @@ -59,10 +59,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'temurin' # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. diff --git a/build.gradle.kts b/build.gradle.kts index 05eb2fb6..3e158e2d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,18 @@ dependencies { compileOnly("org.jetbrains:annotations:26.1.0") } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } + disableAutoTargetJvm() +} + tasks { + withType { + options.release.set(21) + } + shadowJar { relocate("org.objectweb.asm", "com.extendedclip.deluxemenus.libs.asm") relocate("org.openjdk.nashorn", "com.extendedclip.deluxemenus.libs.nashorn") @@ -61,14 +72,8 @@ tasks { archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } - java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - disableAutoTargetJvm() - } - processResources { - filesMatching(listOf("plugin.yml", "paper-plugin.yml")) { + filesMatching("paper-plugin.yml") { expand("version" to rootProject.version) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee047783..3e2aabbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,6 @@ [versions] # Compile only -spigot = "26.2-R0.1-SNAPSHOT" -#paper = "1.20.1-R0.1-SNAPSHOT" -paper = "26.2.build.+" +paper = "26.2.build.112-stable" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index 9aa91a97..2c757175 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -55,7 +55,7 @@ public boolean onCommand( final List arguments = Arrays.asList(args); if (arguments.isEmpty()) { - plugin.sms(sender, Messages.PLUGIN_VERSION.message().replaceText(VERSION_REPLACER_BUILDER.replacement(plugin.getDescription().getVersion()).build()).replaceText(AUTHORS_REPLACER_BUILDER.replacement(plugin.getDescription().getAuthors().stream().map(author -> text(author, NamedTextColor.WHITE)).collect(Component.toComponent(text(", ", NamedTextColor.GRAY)))).build())); + plugin.sms(sender, Messages.PLUGIN_VERSION.message().replaceText(VERSION_REPLACER_BUILDER.replacement(plugin.getPluginMeta().getVersion()).build()).replaceText(AUTHORS_REPLACER_BUILDER.replacement(plugin.getPluginMeta().getAuthors().stream().map(author -> text(author, NamedTextColor.WHITE)).collect(Component.toComponent(text(", ", NamedTextColor.GRAY)))).build())); return true; } diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 15c31ae1..61a5a2f0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -160,7 +160,7 @@ public boolean loadDefConfig() { FileConfiguration c = plugin.getConfig(); c.options().header( - "DeluxeMenus " + plugin.getDescription().getVersion() + " main configuration file" + + "DeluxeMenus " + plugin.getPluginMeta().getVersion() + " main configuration file" + "\n" + "\nA full wiki on how to use this plugin can be found at:" + "\nhttps://wiki.helpch.at/helpchat-plugins/deluxemenus" + diff --git a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java index 93e86fd2..426fcb74 100644 --- a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java +++ b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java @@ -36,12 +36,12 @@ public boolean persist() { @Override public @NotNull String getAuthor() { - return plugin.getDescription().getAuthors().toString(); + return plugin.getPluginMeta().getAuthors().toString(); } @Override public @NotNull String getVersion() { - return plugin.getDescription().getVersion(); + return plugin.getPluginMeta().getVersion(); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java index e30fe77b..dda297bc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java +++ b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java @@ -64,7 +64,7 @@ public void onJoin(final @NotNull PlayerJoinEvent event) { plugin.sms( player, Messages.UPDATE_AVAILABLE.message().replaceText( - CURRENT_VERSION_REPLACER_BUILDER.replacement(plugin.getDescription().getVersion()).build() + CURRENT_VERSION_REPLACER_BUILDER.replacement(plugin.getPluginMeta().getVersion()).build() ).replaceText( LATEST_VERSION_REPLACER_BUILDER.replacement(getLatestVersion()).build() ) @@ -94,13 +94,13 @@ public boolean check() { return false; } - if (checkHigher(plugin.getDescription().getVersion(), version)) { + if (checkHigher(plugin.getPluginMeta().getVersion(), version)) { latestVersion = version; updateAvailable = true; return true; } - latestVersion = plugin.getDescription().getVersion(); + latestVersion = plugin.getPluginMeta().getVersion(); updateAvailable = false; return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java index 64ba469c..905ea69f 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java @@ -77,7 +77,7 @@ public static String createDump( .append(System.lineSeparator()); builder.append("DeluxeMenus Version: ") - .append(plugin.getDescription().getVersion()) + .append(plugin.getPluginMeta().getVersion()) .append(System.lineSeparator()); builder.append("Java Version: ") diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 0dd00eba..2cd8afca 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -2,4 +2,87 @@ name: DeluxeMenus version: '${version}' main: com.extendedclip.deluxemenus.DeluxeMenus description: All in one inventory menu system -api-version: '1.13' \ No newline at end of file +authors: [ HelpChat ] +api-version: '1.20.6' + +dependencies: + server: + PlaceholderAPI: + # load failure. + load: BEFORE + required: false + join-classpath: true + Vault: + load: BEFORE + required: false + join-classpath: true + HeadDatabase: + load: BEFORE + required: false + join-classpath: true + HeadDB: + load: BEFORE + required: false + join-classpath: true + CraftEngine: + load: BEFORE + required: false + join-classpath: true + ItemsAdder: + load: BEFORE + required: false + join-classpath: true + Nexo: + load: BEFORE + required: false + join-classpath: true + Oraxen: + load: BEFORE + required: false + join-classpath: true + ExecutableItems: + load: BEFORE + required: false + join-classpath: true + ExecutableBlocks: + load: BEFORE + required: false + join-classpath: true + Score: + load: BEFORE + required: false + join-classpath: true + SimpleItemGenerator: + load: BEFORE + required: false + join-classpath: true + MMOItems: + load: BEFORE + required: false + join-classpath: true + +permissions: + deluxemenus.admin: + description: admin commands + default: op + deluxemenus.open: + description: open a menu with /dm open + default: op + deluxemenus.open.others: + description: open a menu with /dm open + default: op + deluxemenus.open.bypass: + description: attempt to open a menu for a viewer skipping view requirement checking for the player + default: op + deluxemenus.menu.*: + description: permission for all menus + default: op + deluxemenus.openrequirement.bypass.*: + description: Allows the viewer to bypass all menu open requirements + default: op + deluxemenus.placeholdersfor: + description: permission to parse menu placeholders with /dm open p: + default: op + deluxemenus.placeholdersfor.exempt: + description: exempt from placeholders being parsed when targeted with /dm open for players with this permission + default: op diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml deleted file mode 100644 index 09a47fef..00000000 --- a/src/main/resources/plugin.yml +++ /dev/null @@ -1,36 +0,0 @@ -api-version: 1.13 -name: DeluxeMenus -main: com.extendedclip.deluxemenus.DeluxeMenus -version: ${version} -authors: [ HelpChat ] -softdepend: [ PlaceholderAPI, Vault, HeadDatabase, HeadDB, CraftEngine, ItemsAdder, Nexo, Oraxen, ExecutableItems, ExecutableBlocks, Score, SimpleItemGenerator, MMOItems ] -description: All in one inventory menu system -commands: - deluxemenus: - description: DeluxeMenus main commands - aliases: [ dm, deluxemenu, dmenu ] -permissions: - deluxemenus.admin: - description: admin commands - default: op - deluxemenus.open: - description: open a menu with /dm open - default: op - deluxemenus.open.others: - description: open a menu with /dm open - default: op - deluxemenus.open.bypass: - description: attempt to open a menu for a viewer skipping view requirement checking for the player - default: op - deluxemenus.menu.*: - description: permission for all menus - default: op - deluxemenus.openrequirement.bypass.*: - description: Allows the viewer to bypass all menu open requirements - default: op - deluxemenus.placeholdersfor: - description: permission to parse menu placeholders with /dm open p: - default: op - deluxemenus.placeholdersfor.exempt: - description: exempt from placeholders being parsed when targeted with /dm open for players with this permission - default: op From abfb6fa5a3755b13cfa182e09aedd828a7ee6420 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:28:35 +0300 Subject: [PATCH 06/16] Migrating to paper - phase 1 - replace adventure-bukkit with paper methods --- build.gradle.kts | 3 -- gradle/libs.versions.toml | 4 -- .../extendedclip/deluxemenus/DeluxeMenus.java | 21 +------- .../deluxemenus/action/ClickActionTask.java | 8 +-- .../menu/command/RegistrableMenuCommand.java | 8 +-- .../deluxemenus/utils/AdventureUtils.java | 54 +++++++++++-------- .../deluxemenus/utils/Messages.java | 1 + src/main/resources/paper-plugin.yml | 1 - 8 files changed, 43 insertions(+), 57 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3e158e2d..ca2eb4f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,8 +45,6 @@ dependencies { compileOnly(libs.papi) implementation(libs.nashorn) - implementation(libs.adventure.platform) - implementation(libs.adventure.minimessage) implementation(libs.bstats) compileOnly("org.jetbrains:annotations:26.1.0") @@ -67,7 +65,6 @@ 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 3e2aabbe..a80ea8a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,8 +18,6 @@ bstats = "3.2.1" # Implementation nashorn = "15.7" -adventure-platform = "4.4.1" -adventure-minimessage = "5.2.0" [libraries] # Compile only @@ -41,6 +39,4 @@ sig = { module = "io.github.valerashimchuck:simpleitemgenerator-api", version.re # Implementation nashorn = { module = "org.openjdk.nashorn:nashorn-core", version.ref = "nashorn" } -adventure-platform = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } -adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-minimessage" } bstats = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index a2b2fdb1..572a8398 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -22,7 +22,6 @@ import com.extendedclip.deluxemenus.utils.VersionHelper; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; @@ -51,8 +50,6 @@ public class DeluxeMenus extends JavaPlugin { private MenuItemMarker menuItemMarker; private EphemeralCooldownManager ephemeralCooldownManager; - private BukkitAudiences audiences; - private VaultHook vaultHook; private ItemStack head; @@ -91,8 +88,6 @@ public void onEnable() { this.ephemeralCooldownManager = new EphemeralCooldownManager(this); this.ephemeralCooldownManager.startSweepTask(); - this.audiences = BukkitAudiences.create(this); - hookIntoVault(); setUpItemHooks(); @@ -120,11 +115,6 @@ public void onDisable() { Bukkit.getScheduler().cancelTasks(this); - if (this.audiences != null) { - this.audiences.close(); - this.audiences = null; - } - Menu.unloadForShutdown(this); if (this.ephemeralCooldownManager != null) { @@ -174,11 +164,11 @@ public void connect(Player p, String server) { } public void sms(CommandSender s, Component msg) { - audiences().sender(s).sendMessage(msg); + s.sendMessage(msg); } public void sms(CommandSender s, Messages msg) { - audiences().sender(s).sendMessage(msg.message()); + s.sendMessage(msg.message()); } public void debug(@NotNull final DebugLevel messageDebugLevel, @NotNull final Level level, @NotNull final String... messages) { @@ -211,13 +201,6 @@ public EphemeralCooldownManager getEphemeralCooldownManager() { return ephemeralCooldownManager; } - public BukkitAudiences audiences() { - if (this.audiences == null) { - throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); - } - return this.audiences; - } - public void clearCaches() { itemHooks.values().stream().filter(Objects::nonNull).filter(hook -> hook instanceof SimpleCache).map(hook -> (SimpleCache) hook).forEach(SimpleCache::clearCache); } diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index e9cb9d3b..96f88336 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -112,11 +112,11 @@ public void run() { break; case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + player.sendMessage(MiniMessage.miniMessage().deserialize(executable)); break; case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + AdventureUtils.broadcast(MiniMessage.miniMessage().deserialize(executable)); break; case MESSAGE: @@ -272,12 +272,12 @@ public void run() { break; case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); + AdventureUtils.sendJson(player, executable); break; case JSON_BROADCAST: case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + AdventureUtils.broadcast(AdventureUtils.fromJson(executable)); break; case REFRESH: diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 09e1d936..678bbfef 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -3,8 +3,9 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.Menu; import com.extendedclip.deluxemenus.utils.DebugLevel; +import com.extendedclip.deluxemenus.utils.Messages; import com.extendedclip.deluxemenus.utils.StringUtils; -import me.clip.placeholderapi.util.Msg; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; @@ -49,7 +50,7 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin } if (!(sender instanceof Player player)) { - Msg.msg(sender, "Menus can only be opened by players!"); + plugin.sms(sender, Messages.MENUS_ARE_PLAYER_ONLY); return true; } @@ -60,7 +61,8 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin if (typedArgs.length < menu.options().arguments().size()) { if (menu.options().argumentsUsageMessage().isPresent()) { String usageMessage = menu.options().argumentsUsageMessage().get(); - Msg.msg(sender, StringUtils.replacePlaceholders(usageMessage, (Player) sender)); + plugin.sms(sender, LegacyComponentSerializer.legacySection().deserialize( + StringUtils.color(StringUtils.replacePlaceholders(usageMessage, player)))); } return true; } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index c4773ff9..cf283991 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -1,23 +1,31 @@ -package com.extendedclip.deluxemenus.utils; - -import com.extendedclip.deluxemenus.DeluxeMenus; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -public final class AdventureUtils { - private final static GsonComponentSerializer gson = GsonComponentSerializer.gson(); - - 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); - } -} \ No newline at end of file +package com.extendedclip.deluxemenus.utils; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public final class AdventureUtils { + private final static GsonComponentSerializer gson = GsonComponentSerializer.gson(); + + private AdventureUtils() { + throw new AssertionError("Util classes should not be initialized"); + } + + public static void sendJson(@NotNull final CommandSender sender, @NotNull final String json) { + sender.sendMessage(fromJson(json)); + } + + public static Component fromJson(String json) { + return gson.deserialize(json); + } + + /** + * Sends a message to every online player. The console is deliberately not included. + */ + public static void broadcast(@NotNull final Component message) { + Audience.audience(Bukkit.getOnlinePlayers()).sendMessage(message); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/Messages.java b/src/main/java/com/extendedclip/deluxemenus/utils/Messages.java index 01919b25..fd99db89 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/Messages.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/Messages.java @@ -111,6 +111,7 @@ public enum Messages { .append(text("is exempt from placeholder target arguments.", NamedTextColor.GRAY))), MUST_SPECIFY_PLAYER(text("You must specify a player to open a menu for!", NamedTextColor.RED)), + MENUS_ARE_PLAYER_ONLY(text("Menus can only be opened by players!", NamedTextColor.RED)), WRONG_ACTION_TYPE(text("Action type specified does not exist!", NamedTextColor.RED)), CHANCE_FAIL(text("The chance for this action determined the action should not execute!", NamedTextColor.RED)), diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 2cd8afca..b04ba96e 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -8,7 +8,6 @@ api-version: '1.20.6' dependencies: server: PlaceholderAPI: - # load failure. load: BEFORE required: false join-classpath: true From e050e26e936a78b934d4b2bc592ee5ad8a93cb19 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Wed, 12 Aug 2026 21:14:52 +0300 Subject: [PATCH 07/16] Migrating to paper - phase 3 - Set version floor to 1.20.6 --- build.gradle.kts | 1 - gradle/libs.versions.toml | 2 - .../extendedclip/deluxemenus/DeluxeMenus.java | 8 +- .../deluxemenus/action/ClickActionTask.java | 2 +- .../command/subcommand/MetaCommand.java | 5 +- .../deluxemenus/config/DeluxeMenusConfig.java | 10 +- .../deluxemenus/dupe/MenuItemMarker.java | 14 +- .../deluxemenus/menu/MenuItem.java | 45 ++- .../deluxemenus/placeholder/Expansion.java | 3 +- .../requirement/HasItemRequirement.java | 14 +- .../deluxemenus/utils/SkullUtils.java | 83 +---- .../deluxemenus/utils/SoundUtils.java | 62 +++- .../deluxemenus/utils/StringUtils.java | 6 +- .../deluxemenus/utils/VersionHelper.java | 341 ++++++------------ 14 files changed, 216 insertions(+), 380 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ca2eb4f6..53a68324 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,6 @@ dependencies { compileOnly(libs.paper) compileOnly(libs.vault) - compileOnly(libs.authlib) compileOnly(libs.headdb) compileOnly(libs.headdb.api) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a80ea8a2..a2a6b17a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,6 @@ # Compile only paper = "26.2.build.112-stable" vault = "1.7.1" -authlib = "1.5.25" headdb = "1.3.2" headdb-api = "v7.0.0-rc.3" craftengine = "26.7" @@ -23,7 +22,6 @@ nashorn = "15.7" # Compile only 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" } headdb-api = { module = "com.github.SilentDevelopment.HeadDB:headdb-api", version.ref = "headdb-api" } craftengine-core = { module = "net.momirealms:craft-engine-core", version.ref = "craftengine"} diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index 572a8398..2be85fc4 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -19,7 +19,6 @@ import com.extendedclip.deluxemenus.updatechecker.UpdateChecker; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; -import com.extendedclip.deluxemenus.utils.VersionHelper; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import net.kyori.adventure.text.Component; @@ -239,13 +238,8 @@ 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 { - this.head = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3); - } + this.head = new ItemStack(Material.PLAYER_HEAD, 1); this.itemHooks = 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 96f88336..b38a3837 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -69,7 +69,7 @@ public void run() { switch (actionType) { case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + if (plugin.getPersistentMetaHandler() == null) { plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; } diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java index 631aa206..33614040 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java @@ -7,7 +7,6 @@ import com.extendedclip.deluxemenus.utils.Messages; import com.extendedclip.deluxemenus.utils.PaginationUtils; import com.extendedclip.deluxemenus.utils.StringUtils; -import com.extendedclip.deluxemenus.utils.VersionHelper; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; @@ -51,7 +50,7 @@ public void execute(@NotNull final CommandSender sender, @NotNull final List onTabComplete(@NotNull final CommandSender sender, @NotNull final List arguments) { - if (!sender.hasPermission(META_COMMAND) || !VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + if (!sender.hasPermission(META_COMMAND) || plugin.getPersistentMetaHandler() == null) { return null; } diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 61a5a2f0..89ddbc5e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -683,10 +683,8 @@ private Map> loadMenuItems(FileConfiguration addDamageOptionToBuilder(c, currentPath, key, name, builder); - if (VersionHelper.HAS_ARMOR_TRIMS) { - builder.trimMaterial(c.getString(currentPath + "trim_material", null)); - builder.trimPattern(c.getString(currentPath + "trim_pattern", null)); - } + builder.trimMaterial(c.getString(currentPath + "trim_material", null)); + builder.trimPattern(c.getString(currentPath + "trim_pattern", null)); if (c.contains(currentPath + "banner_meta") && c.isList(currentPath + "banner_meta")) { @@ -1097,10 +1095,6 @@ private RequirementList getRequirements(FileConfiguration c, String path) { break; case HAS_META: case DOES_NOT_HAVE_META: - if (!VersionHelper.IS_PDC_VERSION) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Meta requirement is not available for your server version!"); - break; - } if (c.contains(rPath + ".key") && c.contains(rPath + ".meta_type") && c.contains(rPath + ".value")) { String metaKey = c.getString(rPath + ".key"); invert = type == RequirementType.DOES_NOT_HAVE_META; diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java b/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java index 6f439afb..14a6fa38 100644 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java +++ b/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java @@ -2,11 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.dupe.marker.ItemMarker; -import com.extendedclip.deluxemenus.dupe.marker.impl.NMSMenuItemMarker; import com.extendedclip.deluxemenus.dupe.marker.impl.PDCMenuItemMarker; -import com.extendedclip.deluxemenus.dupe.marker.impl.UnavailableMenuItemMarker; -import com.extendedclip.deluxemenus.nbt.NbtProvider; -import com.extendedclip.deluxemenus.utils.VersionHelper; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -20,8 +16,6 @@ public class MenuItemMarker implements ItemMarker { private final static String DEFAULT_MARK = "DM"; private final static Pattern MARK_PATTERN = Pattern.compile("^[a-zA-Z0-9]+$"); - private final static boolean SUPPORTS_PDC = VersionHelper.IS_PDC_VERSION; - private final static boolean SUPPORTS_NMS = NbtProvider.isAvailable(); private final ItemMarker marker; private final String mark; @@ -31,13 +25,7 @@ public MenuItemMarker(@NotNull final DeluxeMenus plugin) { public MenuItemMarker(@NotNull final DeluxeMenus plugin, @NotNull final String mark) { this.mark = DEFAULT_MARK.equals(mark) || MARK_PATTERN.matcher(mark).matches() ? mark : DEFAULT_MARK; - if (SUPPORTS_PDC) { - marker = new PDCMenuItemMarker(plugin, this.mark); - } else if (SUPPORTS_NMS) { - marker = new NMSMenuItemMarker(this.mark); - } else { - marker = new UnavailableMenuItemMarker(); - } + marker = new PDCMenuItemMarker(plugin, this.mark); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 79fbd8eb..957f00a6 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -257,7 +257,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { return itemStack; } - if (VersionHelper.IS_CUSTOM_MODEL_DATA && this.options.customModelData().isPresent()) { + if (this.options.customModelData().isPresent()) { try { final int modelData = Integer.parseInt(holder.setPlaceholdersAndArguments(this.options.customModelData().get())); itemMeta.setCustomModelData(modelData); @@ -304,28 +304,27 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemMeta.setUnbreakable(true); } - if (VersionHelper.HAS_DATA_COMPONENTS) { - if (this.options.hideTooltip().isPresent()) { - String hideTooltip = holder.setPlaceholdersAndArguments(this.options.hideTooltip().get()); - itemMeta.setHideTooltip(Boolean.parseBoolean(hideTooltip)); - } - if (this.options.enchantmentGlintOverride().isPresent()) { - String enchantmentGlintOverride = holder.setPlaceholdersAndArguments(this.options.enchantmentGlintOverride().get()); - itemMeta.setEnchantmentGlintOverride(Boolean.parseBoolean(enchantmentGlintOverride)); - } - if (this.options.rarity().isPresent()) { - String rarity = holder.setPlaceholdersAndArguments(this.options.rarity().get()); - try { - itemMeta.setRarity(ItemRarity.valueOf(rarity.toUpperCase())); - } catch (IllegalArgumentException e) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Rarity " + rarity + " is not a valid!" - ); - } + if (this.options.hideTooltip().isPresent()) { + String hideTooltip = holder.setPlaceholdersAndArguments(this.options.hideTooltip().get()); + itemMeta.setHideTooltip(Boolean.parseBoolean(hideTooltip)); + } + if (this.options.enchantmentGlintOverride().isPresent()) { + String enchantmentGlintOverride = holder.setPlaceholdersAndArguments(this.options.enchantmentGlintOverride().get()); + itemMeta.setEnchantmentGlintOverride(Boolean.parseBoolean(enchantmentGlintOverride)); + } + if (this.options.rarity().isPresent()) { + String rarity = holder.setPlaceholdersAndArguments(this.options.rarity().get()); + try { + itemMeta.setRarity(ItemRarity.valueOf(rarity.toUpperCase())); + } catch (IllegalArgumentException e) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Rarity " + rarity + " is not a valid!" + ); } } + if (VersionHelper.HAS_TOOLTIP_STYLE) { if (this.options.tooltipStyle().isPresent()) { NamespacedKey tooltipStyle = NamespacedKey.fromString(holder.setPlaceholdersAndArguments(this.options.tooltipStyle().get())); @@ -337,7 +336,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } - if (VersionHelper.HAS_ARMOR_TRIMS && ItemUtils.hasArmorMeta(itemStack)) { + if (ItemUtils.hasArmorMeta(itemStack)) { final Optional trimMaterialName = this.options.trimMaterial(); final Optional trimPatternName = this.options.trimPattern(); @@ -469,7 +468,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { for (final ItemFlag flag : this.options.itemFlags()) { itemMeta.addItemFlags(flag); - if (flag == ItemFlag.HIDE_ATTRIBUTES && VersionHelper.HAS_DATA_COMPONENTS) { + if (flag == ItemFlag.HIDE_ATTRIBUTES) { itemMeta.setAttributeModifiers(ImmutableMultimap.of()); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java index 426fcb74..1f523a7e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java +++ b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java @@ -4,7 +4,6 @@ import com.extendedclip.deluxemenus.menu.Menu; import com.extendedclip.deluxemenus.menu.options.MenuOptions; import com.extendedclip.deluxemenus.persistentmeta.DataType; -import com.extendedclip.deluxemenus.utils.VersionHelper; import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.expansion.PlaceholderExpansion; @@ -112,7 +111,7 @@ public boolean persist() { return null; } - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + if (plugin.getPersistentMetaHandler() == null) { return null; } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index 9c1a1442..3a4866a7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -85,7 +85,7 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia ItemMeta metaToCheck = itemToCheck.getItemMeta(); if (wrapper.isStrict()) { if (metaToCheck != null) { - if (VersionHelper.IS_CUSTOM_MODEL_DATA && metaToCheck.hasCustomModelData()) { + if (metaToCheck.hasCustomModelData()) { return false; } if (VersionHelper.IS_CUSTOM_MODEL_DATA_COMPONENT && !isEmptyModelData(metaToCheck.getCustomModelDataComponent())) { @@ -100,18 +100,14 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia return false; } - if (VersionHelper.IS_CUSTOM_MODEL_DATA_COMPONENT) { - if (!isEmptyModelData(wrapper.getCustomModelDataComponent())) { - return false; - } + if (VersionHelper.IS_CUSTOM_MODEL_DATA_COMPONENT && !isEmptyModelData(wrapper.getCustomModelDataComponent())) { + return false; } } if (wrapper.getCustomData() != 0) { - if (VersionHelper.IS_CUSTOM_MODEL_DATA) { - if (!metaToCheck.hasCustomModelData()) return false; - if (metaToCheck.getCustomModelData() != wrapper.getCustomData()) return false; - } + if (!metaToCheck.hasCustomModelData()) return false; + if (metaToCheck.getCustomModelData() != wrapper.getCustomData()) return false; } if (VersionHelper.IS_CUSTOM_MODEL_DATA_COMPONENT && !isEmptyModelData(wrapper.getCustomModelDataComponent()) && !itemModelComponentContains(holder, metaToCheck.getCustomModelDataComponent(), wrapper.getCustomModelDataComponent())) { diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index 38ad9457..dad186e1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -4,8 +4,6 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; @@ -15,7 +13,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.util.Base64; @@ -57,25 +54,7 @@ public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final DeluxeM return head; } - if (VersionHelper.HAS_PLAYER_PROFILES) { - final PlayerProfile profile = getPlayerProfile(plugin, base64Url); - headMeta.setOwnerProfile(profile); - head.setItemMeta(headMeta); - return head; - } - - final GameProfile profile = getGameProfile(base64Url); - final Field profileField; - try { - profileField = headMeta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(headMeta, profile); - } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { - plugin.printStacktrace( - "Failed to get head item from base64 texture url", - exception - ); - } + headMeta.setOwnerProfile(getPlayerProfile(plugin, base64Url)); head.setItemMeta(headMeta); return head; } @@ -84,35 +63,13 @@ public static String getTextureFromSkull(final DeluxeMenus plugin, ItemStack ite if (!(item.getItemMeta() instanceof SkullMeta)) return null; SkullMeta meta = (SkullMeta) item.getItemMeta(); - if (VersionHelper.HAS_PLAYER_PROFILES) { - PlayerProfile profile = meta.getOwnerProfile(); - if (profile == null) return null; - - URL url = profile.getTextures().getSkin(); - if (url == null) return null; + PlayerProfile profile = meta.getOwnerProfile(); + if (profile == null) return null; - return url.toString().substring("https://textures.minecraft.net/texture/".length() - 1); - } - - GameProfile profile; - try { - final Field profileField = meta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profile = (GameProfile) profileField.get(meta); - } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { - plugin.printStacktrace( - "Failed to get base64 texture url from head item", - exception - ); - return null; - } + URL url = profile.getTextures().getSkin(); + if (url == null) return null; - for (Property property : profile.getProperties().get("textures")) { - if (property.getName().equals("textures")) { - return decodeSkinUrl(property.getValue()); - } - } - return null; + return url.toString().substring("https://textures.minecraft.net/texture/".length() - 1); } @@ -136,13 +93,11 @@ public static ItemStack getSkullByName(@NotNull final DeluxeMenus plugin, @NotNu final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); - if (VersionHelper.HAS_PLAYER_PROFILES && offlinePlayer.getPlayerProfile().getTextures().isEmpty()) { + if (offlinePlayer.getPlayerProfile().getTextures().isEmpty()) { // updates the Player Profile and populates textures for offline players - for some reason this doesn't populate when getting the Profile first time headMeta.setOwnerProfile(offlinePlayer.getPlayerProfile().update().join()); - } else if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { - headMeta.setOwningPlayer(offlinePlayer); } else { - headMeta.setOwner(offlinePlayer.getName()); + headMeta.setOwningPlayer(offlinePlayer); } head.setItemMeta(headMeta); @@ -153,30 +108,12 @@ public static String getSkullOwner(ItemStack skull) { if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; SkullMeta meta = (SkullMeta) skull.getItemMeta(); - if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { - if (meta.getOwningPlayer() == null) return null; - return meta.getOwningPlayer().getName(); - } - - return meta.getOwner(); - } - - /** - * Create a game profile object - * - * @param base64Url the base64 encoded texture url to use - * @return game profile - */ - @NotNull - private static GameProfile getGameProfile(@NotNull final String base64Url) { - GameProfile profile = new GameProfile(UUID.randomUUID(), ""); - profile.getProperties().put("textures", new Property("textures", base64Url)); - return profile; + if (meta.getOwningPlayer() == null) return null; + return meta.getOwningPlayer().getName(); } /** * Create a player profile object - * Player profile was introduced in 1.18.1+ * * @param base64Url the base64 encoded texture URL to use * @return player profile diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java index 3ffc4842..af10933b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java @@ -1,21 +1,61 @@ package com.extendedclip.deluxemenus.utils; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.Sound; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; public class SoundUtils { - public static Sound getSound(String name) { - try { - // As of Minecraft 1.21.3, the org.bukkit.Sound class type changed from Enum to Interface. - // This fixes java.lang.IncompatibleClassChangeError when trying to use versions prior to 1.21.3. - Method valueOfMethod = Class.forName("org.bukkit.Sound").getMethod("valueOf", String.class); - return (Sound) valueOfMethod.invoke(null, name); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - // Use the Sound#valueOf method if Reflection fails. - return Sound.valueOf(name); + /** + * Maps the legacy {@code Sound} enum constant names menus are configured with onto the + * registry. The mapping is derived from the registry rather than by replacing {@code _} + * with {@code .}, because that naive conversion is wrong for keys whose segments contain + * underscores: {@code BLOCK_NOTE_BLOCK_HARP} is {@code block.note_block.harp}. + *

+ * Uses {@code Registry.SOUNDS} rather than {@code Registry.SOUND_EVENT}: the latter does + * not exist on 1.20.6, the minimum supported version. On current versions they are the + * same registry instance. + */ + private static final class Lookup { + static final Map BY_LEGACY_NAME = build(); + + private static Map build() { + final Map map = new HashMap<>(); + for (final Sound sound : Registry.SOUNDS) { + final NamespacedKey key = Registry.SOUNDS.getKey(sound); + if (key == null) continue; + map.put(toLegacyName(key.value()), sound); + } + return map; } } + + private static String toLegacyName(@NotNull final String keyValue) { + return keyValue.toUpperCase(Locale.ROOT).replace('.', '_'); + } + + /** + * Resolves a sound from either a namespaced key ({@code entity.player.levelup}, + * {@code minecraft:entity.player.levelup}) or a legacy enum constant name + * ({@code ENTITY_PLAYER_LEVELUP}). + * + * @return the sound, or {@code null} if no sound matches + */ + public static @Nullable Sound getSound(@NotNull final String name) { + final NamespacedKey key = NamespacedKey.fromString(name.toLowerCase(Locale.ROOT)); + if (key != null) { + final Sound sound = Registry.SOUNDS.get(key); + if (sound != null) { + return sound; + } + } + + return Lookup.BY_LEGACY_NAME.get(toLegacyName(name)); + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java index 6ae95804..c095da20 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -28,10 +28,8 @@ public class StringUtils { public static String color(@NotNull String input) { // Hex Support for 1.16.1+ Matcher m = HEX_PATTERN.matcher(input); - if (VersionHelper.IS_HEX_VERSION) { - while (m.find()) { - input = input.replace(m.group(), ChatColor.of(m.group(1)).toString()); - } + while (m.find()) { + input = input.replace(m.group(), ChatColor.of(m.group(1)).toString()); } return ChatColor.translateAlternateColorCodes('&', input); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 87e9a943..3b181cbd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -1,223 +1,118 @@ -package com.extendedclip.deluxemenus.utils; - -import com.google.common.primitives.Ints; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.inventory.InventoryType; -import org.jetbrains.annotations.NotNull; - -/** - * Class for detecting server version. - * - * @author Matt from triumph-gui - */ -public final class VersionHelper { - - private static final String PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); - public static final String NMS_VERSION = PACKAGE_NAME.substring(PACKAGE_NAME.lastIndexOf('.') + 1); - - // Custom Model Data Component - private static final int V1_21_4 = 1_21_4; - // Tooltip Style & Item Model - private static final int V1_21_2 = 1_21_2; - // Data components - private static final int V1_20_5 = 1_20_5; - // ArmorTrims - private static final int V1_19_4 = 1194; - // PlayerProfile API - private static final int V1_18_1 = 1181; - // Mojang obfuscation changes - private static final int V1_17 = 1170; - // Material and components on items change - private static final int V1_13 = 1130; - // PDC and customModelData - private static final int V1_14 = 1140; - // Hex colors - private static final int V1_16 = 1160; - // Paper adventure changes - private static final int V1_16_5 = 1165; - // SkullMeta#setOwningPlayer was added - private static final int V1_12 = 1120; - - public static final int CURRENT_VERSION = getCurrentVersion(); - - private static final boolean IS_PAPER = checkPaper(); - - /** - * Checks if the current version includes the setTooltipStyle and setItemModel - */ - public static final boolean HAS_TOOLTIP_STYLE = CURRENT_VERSION >= V1_21_2; - - /** - * Checks if the current version includes the Data Components - */ - public static final boolean HAS_DATA_COMPONENTS = CURRENT_VERSION >= V1_20_5; - - /** - * Checks if the current version includes the ArmorTrims API - */ - public static final boolean HAS_ARMOR_TRIMS = CURRENT_VERSION >= V1_19_4; - /** - * Checks if current version includes the PlayerProfile API - */ - public static final boolean HAS_PLAYER_PROFILES = CURRENT_VERSION >= V1_18_1; - - /** - * Checks if the current version was a version without versioned packages. - */ - public static final boolean HAS_OBFUSCATED_NAMES = CURRENT_VERSION >= V1_17; - - /** - * Checks if the version supports Components or not - * Paper versions above 1.16.5 would be true - * Spigot always false - */ - public static final boolean IS_COMPONENT = IS_PAPER && CURRENT_VERSION >= V1_16_5; - - /** - * Checks if the version is lower than 1.13 due to the item changes - */ - public static final boolean IS_ITEM_LEGACY = CURRENT_VERSION < V1_13; - - /** - * Checks if the version supports {@link org.bukkit.persistence.PersistentDataContainer} - */ - public static final boolean IS_PDC_VERSION = CURRENT_VERSION >= V1_14; - - /** - * Checks if the version doesn't have {@link org.bukkit.inventory.meta.SkullMeta#setOwningPlayer(OfflinePlayer)} and - * {@link org.bukkit.inventory.meta.SkullMeta#setOwner(String)} should be used instead - */ - public static final boolean IS_SKULL_OWNER_LEGACY = CURRENT_VERSION <= V1_12; - - /** - * Checks if the version has {@link org.bukkit.inventory.meta.ItemMeta#setCustomModelData(Integer)} - */ - public static final boolean IS_CUSTOM_MODEL_DATA = CURRENT_VERSION >= V1_14; - - public static final boolean IS_CUSTOM_MODEL_DATA_COMPONENT = CURRENT_VERSION >= V1_21_4; - - public static final boolean IS_HEX_VERSION = CURRENT_VERSION >= V1_16; - - private static List CHEST_INVENTORY_TYPES = null; - - private static List VALID_INVENTORY_TYPES = null; - - private static List getChestInventoryTypes() { - if (CHEST_INVENTORY_TYPES != null) return CHEST_INVENTORY_TYPES; - - if (CURRENT_VERSION >= V1_14) { - CHEST_INVENTORY_TYPES = List.of( - InventoryType.BARREL, - InventoryType.CHEST, - InventoryType.CRAFTING, - InventoryType.CREATIVE, - InventoryType.ENDER_CHEST, - InventoryType.LECTERN, - InventoryType.MERCHANT, - InventoryType.SHULKER_BOX - ); - - return CHEST_INVENTORY_TYPES; - } - - CHEST_INVENTORY_TYPES = List.of( - InventoryType.CHEST, - InventoryType.CRAFTING, - InventoryType.CREATIVE, - InventoryType.ENDER_CHEST, - InventoryType.MERCHANT, - InventoryType.SHULKER_BOX - ); - - return CHEST_INVENTORY_TYPES; - } - - public static List getValidInventoryTypes() { - if (VALID_INVENTORY_TYPES != null) return VALID_INVENTORY_TYPES; - - final List chestInventoryTypes = getChestInventoryTypes(); - final List validInventoryTypes = new ArrayList<>(); - - for (final InventoryType inventoryType : InventoryType.values()) { - if (inventoryType != InventoryType.CHEST && chestInventoryTypes.contains(inventoryType)) continue; - validInventoryTypes.add(inventoryType); - } - - VALID_INVENTORY_TYPES = validInventoryTypes; - return VALID_INVENTORY_TYPES; - } - - /** - * Check if the server has access to the Paper API - * Taken from PaperLib - * - * @return True if on Paper server (or forks), false anything else - */ - private static boolean checkPaper() { - try { - Class.forName("com.destroystokyo.paper.PaperConfig"); - return true; - } catch (ClassNotFoundException ignored) { - return false; - } - } - - /** - * Gets the current server version - * - * @return A protocol like number representing the version, for example 1.16.5 - 1165 - */ - private static int getCurrentVersion() { - // No need to cache since will only run once - final Matcher matcher = Pattern.compile("(?\\d+\\.\\d+)(?\\.\\d+)?").matcher(Bukkit.getBukkitVersion()); - - final StringBuilder stringBuilder = new StringBuilder(); - if (matcher.find()) { - stringBuilder.append(matcher.group("version").replace(".", "")); - final String patch = matcher.group("patch"); - if (patch == null) stringBuilder.append("0"); - else stringBuilder.append(patch.replace(".", "")); - } - - //noinspection UnstableApiUsage - final Integer version = Ints.tryParse(stringBuilder.toString()); - - // Should never fail - if (version == null) throw new RuntimeException("Could not retrieve server version!"); - - return version; - } - - public static String getNmsVersion() { - final String version = Bukkit.getServer().getClass().getPackage().getName(); - return version.substring(version.lastIndexOf('.') + 1); - } - - /** - * Gets the NMS class from class name. - * - * @return The NMS class. - */ - public static Class getNMSClass(final String pkg, final String className) throws ClassNotFoundException { - if (VersionHelper.HAS_OBFUSCATED_NAMES) { - return Class.forName("net.minecraft." + pkg + "." + className); - } - return Class.forName("net.minecraft.server." + VersionHelper.NMS_VERSION + "." + className); - } - - /** - * Gets the craft class from class name. - * - * @return The craft class. - */ - public static Class getCraftClass(@NotNull final String name) throws ClassNotFoundException { - return Class.forName("org.bukkit.craftbukkit." + NMS_VERSION + "." + name); - } - -} +package com.extendedclip.deluxemenus.utils; + +import com.google.common.primitives.Ints; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * Class for detecting server version. + * + * @author Matt from triumph-gui + */ +public final class VersionHelper { + + private static final String PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); + public static final String NMS_VERSION = PACKAGE_NAME.substring(PACKAGE_NAME.lastIndexOf('.') + 1); + + // Custom Model Data Component + private static final int V1_21_4 = 1_21_4; + // Tooltip Style & Item Model + private static final int V1_21_2 = 1_21_2; + // Mojang obfuscation changes + private static final int V1_17 = 1170; + + public static final int CURRENT_VERSION = getCurrentVersion(); + + /** + * Checks if the current version includes the setTooltipStyle and setItemModel + */ + public static final boolean HAS_TOOLTIP_STYLE = CURRENT_VERSION >= V1_21_2; + + /** + * Checks if the current version was a version without versioned packages. + */ + public static final boolean HAS_OBFUSCATED_NAMES = CURRENT_VERSION >= V1_17; + + public static final boolean IS_CUSTOM_MODEL_DATA_COMPONENT = CURRENT_VERSION >= V1_21_4; + + private static List VALID_INVENTORY_TYPES = null; + + private static final List CHEST_INVENTORY_TYPES = List.of( + InventoryType.BARREL, + InventoryType.CHEST, + InventoryType.CRAFTING, + InventoryType.CREATIVE, + InventoryType.ENDER_CHEST, + InventoryType.LECTERN, + InventoryType.MERCHANT, + InventoryType.SHULKER_BOX + ); + + public static List getValidInventoryTypes() { + if (VALID_INVENTORY_TYPES != null) return VALID_INVENTORY_TYPES; + + final List validInventoryTypes = new ArrayList<>(); + + for (final InventoryType inventoryType : InventoryType.values()) { + if (inventoryType != InventoryType.CHEST && CHEST_INVENTORY_TYPES.contains(inventoryType)) continue; + validInventoryTypes.add(inventoryType); + } + + VALID_INVENTORY_TYPES = validInventoryTypes; + return VALID_INVENTORY_TYPES; + } + + /** + * Gets the current server version + * + * @return A protocol like number representing the version, for example 1.16.5 - 1165 + */ + private static int getCurrentVersion() { + // No need to cache since will only run once + final Matcher matcher = Pattern.compile("(?\\d+\\.\\d+)(?\\.\\d+)?").matcher(Bukkit.getBukkitVersion()); + + final StringBuilder stringBuilder = new StringBuilder(); + if (matcher.find()) { + stringBuilder.append(matcher.group("version").replace(".", "")); + final String patch = matcher.group("patch"); + if (patch == null) stringBuilder.append("0"); + else stringBuilder.append(patch.replace(".", "")); + } + + //noinspection UnstableApiUsage + final Integer version = Ints.tryParse(stringBuilder.toString()); + + // Should never fail + if (version == null) throw new RuntimeException("Could not retrieve server version!"); + + return version; + } + + /** + * Gets the NMS class from class name. + * + * @return The NMS class. + */ + public static Class getNMSClass(final String pkg, final String className) throws ClassNotFoundException { + if (VersionHelper.HAS_OBFUSCATED_NAMES) { + return Class.forName("net.minecraft." + pkg + "." + className); + } + return Class.forName("net.minecraft.server." + VersionHelper.NMS_VERSION + "." + className); + } + + /** + * Gets the craft class from class name. + * + * @return The craft class. + */ + public static Class getCraftClass(@NotNull final String name) throws ClassNotFoundException { + return Class.forName("org.bukkit.craftbukkit." + NMS_VERSION + "." + name); + } + +} From bcd65c18df9240970b658c1cee0aae4d6e2b8de0 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:23:47 +0300 Subject: [PATCH 08/16] Migrating to paper - phase 4 - remove NMS and other unsupported features --- .../extendedclip/deluxemenus/DeluxeMenus.java | 36 -- .../dupe/marker/impl/NMSMenuItemMarker.java | 30 -- .../impl/UnavailableMenuItemMarker.java | 22 - .../deluxemenus/hooks/BaseHeadHook.java | 5 +- .../deluxemenus/hooks/TextureHeadHook.java | 2 +- .../deluxemenus/menu/MenuItem.java | 67 --- .../deluxemenus/nbt/NbtProvider.java | 416 ------------------ .../deluxemenus/utils/SkullUtils.java | 350 ++++++++------- .../deluxemenus/utils/VersionHelper.java | 32 -- 9 files changed, 177 insertions(+), 783 deletions(-) delete mode 100644 src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/NMSMenuItemMarker.java delete mode 100644 src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/UnavailableMenuItemMarker.java delete mode 100644 src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index 2be85fc4..91727867 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -10,10 +10,8 @@ import com.extendedclip.deluxemenus.hooks.*; import com.extendedclip.deluxemenus.listener.PlayerListener; import com.extendedclip.deluxemenus.menu.Menu; -import com.extendedclip.deluxemenus.menu.MenuItem; import com.extendedclip.deluxemenus.menu.options.HeadType; import com.extendedclip.deluxemenus.menu.options.MenuOptions; -import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; import com.extendedclip.deluxemenus.placeholder.Expansion; import com.extendedclip.deluxemenus.updatechecker.UpdateChecker; @@ -57,20 +55,6 @@ public class DeluxeMenus extends JavaPlugin { private final GeneralConfig generalConfig = new GeneralConfig(this); private DeluxeMenusConfig menuConfig; - @Override - public void onLoad() { - if (NbtProvider.isAvailable()) { - this.debug(DebugLevel.HIGHEST, Level.INFO, "NMS hook has been setup successfully!"); - return; - } - - this.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Could not setup a NMS hook for your server version! The following Item options will not work: nbt_int, nbt_ints, nbt_string and nbt_strings." - ); - } - @Override public void onEnable() { this.generalConfig.load(); @@ -330,25 +314,5 @@ private void setUpMetrics() { .map(Menu::options) .map(MenuOptions::type) .collect(Collectors.groupingBy(Enum::name, Collectors.summingInt(type -> 1))))); - - // added for 1.21 usage - metrics.addCustomChart(new AdvancedPie("nbt_usage", () -> { - final var results = new HashMap(); - final var options = Menu.getAllMenus().stream() - .map(Menu::getMenuItems) - .flatMap(c -> c.values().stream().map(TreeMap::values).flatMap(Collection::stream)) - .map(MenuItem::options) - .collect(Collectors.toList()); - results.put("Byte", options.stream().filter(option -> option.nbtByte().isPresent()).mapToInt(b -> 1).sum()); - results.put("Bytes", options.stream().filter(option -> !option.nbtBytes().isEmpty()).mapToInt(b -> 1).sum()); - results.put("Short", options.stream().filter(option -> option.nbtShort().isPresent()).mapToInt(s -> 1).sum()); - results.put("Shorts", options.stream().filter(option -> !option.nbtShorts().isEmpty()).mapToInt(s -> 1).sum()); - results.put("Int", options.stream().filter(option -> option.nbtInt().isPresent()).mapToInt(i -> 1).sum()); - results.put("Ints", options.stream().filter(option -> !option.nbtInts().isEmpty()).mapToInt(i -> 1).sum()); - results.put("String", options.stream().filter(option -> option.nbtString().isPresent()).mapToInt(s -> 1).sum()); - results.put("Strings", options.stream().filter(option -> !option.nbtStrings().isEmpty()).mapToInt(s -> 1).sum()); - results.put("Model Data", options.stream().filter(option -> option.customModelData().isPresent()).mapToInt(c -> 1).sum()); - return results; - })); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/NMSMenuItemMarker.java b/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/NMSMenuItemMarker.java deleted file mode 100644 index 3dd11e26..00000000 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/NMSMenuItemMarker.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.extendedclip.deluxemenus.dupe.marker.impl; - -import com.extendedclip.deluxemenus.dupe.marker.ItemMarker; -import com.extendedclip.deluxemenus.nbt.NbtProvider; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -public class NMSMenuItemMarker implements ItemMarker { - - private final String mark; - - public NMSMenuItemMarker(@NotNull final String mark) { - this.mark = mark; - } - - @Override - public @NotNull ItemStack mark(@NotNull ItemStack itemStack) { - return NbtProvider.setBoolean(itemStack, mark, true); - } - - @Override - public @NotNull ItemStack unmark(@NotNull ItemStack itemStack) { - return NbtProvider.removeKey(itemStack, mark); - } - - @Override - public boolean isMarked(@NotNull ItemStack itemStack) { - return NbtProvider.hasKey(itemStack, mark); - } -} diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/UnavailableMenuItemMarker.java b/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/UnavailableMenuItemMarker.java deleted file mode 100644 index 81505278..00000000 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/marker/impl/UnavailableMenuItemMarker.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.extendedclip.deluxemenus.dupe.marker.impl; - -import com.extendedclip.deluxemenus.dupe.marker.ItemMarker; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -public class UnavailableMenuItemMarker implements ItemMarker { - @Override - public @NotNull ItemStack mark(@NotNull ItemStack itemStack) { - return itemStack; - } - - @Override - public @NotNull ItemStack unmark(@NotNull ItemStack itemStack) { - return itemStack; - } - - @Override - public boolean isMarked(@NotNull ItemStack itemStack) { - return false; - } -} diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 0178ec75..e7b663e8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -41,11 +41,10 @@ public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String.. if (arguments.length == 0) { return false; } - String itemTexture = SkullUtils.getTextureFromSkull(plugin, item); - String texture = SkullUtils.decodeSkinUrl(arguments[0]); + String itemTexture = SkullUtils.getTextureFromSkull(item); + String texture = SkullUtils.getTextureIdFromBase64(arguments[0]); if (itemTexture == null || texture == null) return false; - texture = texture.substring("https://textures.minecraft.net/texture/".length()-1); return texture.equals(itemTexture); } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index 127a8356..fe45399a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -38,7 +38,7 @@ public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String.. if (arguments.length == 0) { return false; } - return arguments[0].equals(SkullUtils.getTextureFromSkull(plugin, item)); + return arguments[0].equals(SkullUtils.getTextureFromSkull(item)); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 957f00a6..0107d3db 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -6,7 +6,6 @@ import com.extendedclip.deluxemenus.menu.options.LoreAppendMode; 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; @@ -476,72 +475,6 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemStack.setItemMeta(itemMeta); - if (NbtProvider.isAvailable()) { - if (this.options.nbtString().isPresent()) { - final String tag = holder.setPlaceholdersAndArguments(this.options.nbtString().get()); - if (tag.contains(":")) { - final String[] parts = tag.split(":", 2); - itemStack = NbtProvider.setString(itemStack, parts[0], parts[1]); - } - } - - if (this.options.nbtByte().isPresent()) { - final String tag = holder.setPlaceholdersAndArguments(this.options.nbtByte().get()); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setByte(itemStack, parts[0], Byte.parseByte(parts[1])); - } - } - - if (this.options.nbtShort().isPresent()) { - final String tag = holder.setPlaceholdersAndArguments(this.options.nbtShort().get()); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setShort(itemStack, parts[0], Short.parseShort(parts[1])); - } - } - - if (this.options.nbtInt().isPresent()) { - final String tag = holder.setPlaceholdersAndArguments(this.options.nbtInt().get()); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setInt(itemStack, parts[0], Integer.parseInt(parts[1])); - } - } - - for (String nbtTag : this.options.nbtStrings()) { - final String tag = holder.setPlaceholdersAndArguments(nbtTag); - if (tag.contains(":")) { - final String[] parts = tag.split(":", 2); - itemStack = NbtProvider.setString(itemStack, parts[0], parts[1]); - } - } - - for (String nbtTag : this.options.nbtBytes()) { - final String tag = holder.setPlaceholdersAndArguments(nbtTag); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setByte(itemStack, parts[0], Byte.parseByte(parts[1])); - } - } - - for (String nbtTag : this.options.nbtShorts()) { - final String tag = holder.setPlaceholdersAndArguments(nbtTag); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setShort(itemStack, parts[0], Short.parseShort(parts[1])); - } - } - - for (String nbtTag : this.options.nbtInts()) { - final String tag = holder.setPlaceholdersAndArguments(nbtTag); - if (tag.contains(":")) { - final String[] parts = tag.split(":"); - itemStack = NbtProvider.setInt(itemStack, parts[0], Integer.parseInt(parts[1])); - } - } - } - return itemStack; } diff --git a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java b/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java deleted file mode 100644 index 3e0c4262..00000000 --- a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java +++ /dev/null @@ -1,416 +0,0 @@ -package com.extendedclip.deluxemenus.nbt; - -import com.extendedclip.deluxemenus.utils.VersionHelper; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public final class NbtProvider { - - private static boolean NBT_HOOKED; - - private static Method getStringMethod; - private static Method setStringMethod; - private static Method setBooleanMethod; - private static Method setByteMethod; - private static Method setShortMethod; - private static Method setIntMethod; - private static Method removeTagMethod; - private static Method hasTagMethod; - private static Method getTagMethod; - private static Method setTagMethod; - private static Method containsMethod; - private static Method asNMSCopyMethod; - private static Method asBukkitCopyMethod; - - private static Constructor nbtCompoundConstructor; - - static { - try { - final Class compoundClass = VersionHelper.getNMSClass("nbt", "NBTTagCompound"); - final Class itemStackClass = VersionHelper.getNMSClass("world.item", "ItemStack"); - final Class inventoryClass = VersionHelper.getCraftClass("inventory.CraftItemStack"); - - containsMethod = compoundClass.getMethod(VersionConstants.CONTAINS_METHOD_NAME, String.class); - getStringMethod = compoundClass.getMethod(VersionConstants.GET_STRING_METHOD_NAME, String.class); - setStringMethod = compoundClass.getMethod(VersionConstants.SET_STRING_METHOD_NAME, String.class, String.class); - setBooleanMethod = compoundClass.getMethod(VersionConstants.SET_BOOLEAN_METHOD_NAME, String.class, boolean.class); - setByteMethod = compoundClass.getMethod(VersionConstants.SET_BYTE_METHOD_NAME, String.class, byte.class); - setShortMethod = compoundClass.getMethod(VersionConstants.SET_SHORT_METHOD_NAME, String.class, short.class); - setIntMethod = compoundClass.getMethod(VersionConstants.SET_INTEGER_METHOD_NAME, String.class, int.class); - removeTagMethod = compoundClass.getMethod(VersionConstants.REMOVE_TAG_METHOD_NAME, String.class); - hasTagMethod = itemStackClass.getMethod(VersionConstants.HAS_TAG_METHOD_NAME); - getTagMethod = itemStackClass.getMethod(VersionConstants.GET_TAG_METHOD_NAME); - setTagMethod = itemStackClass.getMethod(VersionConstants.SET_TAG_METHOD_NAME, compoundClass); - nbtCompoundConstructor = compoundClass.getDeclaredConstructor(); - - asNMSCopyMethod = inventoryClass.getMethod("asNMSCopy", ItemStack.class); - asBukkitCopyMethod = inventoryClass.getMethod("asBukkitCopy", itemStackClass); - - NBT_HOOKED = true; - } catch (NoSuchMethodException | ClassNotFoundException e) { - NBT_HOOKED = false; - } - } - - public static boolean isAvailable() { - return NBT_HOOKED; - } - - /** - * Sets an NBT tag to the an {@link ItemStack}. - * - * @param itemStack The current {@link ItemStack} to be set. - * @param key The NBT key to use. - * @param value The tag value to set. - * @return An {@link ItemStack} that has NBT set. - */ - public static ItemStack setString(final ItemStack itemStack, final String key, final String value) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return itemStack; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - setString(itemCompound, key, value); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - /** - * Sets a boolean to the {@link ItemStack}. - * Mainly used for setting an item to be unbreakable on older versions. - * - * @param itemStack The {@link ItemStack} to set the boolean to. - * @param key The key to use. - * @param value The boolean value. - * @return An {@link ItemStack} with a boolean value set. - */ - public static ItemStack setBoolean(final ItemStack itemStack, final String key, final boolean value) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return itemStack; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - setBoolean(itemCompound, key, value); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - /** - * Gets the NBT tag based on a given key. - * - * @param itemStack The {@link ItemStack} to get from. - * @param key The key to look for. - * @return The tag that was stored in the {@link ItemStack}. - */ - public static String getString(final ItemStack itemStack, final String key) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return null; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - return getString(itemCompound, key); - } - - public static ItemStack setByte(final ItemStack itemStack, final String key, final byte value) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return null; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - setByte(itemCompound, key, value); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - public static ItemStack setShort(final ItemStack itemStack, final String key, final short value) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return null; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - setShort(itemCompound, key, value); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - public static ItemStack setInt(final ItemStack itemStack, final String key, final int value) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return null; - - Object nmsItemStack = asNMSCopy(itemStack); - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - setInt(itemCompound, key, value); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - public static boolean hasKey(final ItemStack itemStack, final String key) { - if (itemStack == null) return false; - - final Object nmsItemStack = asNMSCopy(itemStack); - final Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - try { - return (boolean) containsMethod.invoke(itemCompound, key); - } catch (IllegalAccessException | InvocationTargetException e) { - return false; - } - } - - public static ItemStack removeKey(final ItemStack itemStack, final String key) { - if (itemStack == null) return null; - if (itemStack.getType() == Material.AIR) return null; - - Object nmsItemStack = asNMSCopy(itemStack); - if (!hasTag(nmsItemStack)) return itemStack; - Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); - - removeTag(itemCompound, key); - setTag(nmsItemStack, itemCompound); - - return asBukkitCopy(nmsItemStack); - } - - /** - * Mimics the itemCompound#setString method. - * - * @param itemCompound The ItemCompound. - * @param key The key to add. - * @param value The value to add. - */ - private static void setString(final Object itemCompound, final String key, final String value) { - try { - setStringMethod.invoke(itemCompound, key, value); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - private static void setBoolean(final Object itemCompound, final String key, final boolean value) { - try { - setBooleanMethod.invoke(itemCompound, key, value); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - private static void setByte(final Object itemCompound, final String key, final byte value) { - try { - setByteMethod.invoke(itemCompound, key, value); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - private static void setShort(final Object itemCompound, final String key, final short value) { - try { - setShortMethod.invoke(itemCompound, key, value); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - private static void setInt(final Object itemCompound, final String key, final int value) { - try { - setIntMethod.invoke(itemCompound, key, value); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - /** - * Mimics the itemCompound#getString method. - * - * @param itemCompound The ItemCompound. - * @param key The key to get from. - * @return A string with the value from the key. - */ - private static String getString(final Object itemCompound, final String key) { - try { - return (String) getStringMethod.invoke(itemCompound, key); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - /** - * Mimics the nmsItemStack#hasTag method. - * - * @param nmsItemStack the NMS ItemStack to check from. - * @return True or false depending if it has tag or not. - */ - private static boolean hasTag(final Object nmsItemStack) { - try { - return (boolean) hasTagMethod.invoke(nmsItemStack); - } catch (IllegalAccessException | InvocationTargetException e) { - return false; - } - } - - /** - * Mimics the nmsItemStack#getTag method. - * - * @param nmsItemStack The NMS ItemStack to get from. - * @return The tag compound. - */ - public static Object getTag(final Object nmsItemStack) { - try { - return getTagMethod.invoke(nmsItemStack); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - /** - * Mimics the nmsItemStack#setTag method. - * - * @param nmsItemStack the NMS ItemStack to set the tag to. - * @param itemCompound The item compound to set. - */ - private static void setTag(final Object nmsItemStack, final Object itemCompound) { - try { - setTagMethod.invoke(nmsItemStack, itemCompound); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - /** - * Mimics the nmsItemStack#removeTag method. - * - * @param nmsItemStack the NMS ItemStack to remove the tag from. - * @param itemCompound The item compound to remove. - */ - private static void removeTag(final Object nmsItemStack, final Object itemCompound) { - try { - removeTagMethod.invoke(nmsItemStack, itemCompound); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } - - /** - * Mimics the new NBTTagCompound instantiation. - * - * @return The new NBTTagCompound. - */ - private static Object newNBTTagCompound() { - try { - return nbtCompoundConstructor.newInstance(); - } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - return null; - } - } - - /** - * Mimics the CraftItemStack#asNMSCopy method. - * - * @param itemStack The ItemStack to make NMS copy. - * @return An NMS copy of the ItemStack. - */ - public static Object asNMSCopy(final ItemStack itemStack) { - try { - return asNMSCopyMethod.invoke(null, itemStack); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - /** - * Mimics the CraftItemStack#asBukkitCopy method. - * - * @param nmsItemStack The NMS ItemStack to turn into {@link ItemStack}. - * @return The new {@link ItemStack}. - */ - public static ItemStack asBukkitCopy(final Object nmsItemStack) { - try { - return (ItemStack) asBukkitCopyMethod.invoke(null, nmsItemStack); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - private static class VersionConstants { - - private final static String CONTAINS_METHOD_NAME = containsMethodName(); - private final static String GET_STRING_METHOD_NAME = getStringMethodName(); - private final static String SET_STRING_METHOD_NAME = setStringMethodName(); - private final static String SET_BOOLEAN_METHOD_NAME = setBooleanMethodName(); - private final static String SET_BYTE_METHOD_NAME = setByteMethodName(); - private final static String SET_SHORT_METHOD_NAME = setShortMethodName(); - private final static String SET_INTEGER_METHOD_NAME = setIntegerMethodName(); - private final static String REMOVE_TAG_METHOD_NAME = removeTagMethodName(); - private final static String HAS_TAG_METHOD_NAME = hasTagMethodName(); - private final static String GET_TAG_METHOD_NAME = getTagMethodName(); - private final static String SET_TAG_METHOD_NAME = setTagMethodName(); - - private static String getStringMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "l"; - return "getString"; - } - - private static String setStringMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; - return "setString"; - } - - private static String setBooleanMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; - return "setBoolean"; - } - - private static String setByteMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; - return "setByte"; - } - - private static String setShortMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; - return "setShort"; - } - - private static String setIntegerMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; - return "setInt"; - } - - private static String hasTagMethodName() { - if (VersionHelper.CURRENT_VERSION >= 1200) return "u"; // 1.20 variable change - if (VersionHelper.CURRENT_VERSION >= 1190) return "t"; // 1.19 variable change - if (VersionHelper.CURRENT_VERSION == 1182) return "s"; // 1.18.2 variable change - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "r"; // 1.18-1.18.1 - return "hasTag"; - } - - private static String getTagMethodName() { - if (VersionHelper.CURRENT_VERSION >= 1200) return "v"; // 1.20 variable change - if (VersionHelper.CURRENT_VERSION >= 1190) return "u"; // 1.19 variable change - if (VersionHelper.CURRENT_VERSION == 1182) return "t"; // 1.18.2 variable change - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "s"; // 1.18-1.18.1 - return "getTag"; - } - - private static String containsMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "e"; - return "hasKey"; - } - - private static String setTagMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "c"; - return "setTag"; - } - - private static String removeTagMethodName() { - if (VersionHelper.HAS_OBFUSCATED_NAMES) return "r"; - return "remove"; - } - - } -} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index dad186e1..059bb5f5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -1,176 +1,174 @@ -package com.extendedclip.deluxemenus.utils; - -import com.extendedclip.deluxemenus.DeluxeMenus; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; -import org.bukkit.profile.PlayerProfile; -import org.bukkit.profile.PlayerTextures; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Base64; -import java.util.UUID; - -public class SkullUtils { - - private static final Gson GSON = new Gson(); - - /** - * Helper method to get the encoded bytes for a full MC Texture - * - * @param url the url of the texture - * @return fully encoded texture url - */ - @NotNull - public static String getEncoded(@NotNull final String url) { - final byte[] encodedData = Base64.getEncoder().encode(String - .format("{textures:{SKIN:{url:\"%s\"}}}", "https://textures.minecraft.net/texture/" + url) - .getBytes()); - return new String(encodedData); - } - - /** - * Get the skull from a base64 encoded texture url - * - * @param base64Url base64 encoded url to use - * @return skull - */ - @NotNull - public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { - final ItemStack head = plugin.getHead().clone(); - if (base64Url.isEmpty()) { - return head; - } - - final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); - if (headMeta == null) { - return head; - } - - headMeta.setOwnerProfile(getPlayerProfile(plugin, base64Url)); - head.setItemMeta(headMeta); - return head; - } - - public static String getTextureFromSkull(final DeluxeMenus plugin, ItemStack item) { - if (!(item.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) item.getItemMeta(); - - PlayerProfile profile = meta.getOwnerProfile(); - if (profile == null) return null; - - URL url = profile.getTextures().getSkin(); - if (url == null) return null; - - return url.toString().substring("https://textures.minecraft.net/texture/".length() - 1); - } - - - /** - * Get the skull from a player name - * - * @param playerName the player name to use - * @return skull - */ - @NotNull - public static ItemStack getSkullByName(@NotNull final DeluxeMenus plugin, @NotNull final String playerName) { - final ItemStack head = plugin.getHead().clone(); - if (playerName.isEmpty()) { - return head; - } - - final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); - if (headMeta == null) { - return head; - } - - final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); - - if (offlinePlayer.getPlayerProfile().getTextures().isEmpty()) { - // updates the Player Profile and populates textures for offline players - for some reason this doesn't populate when getting the Profile first time - headMeta.setOwnerProfile(offlinePlayer.getPlayerProfile().update().join()); - } else { - headMeta.setOwningPlayer(offlinePlayer); - } - - head.setItemMeta(headMeta); - return head; - } - - public static String getSkullOwner(ItemStack skull) { - if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) skull.getItemMeta(); - - if (meta.getOwningPlayer() == null) return null; - return meta.getOwningPlayer().getName(); - } - - /** - * Create a player profile object - * - * @param base64Url the base64 encoded texture URL to use - * @return player profile - */ - @NotNull - private static PlayerProfile getPlayerProfile(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { - final PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID()); - - final String decodedBase64 = decodeSkinUrl(base64Url); - if (decodedBase64 == null) { - return profile; - } - - final PlayerTextures textures = profile.getTextures(); - - try { - textures.setSkin(new URL(decodedBase64)); - } catch (final MalformedURLException exception) { - plugin.printStacktrace("Something went horribly wrong trying to create basehead URL", exception); - } - - profile.setTextures(textures); - return profile; - } - - /** - * Decode a base64 string and extract the url of the skin. Example: - *
- * - Base64: {@code eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGNlYjE3MDhkNTQwNGVmMzI2MTAzZTdiNjA1NTljOTE3OGYzZGNlNzI5MDA3YWM5YTBiNDk4YmRlYmU0NjEwNyJ9fX0=} - *
- * - JSON: {@code {"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107"}}}} - *
- * - Result: {@code http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107} - *
- * Credit: iGabyTM - * - * @param base64Texture the texture - * @return the url of the texture if found, otherwise {@code null} - */ - @Nullable - public static String decodeSkinUrl(@NotNull final String base64Texture) { - final String decoded = new String(Base64.getDecoder().decode(base64Texture)); - final JsonObject object = GSON.fromJson(decoded, JsonObject.class); - - final JsonElement textures = object.get("textures"); - - if (textures == null) { - return null; - } - - final JsonElement skin = textures.getAsJsonObject().get("SKIN"); - - if (skin == null) { - return null; - } - - final JsonElement url = skin.getAsJsonObject().get("url"); - return url == null ? null : url.getAsString(); - } -} +package com.extendedclip.deluxemenus.utils; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Base64; +import java.util.UUID; + +public class SkullUtils { + + private static final String TEXTURES_PROPERTY = "textures"; + private static final String TEXTURE_URL_PREFIX = "https://textures.minecraft.net/texture/"; + + private static final Gson GSON = new Gson(); + + /** + * Helper method to get the encoded bytes for a full MC Texture + * + * @param url the url of the texture + * @return fully encoded texture url + */ + @NotNull + public static String getEncoded(@NotNull final String url) { + final byte[] encodedData = Base64.getEncoder().encode(String + .format("{%s:{SKIN:{url:\"%s\"}}}", TEXTURES_PROPERTY, TEXTURE_URL_PREFIX + url) + .getBytes()); + return new String(encodedData); + } + + /** + * Get the skull from a base64 encoded texture url + * + * @param base64Url base64 encoded url to use + * @return skull + */ + @NotNull + public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { + final ItemStack head = plugin.getHead().clone(); + if (base64Url.isEmpty()) { + return head; + } + + final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); + if (headMeta == null) { + return head; + } + + final PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); + profile.setProperty(new ProfileProperty(TEXTURES_PROPERTY, base64Url)); + headMeta.setPlayerProfile(profile); + + head.setItemMeta(headMeta); + return head; + } + + /** + * Get the texture id of a skull, i.e. the trailing path segment of its skin url. + * + * @return the texture id, or {@code null} if the item is not a skull or carries no texture + */ + public static @Nullable String getTextureFromSkull(@NotNull final ItemStack item) { + if (!(item.getItemMeta() instanceof SkullMeta meta)) return null; + + final PlayerProfile profile = meta.getPlayerProfile(); + if (profile == null) return null; + + for (final ProfileProperty property : profile.getProperties()) { + if (TEXTURES_PROPERTY.equals(property.getName())) { + return getTextureIdFromBase64(property.getValue()); + } + } + + return null; + } + + /** + * Get the skull from a player name + * + * @param playerName the player name to use + * @return skull + */ + @NotNull + public static ItemStack getSkullByName(@NotNull final DeluxeMenus plugin, @NotNull final String playerName) { + final ItemStack head = plugin.getHead().clone(); + if (playerName.isEmpty()) { + return head; + } + + final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); + if (headMeta == null) { + return head; + } + + final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); + final PlayerProfile profile = offlinePlayer.getPlayerProfile(); + + if (!profile.hasTextures()) { + // updates the Player Profile and populates textures for offline players - for some reason this doesn't populate when getting the Profile first time + headMeta.setPlayerProfile(profile.update().join()); + } else { + headMeta.setOwningPlayer(offlinePlayer); + } + + head.setItemMeta(headMeta); + return head; + } + + public static String getSkullOwner(ItemStack skull) { + if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + + if (meta.getOwningPlayer() == null) return null; + return meta.getOwningPlayer().getName(); + } + + /** + * Extract the texture id from a base64 encoded texture blob. + * + * @return the texture id, or {@code null} if the blob carries no skin url + */ + public static @Nullable String getTextureIdFromBase64(@NotNull final String base64Texture) { + final String url = decodeSkinUrl(base64Texture); + if (url == null) { + return null; + } + + return url.substring(url.lastIndexOf('/') + 1); + } + + /** + * Decode a base64 string and extract the url of the skin. Example: + *
+ * - Base64: {@code eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGNlYjE3MDhkNTQwNGVmMzI2MTAzZTdiNjA1NTljOTE3OGYzZGNlNzI5MDA3YWM5YTBiNDk4YmRlYmU0NjEwNyJ9fX0=} + *
+ * - JSON: {@code {"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107"}}}} + *
+ * - Result: {@code http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107} + *
+ * Credit: iGabyTM + * + * @param base64Texture the texture + * @return the url of the texture if found, otherwise {@code null} + */ + @Nullable + public static String decodeSkinUrl(@NotNull final String base64Texture) { + final String decoded = new String(Base64.getDecoder().decode(base64Texture)); + final JsonObject object = GSON.fromJson(decoded, JsonObject.class); + + final JsonElement textures = object.get("textures"); + + if (textures == null) { + return null; + } + + final JsonElement skin = textures.getAsJsonObject().get("SKIN"); + + if (skin == null) { + return null; + } + + final JsonElement url = skin.getAsJsonObject().get("url"); + return url == null ? null : url.getAsString(); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 3b181cbd..a941592a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -8,7 +8,6 @@ import java.util.regex.Pattern; import org.bukkit.Bukkit; import org.bukkit.event.inventory.InventoryType; -import org.jetbrains.annotations.NotNull; /** * Class for detecting server version. @@ -17,15 +16,10 @@ */ public final class VersionHelper { - private static final String PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); - public static final String NMS_VERSION = PACKAGE_NAME.substring(PACKAGE_NAME.lastIndexOf('.') + 1); - // Custom Model Data Component private static final int V1_21_4 = 1_21_4; // Tooltip Style & Item Model private static final int V1_21_2 = 1_21_2; - // Mojang obfuscation changes - private static final int V1_17 = 1170; public static final int CURRENT_VERSION = getCurrentVersion(); @@ -34,11 +28,6 @@ public final class VersionHelper { */ public static final boolean HAS_TOOLTIP_STYLE = CURRENT_VERSION >= V1_21_2; - /** - * Checks if the current version was a version without versioned packages. - */ - public static final boolean HAS_OBFUSCATED_NAMES = CURRENT_VERSION >= V1_17; - public static final boolean IS_CUSTOM_MODEL_DATA_COMPONENT = CURRENT_VERSION >= V1_21_4; private static List VALID_INVENTORY_TYPES = null; @@ -94,25 +83,4 @@ private static int getCurrentVersion() { return version; } - /** - * Gets the NMS class from class name. - * - * @return The NMS class. - */ - public static Class getNMSClass(final String pkg, final String className) throws ClassNotFoundException { - if (VersionHelper.HAS_OBFUSCATED_NAMES) { - return Class.forName("net.minecraft." + pkg + "." + className); - } - return Class.forName("net.minecraft.server." + VersionHelper.NMS_VERSION + "." + className); - } - - /** - * Gets the craft class from class name. - * - * @return The craft class. - */ - public static Class getCraftClass(@NotNull final String name) throws ClassNotFoundException { - return Class.forName("org.bukkit.craftbukkit." + NMS_VERSION + "." + name); - } - } From c970fbf78e088dd68617608b7636d543acf31571 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:44:22 +0300 Subject: [PATCH 09/16] Migrating to paper - phase 5 - replace bungee usage with adventure --- .../deluxemenus/action/ClickActionTask.java | 6 +- .../extendedclip/deluxemenus/menu/Menu.java | 5 +- .../deluxemenus/menu/MenuHolder.java | 4 +- .../deluxemenus/menu/MenuItem.java | 15 +- .../menu/command/RegistrableMenuCommand.java | 4 +- .../requirement/HasItemRequirement.java | 25 +- .../deluxemenus/utils/StringUtils.java | 230 +++++++++++------- 7 files changed, 173 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index b38a3837..de035132 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -6,8 +6,6 @@ import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; import com.extendedclip.deluxemenus.utils.*; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.md_5.bungee.api.ChatMessageType; -import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -124,7 +122,7 @@ public void run() { break; case ACTION_BAR: - player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(StringUtils.color(executable))); + player.sendActionBar(StringUtils.color(executable)); break; case LOG: @@ -156,7 +154,7 @@ public void run() { break; case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); + AdventureUtils.broadcast(StringUtils.color(executable)); break; case CLOSE: diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 615bedf7..4121e310 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,8 +1,6 @@ package com.extendedclip.deluxemenus.menu; import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.action.ClickHandler; -import com.extendedclip.deluxemenus.dupe.MenuItemMarker; import com.extendedclip.deluxemenus.events.DeluxeMenusOpenMenuEvent; import com.extendedclip.deluxemenus.events.DeluxeMenusPreOpenMenuEvent; import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; @@ -15,6 +13,7 @@ import java.util.Map.Entry; import java.util.logging.Level; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; @@ -338,7 +337,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map h.onClick(holder)); - String title = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.title())); + Component title = StringUtils.color(holder.setPlaceholdersAndArguments(this.options.title())); Inventory inventory; diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index b101a365..7799c0db 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -304,11 +304,11 @@ public void run() { ItemMeta meta = i.getItemMeta(); if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - meta.setDisplayName(StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get()))); + meta.displayName(StringUtils.colorNonItalic(setPlaceholdersAndArguments(item.options().displayName().get()))); } if (item.options().loreHasPlaceholders()) { - meta.setLore(item.getMenuItemLore(getHolder(), item.options().lore())); + meta.lore(item.getMenuItemLore(getHolder(), item.options().lore())); } i.setItemMeta(meta); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 0107d3db..8206c9ff 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -11,6 +11,7 @@ import com.extendedclip.deluxemenus.utils.StringUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; import com.google.common.collect.ImmutableMultimap; +import net.kyori.adventure.text.Component; import org.bukkit.Color; import org.bukkit.FireworkEffect; import org.bukkit.Material; @@ -270,13 +271,13 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { if (this.options.displayName().isPresent()) { final String displayName = holder.setPlaceholdersAndArguments(this.options.displayName().get()); - itemMeta.setDisplayName(StringUtils.color(displayName)); + itemMeta.displayName(StringUtils.colorNonItalic(displayName)); } - List lore = new ArrayList<>(); + 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<>()); + // ItemMeta.lore 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.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; @@ -297,7 +298,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { break; } - itemMeta.setLore(lore); + itemMeta.lore(lore); if (this.options.unbreakable()) { itemMeta.setUnbreakable(true); @@ -501,14 +502,14 @@ 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 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) + .map(StringUtils::colorNonItalic) .collect(Collectors.toList()); } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 678bbfef..2a41f6c0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -5,7 +5,6 @@ import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; import com.extendedclip.deluxemenus.utils.StringUtils; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; @@ -61,8 +60,7 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin if (typedArgs.length < menu.options().arguments().size()) { if (menu.options().argumentsUsageMessage().isPresent()) { String usageMessage = menu.options().argumentsUsageMessage().get(); - plugin.sms(sender, LegacyComponentSerializer.legacySection().deserialize( - StringUtils.color(StringUtils.replacePlaceholders(usageMessage, player)))); + plugin.sms(sender, StringUtils.color(StringUtils.replacePlaceholders(usageMessage, player))); } return true; } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index 3a4866a7..dc83fa06 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -6,12 +6,14 @@ import com.extendedclip.deluxemenus.requirement.wrappers.ItemWrapper; import com.extendedclip.deluxemenus.utils.StringUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; +import net.kyori.adventure.text.Component; import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.components.CustomModelDataComponent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Objects; @@ -117,8 +119,8 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia if (wrapper.getName() != null) { if (!metaToCheck.hasDisplayName()) return false; - String name = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getName())); - String nameToCheck = StringUtils.color(holder.setPlaceholdersAndArguments(metaToCheck.getDisplayName())); + String name = StringUtils.legacyColor(holder.setPlaceholdersAndArguments(wrapper.getName())); + String nameToCheck = StringUtils.legacyColor(holder.setPlaceholdersAndArguments(StringUtils.legacy(metaToCheck.displayName()))); if (wrapper.checkNameContains() && wrapper.checkNameIgnoreCase()) { if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(nameToCheck, name)) return false; @@ -132,11 +134,11 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia } if (wrapper.getLoreList() != null) { - List loreX = metaToCheck.getLore(); + List loreX = legacyLore(metaToCheck); if (loreX == null) return false; - String lore = wrapper.getLoreList().stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); - String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); + String lore = wrapper.getLoreList().stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::legacyColor).collect(Collectors.joining("&&")); + String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::legacyColor).collect(Collectors.joining("&&")); if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore)) return false; @@ -150,11 +152,11 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia } if (wrapper.getLore() != null) { - List loreX = metaToCheck.getLore(); + List loreX = legacyLore(metaToCheck); if (loreX == null) return false; - String lore = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getLore())); - String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); + String lore = StringUtils.legacyColor(holder.setPlaceholdersAndArguments(wrapper.getLore())); + String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::legacyColor).collect(Collectors.joining("&&")); if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { return org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore); @@ -168,6 +170,13 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia return true; } + private @Nullable List legacyLore(@NotNull final ItemMeta meta) { + final List lore = meta.lore(); + if (lore == null) return null; + + return lore.stream().map(StringUtils::legacy).collect(Collectors.toList()); + } + private boolean isEmptyModelData(@NotNull final CustomModelDataComponent modelData) { return modelData.getColors().isEmpty() && modelData.getFlags().isEmpty() && modelData.getFloats().isEmpty() && modelData.getStrings().isEmpty(); } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java index c095da20..29d0b2bd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -1,89 +1,141 @@ -package com.extendedclip.deluxemenus.utils; - -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.clip.placeholderapi.PlaceholderAPI; -import net.md_5.bungee.api.ChatColor; -import org.bukkit.Color; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StringUtils { - - private final static Pattern HEX_PATTERN = Pattern - .compile("&(#[a-f0-9]{6})", Pattern.CASE_INSENSITIVE); - - /** - * Translates the ampersand color codes like '&7' to their section symbol counterparts like '§7'. - *
- * It also translates hex colors like '&#aaFF00' to their section symbol counterparts like '§x§a§a§F§F§0§0'. - * - * @param input The string in which to translate the color codes. - * @return The string with the translated colors. - */ - @NotNull - public static String color(@NotNull String input) { - // Hex Support for 1.16.1+ - Matcher m = HEX_PATTERN.matcher(input); - while (m.find()) { - input = input.replace(m.group(), ChatColor.of(m.group(1)).toString()); - } - - return ChatColor.translateAlternateColorCodes('&', input); - } - - @NotNull - public static String replacePlaceholdersAndArguments(@NotNull String input, final @Nullable Map arguments, - final @Nullable Player player, - final boolean parsePlaceholdersInsideArguments, - final boolean parsePlaceholdersAfterArguments) { - if (player == null) { - return replaceArguments(input, arguments, null, parsePlaceholdersInsideArguments); - } - - if (parsePlaceholdersAfterArguments) { - return replacePlaceholders(replaceArguments(input, arguments, player, parsePlaceholdersInsideArguments), player); - } - - return replaceArguments(replacePlaceholders(input, player), arguments, player, parsePlaceholdersInsideArguments); - } - - @NotNull - public static String replacePlaceholders(final @NotNull String input, final @NotNull Player player) { - return PlaceholderAPI.setPlaceholders(player, input); - } - - @NotNull - public static String replaceArguments(@NotNull String input, final @Nullable Map arguments, - final @Nullable Player player, boolean parsePlaceholdersInsideArguments) { - if (arguments == null || arguments.isEmpty()) { - return input; - } - - for (final Map.Entry entry : arguments.entrySet()) { - final String value = player != null && parsePlaceholdersInsideArguments - ? replacePlaceholders(entry.getValue(), player) - : entry.getValue(); - input = input.replace("{" + entry.getKey() + "}", value); - } - - return input; - } - - @Nullable - public static Color parseRGBColor(@NotNull final String input) { - final String[] parts = input.split(","); - try { - return Color.fromRGB( - Integer.parseInt(parts[0].trim()), - Integer.parseInt(parts[1].trim()), - Integer.parseInt(parts[2].trim()) - ); - } catch (final Exception exception) { - return null; - } - } -} +package com.extendedclip.deluxemenus.utils; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.clip.placeholderapi.PlaceholderAPI; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Color; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StringUtils { + + private static final char SECTION_CHAR = '§'; + private static final String COLOR_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx"; + + private final static Pattern HEX_PATTERN = Pattern + .compile("&(#[a-f0-9]{6})", Pattern.CASE_INSENSITIVE); + + private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() + .character(SECTION_CHAR) + .hexCharacter('#') + .hexColors() + .useUnusualXRepeatedCharacterHexFormat() + .build(); + + /** + * Translates the ampersand color codes like '&7' to their section symbol counterparts like '§7'. + *
+ * It also translates hex colors like '&#aaFF00' to their section symbol counterparts like '§x§a§a§F§F§0§0'. + * + * @param input The string in which to translate the color codes. + * @return The string with the translated colors. + */ + @NotNull + public static String legacyColor(@NotNull String input) { + final Matcher matcher = HEX_PATTERN.matcher(input); + final StringBuilder builder = new StringBuilder(); + + while (matcher.find()) { + final StringBuilder replacement = new StringBuilder().append(SECTION_CHAR).append('x'); + for (final char character : matcher.group(1).substring(1).toCharArray()) { + replacement.append(SECTION_CHAR).append(character); + } + matcher.appendReplacement(builder, Matcher.quoteReplacement(replacement.toString())); + } + matcher.appendTail(builder); + + final char[] characters = builder.toString().toCharArray(); + for (int i = 0; i < characters.length - 1; i++) { + if (characters[i] != '&' || COLOR_CODES.indexOf(characters[i + 1]) == -1) continue; + characters[i] = SECTION_CHAR; + characters[i + 1] = Character.toLowerCase(characters[i + 1]); + } + + return new String(characters); + } + + /** + * Parses a configured string into a component. Section symbols already present in the input, + * such as those produced by PlaceholderAPI, are honoured alongside the '&' codes. + */ + @NotNull + public static Component color(@NotNull final String input) { + return SERIALIZER.deserialize(legacyColor(input)); + } + + /** + * As {@link #color(String)}, but with italics explicitly disabled. Item display names and lore + * render italic by default when set as components, which the legacy string setters suppressed. + */ + @NotNull + public static Component colorNonItalic(@NotNull final String input) { + return color(input).decoration(TextDecoration.ITALIC, false); + } + + /** + * Serializes a component back into a legacy section symbol string, for comparison against + * configured values. + */ + @NotNull + public static String legacy(@Nullable final Component component) { + return component == null ? "" : SERIALIZER.serialize(component); + } + + @NotNull + public static String replacePlaceholdersAndArguments(@NotNull String input, final @Nullable Map arguments, + final @Nullable Player player, + final boolean parsePlaceholdersInsideArguments, + final boolean parsePlaceholdersAfterArguments) { + if (player == null) { + return replaceArguments(input, arguments, null, parsePlaceholdersInsideArguments); + } + + if (parsePlaceholdersAfterArguments) { + return replacePlaceholders(replaceArguments(input, arguments, player, parsePlaceholdersInsideArguments), player); + } + + return replaceArguments(replacePlaceholders(input, player), arguments, player, parsePlaceholdersInsideArguments); + } + + @NotNull + public static String replacePlaceholders(final @NotNull String input, final @NotNull Player player) { + return PlaceholderAPI.setPlaceholders(player, input); + } + + @NotNull + public static String replaceArguments(@NotNull String input, final @Nullable Map arguments, + final @Nullable Player player, boolean parsePlaceholdersInsideArguments) { + if (arguments == null || arguments.isEmpty()) { + return input; + } + + for (final Map.Entry entry : arguments.entrySet()) { + final String value = player != null && parsePlaceholdersInsideArguments + ? replacePlaceholders(entry.getValue(), player) + : entry.getValue(); + input = input.replace("{" + entry.getKey() + "}", value); + } + + return input; + } + + @Nullable + public static Color parseRGBColor(@NotNull final String input) { + final String[] parts = input.split(","); + try { + return Color.fromRGB( + Integer.parseInt(parts[0].trim()), + Integer.parseInt(parts[1].trim()), + Integer.parseInt(parts[2].trim()) + ); + } catch (final Exception exception) { + return null; + } + } +} From 04a93477fdd73f2b6c886a275ee1d7e544a19b99 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:53:30 +0300 Subject: [PATCH 10/16] Make [PLAYER] action execute perormCommand (to separate from [commandevent]) --- .../deluxemenus/action/ClickActionTask.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index de035132..0518fb09 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -93,6 +93,8 @@ public void run() { break; case PLAYER: + player.performCommand(executable); + break; case PLAYER_COMMAND_EVENT: player.chat("/" + executable); break; @@ -436,30 +438,14 @@ public void run() { if (!executable.contains(" ")) { if (!isRaw) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception - ); - break; - } + sound = SoundUtils.getSound(executable.toUpperCase()); } } else { String[] parts = executable.split(" ", 3); soundName = parts[0]; if (!isRaw) { - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); - break; - } + sound = SoundUtils.getSound(parts[0].toUpperCase()); } if (parts.length == 3) { From ecfe77d635550713625692990d93a5f34dc0c5ba Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:26:06 +0300 Subject: [PATCH 11/16] Migrating to paper - phase 6 - Use brigadier for DM commands, and swap reflection to Bukkit#getCommandMap for menu commands --- .../extendedclip/deluxemenus/DeluxeMenus.java | 19 ++++++- .../command/DeluxeMenusCommand.java | 40 ++++---------- .../menu/command/RegistrableMenuCommand.java | 55 ++----------------- 3 files changed, 30 insertions(+), 84 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index 91727867..d0079c8c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -19,6 +19,7 @@ import com.extendedclip.deluxemenus.utils.Messages; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.Component; import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; @@ -82,9 +83,7 @@ public void onEnable() { } new PlayerListener(this).register(); - if (!new DeluxeMenusCommand(this).register()) { - debug(DebugLevel.HIGHEST, Level.SEVERE, "Could not register the DeluxeMenus command!"); - } + registerMainCommand(); new Expansion(this).register(); setUpBungeeCordMessaging(); @@ -286,6 +285,20 @@ private void setUpItemHooks() { } } + private void registerMainCommand() { + final DeluxeMenusCommand command = new DeluxeMenusCommand(this); + + // paper-plugin.yml has no `commands:` block, so the command is registered through the + // lifecycle registrar. + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> + event.registrar().register( + "deluxemenus", + "DeluxeMenus main commands", + List.of("dm", "deluxemenu", "dmenu"), + command + )); + } + private void setUpBungeeCordMessaging() { Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); } diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index 2c757175..955c15cb 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -2,18 +2,17 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.command.subcommand.*; -import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; +import io.papermc.paper.command.brigadier.BasicCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.TabExecutor; import org.jetbrains.annotations.NotNull; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,7 +21,7 @@ import static net.kyori.adventure.text.Component.text; -public class DeluxeMenusCommand implements TabExecutor { +public class DeluxeMenusCommand implements BasicCommand { private static final TextReplacementConfig.Builder VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); private static final TextReplacementConfig.Builder AUTHORS_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); @@ -32,51 +31,32 @@ public class DeluxeMenusCommand implements TabExecutor { public DeluxeMenusCommand(final @NotNull DeluxeMenus plugin) { this.plugin = plugin; - } - - public boolean register() { - final PluginCommand command = this.plugin.getCommand("deluxemenus"); - if (command == null) { - return false; - } - - command.setExecutor(this); registerSubCommands(); - return true; } @Override - public boolean onCommand( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, - final @NotNull String[] args - ) { + public void execute(final @NotNull CommandSourceStack source, final @NotNull String[] args) { + final CommandSender sender = source.getSender(); final List arguments = Arrays.asList(args); if (arguments.isEmpty()) { plugin.sms(sender, Messages.PLUGIN_VERSION.message().replaceText(VERSION_REPLACER_BUILDER.replacement(plugin.getPluginMeta().getVersion()).build()).replaceText(AUTHORS_REPLACER_BUILDER.replacement(plugin.getPluginMeta().getAuthors().stream().map(author -> text(author, NamedTextColor.WHITE)).collect(Component.toComponent(text(", ", NamedTextColor.GRAY)))).build())); - return true; + return; } final SubCommand subCommand = subCommands.get(arguments.get(0).toLowerCase()); if (subCommand != null) { subCommand.execute(sender, arguments.subList(1, arguments.size())); - return true; + return; } plugin.sms(sender, Messages.WRONG_USAGE); - return true; } @Override - public List onTabComplete( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, - final @NotNull String[] args - ) { + public @NotNull Collection suggest(final @NotNull CommandSourceStack source, final @NotNull String[] args) { + final CommandSender sender = source.getSender(); final List arguments = Arrays.asList(args); return subCommands.values() diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 2a41f6c0..181fd69b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -9,11 +9,9 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.Locale; @@ -23,7 +21,6 @@ public class RegistrableMenuCommand extends Command { private static final String FALLBACK_PREFIX = "DeluxeMenus".toLowerCase(Locale.ROOT).trim(); - private static CommandMap commandMap = null; private final DeluxeMenus plugin; @@ -88,41 +85,10 @@ public void register() { if (registered) { throw new IllegalStateException("This command was already registered!"); } - if (registered) { - throw new IllegalStateException("This command was already registered!"); - } registered = true; - registered = true; - - if (commandMap == null) { - try { - final Field f = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - f.setAccessible(true); - commandMap = (CommandMap) f.get(Bukkit.getServer()); - } catch (final @NotNull Exception exception) { - plugin.printStacktrace( - "Something went wrong while trying to register command: " + this.getName(), - exception - ); - return; - } - } - if (commandMap == null) { - try { - final Field f = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - f.setAccessible(true); - commandMap = (CommandMap) f.get(Bukkit.getServer()); - } catch (final @NotNull Exception exception) { - plugin.printStacktrace( - "Something went wrong while trying to register command: " + this.getName(), - exception - ); - return; - } - } - boolean registered = commandMap.register(FALLBACK_PREFIX, this); + boolean registered = Bukkit.getCommandMap().register(FALLBACK_PREFIX, this); if (registered) { plugin.debug( DebugLevel.LOW, @@ -150,21 +116,9 @@ public void unregister() { unregistered = true; - if (commandMap == null) { - this.menu = null; - return; - } - - Field cMap; - Field knownCommands; try { - cMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - cMap.setAccessible(true); - knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommands.setAccessible(true); - - //noinspection unchecked - final Map knownCommandsMap = (Map) knownCommands.get(cMap.get(Bukkit.getServer())); + final CommandMap commandMap = Bukkit.getCommandMap(); + final Map knownCommandsMap = commandMap.getKnownCommands(); // We need to remove every single alias because CommandMap#register() adds them all to the map. // If we do not remove them, then we will have dangling references to the command. @@ -176,8 +130,7 @@ public void unregister() { knownCommandsMap.remove(FALLBACK_PREFIX + ":" + alias); } - boolean unregistered = this.unregister((CommandMap) cMap.get(Bukkit.getServer())); - this.unregister(commandMap); + boolean unregistered = this.unregister(commandMap); if (unregistered) { plugin.debug( DebugLevel.HIGH, From 251f09825e37702696402868406ef7a4fb55360d Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:13:40 +0300 Subject: [PATCH 12/16] Fix registry lookup on older versions --- .../deluxemenus/config/DeluxeMenusConfig.java | 13 +++- .../deluxemenus/utils/RegistryUtils.java | 78 +++++++++++++++++++ .../deluxemenus/utils/SoundUtils.java | 45 +---------- 3 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/extendedclip/deluxemenus/utils/RegistryUtils.java diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 89ddbc5e..081cdc88 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -33,12 +33,14 @@ import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.ItemUtils; import com.extendedclip.deluxemenus.utils.LocationUtils; +import com.extendedclip.deluxemenus.utils.RegistryUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; import com.google.common.base.Enums; import com.google.common.primitives.Ints; import org.bukkit.DyeColor; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Registry; import org.bukkit.block.banner.PatternType; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; @@ -704,11 +706,9 @@ private Map> loadMenuItems(FileConfiguration } final DyeColor color; - final PatternType type; try { color = DyeColor.valueOf(metaParts[0].toUpperCase()); - type = PatternType.valueOf(metaParts[1].toUpperCase()); } catch (IllegalArgumentException exception) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); @@ -716,6 +716,15 @@ private Map> loadMenuItems(FileConfiguration continue; } + // Resolved through the registry, never PatternType.valueOf: PatternType is + // an enum on 1.20.6 and an interface on 26.2. + final PatternType type = RegistryUtils.byNameOrKey(Registry.BANNER_PATTERN, metaParts[1]); + + if (type == null) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); + continue; + } + bannerMeta.add(new org.bukkit.block.banner.Pattern(color, type)); } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/RegistryUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/RegistryUtils.java new file mode 100644 index 00000000..3c61f306 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/utils/RegistryUtils.java @@ -0,0 +1,78 @@ +package com.extendedclip.deluxemenus.utils; + +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Resolves registry values from the legacy enum constant names menus are configured with. + *

+ * Several Bukkit enums have been converted into interfaces over time ({@code Sound} in + * 1.21.3, {@code PatternType} in 1.20.5). Calling a static method such as {@code valueOf} + * on one of those types compiles into an {@code InterfaceMethodref} against the newest API + * and throws {@link IncompatibleClassChangeError} on an older server where the same type is + * still an enum — it does not fail at compile time, and it does not fail on the version the + * plugin was compiled against. Going through the registry avoids the problem entirely and + * works identically on every supported version. + */ +public final class RegistryUtils { + + private static final Map, Map> INDEXES = new ConcurrentHashMap<>(); + + private RegistryUtils() { + throw new AssertionError("Util classes should not be initialized"); + } + + /** + * Resolves a registry value from either a namespaced key ({@code entity.player.levelup}, + * {@code minecraft:stripe_bottom}) or a legacy enum constant name + * ({@code ENTITY_PLAYER_LEVELUP}, {@code STRIPE_BOTTOM}). + * + * @return the value, or {@code null} if nothing matches + */ + @SuppressWarnings("unchecked") + public static @Nullable T byNameOrKey( + final @NotNull Registry registry, + final @NotNull String input + ) { + final NamespacedKey key = NamespacedKey.fromString(input.toLowerCase(Locale.ROOT)); + if (key != null) { + final T direct = registry.get(key); + if (direct != null) { + return direct; + } + } + + return ((Map) INDEXES.computeIfAbsent(registry, RegistryUtils::index)) + .get(toLegacyName(input)); + } + + /** + * Builds the legacy-name index from the registry itself rather than by transforming the + * name, because the reverse transformation is lossy: {@code BLOCK_NOTE_BLOCK_HARP} is + * {@code block.note_block.harp}, not {@code block.note.block.harp}. + */ + private static Map index(final Registry registry) { + final Map index = new HashMap<>(); + + for (final T value : registry) { + final NamespacedKey key = registry.getKey(value); + if (key == null) continue; + + index.put(toLegacyName(key.value()), value); + } + + return index; + } + + private static String toLegacyName(final @NotNull String value) { + return value.toUpperCase(Locale.ROOT).replace('.', '_'); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java index af10933b..9ed4de2d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java @@ -1,61 +1,24 @@ package com.extendedclip.deluxemenus.utils; -import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Sound; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - public class SoundUtils { /** - * Maps the legacy {@code Sound} enum constant names menus are configured with onto the - * registry. The mapping is derived from the registry rather than by replacing {@code _} - * with {@code .}, because that naive conversion is wrong for keys whose segments contain - * underscores: {@code BLOCK_NOTE_BLOCK_HARP} is {@code block.note_block.harp}. + * Resolves a sound from either a namespaced key ({@code entity.player.levelup}, + * {@code minecraft:entity.player.levelup}) or a legacy enum constant name + * ({@code ENTITY_PLAYER_LEVELUP}). *

* Uses {@code Registry.SOUNDS} rather than {@code Registry.SOUND_EVENT}: the latter does * not exist on 1.20.6, the minimum supported version. On current versions they are the * same registry instance. - */ - private static final class Lookup { - static final Map BY_LEGACY_NAME = build(); - - private static Map build() { - final Map map = new HashMap<>(); - for (final Sound sound : Registry.SOUNDS) { - final NamespacedKey key = Registry.SOUNDS.getKey(sound); - if (key == null) continue; - map.put(toLegacyName(key.value()), sound); - } - return map; - } - } - - private static String toLegacyName(@NotNull final String keyValue) { - return keyValue.toUpperCase(Locale.ROOT).replace('.', '_'); - } - - /** - * Resolves a sound from either a namespaced key ({@code entity.player.levelup}, - * {@code minecraft:entity.player.levelup}) or a legacy enum constant name - * ({@code ENTITY_PLAYER_LEVELUP}). * * @return the sound, or {@code null} if no sound matches */ public static @Nullable Sound getSound(@NotNull final String name) { - final NamespacedKey key = NamespacedKey.fromString(name.toLowerCase(Locale.ROOT)); - if (key != null) { - final Sound sound = Registry.SOUNDS.get(key); - if (sound != null) { - return sound; - } - } - - return Lookup.BY_LEGACY_NAME.get(toLegacyName(name)); + return RegistryUtils.byNameOrKey(Registry.SOUNDS, name); } } From 42100118f886282de0308108256beeb282e9d201 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:34:38 +0300 Subject: [PATCH 13/16] Fix component build on 1.20.6 --- .../deluxemenus/command/subcommand/ListCommand.java | 12 ++++++------ .../deluxemenus/command/subcommand/MetaCommand.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java index 92a1a89f..301a2f00 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java @@ -138,7 +138,7 @@ private void sendSimpleMenuList(@NotNull final CommandSender sender, @NotNull fi : text(menu.options().name(), NamedTextColor.DARK_AQUA).append(text(" - ", NamedTextColor.GRAY)).append(text(menuCommand, NamedTextColor.GREEN)); }).collect(Component.toComponent(text(" | ", NamedTextColor.WHITE))); - plugin.sms(sender, list.append(menusList).build()); + plugin.sms(sender, list.append(menusList).asComponent()); return; } @@ -153,7 +153,7 @@ private void sendSimpleMenuList(@NotNull final CommandSender sender, @NotNull fi }).collect(Component.toComponent(text(", ", NamedTextColor.WHITE))); list.append(menusList); - plugin.sms(sender, list.build()); + plugin.sms(sender, list.asComponent()); } private void sendPaginatedMenuList(@NotNull final CommandSender sender, @NotNull final Map> menus, @@ -187,7 +187,7 @@ private void sendPaginatedMenuList(@NotNull final CommandSender sender, @NotNull final var menuList = createMenuListForConsole(pageMenusTree, 0); list.append(newline()).append(menuList).append(newline()).append(text("Use /dm list to view more menus", NamedTextColor.GRAY)); - plugin.sms(sender, list.build()); + plugin.sms(sender, list.asComponent()); return; } @@ -225,7 +225,7 @@ private void sendPaginatedMenuList(@NotNull final CommandSender sender, @NotNull } } - plugin.sms(sender, list.build()); + plugin.sms(sender, list.asComponent()); } private Map> getPaginatedMenus(final Map> menus, @@ -293,7 +293,7 @@ private Component createMenuListForConsole(final Map tree, int t } } - return list.build(); + return list.asComponent(); } @SuppressWarnings("unchecked") @@ -318,7 +318,7 @@ private Component createMenuListForPlayer(final Map tree, int ta } } - return list.build(); + return list.asComponent(); } private Map convertMenusToTree(final Map> menus) { diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java index 33614040..b17a3bfc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java @@ -306,7 +306,7 @@ private void handleListMeta(@NotNull final CommandSender sender, @NotNull final .append(pairsList) .append(newline()) .append(text("Use /dm meta list " + typeName + " to view more values of this type", NamedTextColor.GRAY)); - plugin.sms(sender, list.build()); + plugin.sms(sender, list.asComponent()); } private void handleShowMeta(@NotNull final CommandSender sender, @NotNull final Player target, From 21d501894980eff338f996dee1ea342b72dc5d99 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:50:25 +0300 Subject: [PATCH 14/16] Fix italic always set to false --- .../extendedclip/deluxemenus/utils/StringUtils.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java index 29d0b2bd..82dff93a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -70,12 +70,17 @@ public static Component color(@NotNull final String input) { } /** - * As {@link #color(String)}, but with italics explicitly disabled. Item display names and lore - * render italic by default when set as components, which the legacy string setters suppressed. + * As {@link #color(String)}, but with italics disabled by default. Item display names + * and lore render italic when set as components, which the legacy string setters suppressed by + * building on a {@code Style.EMPTY.withItalic(false)} base. + *

+ * This must be a fallback, not an override. The legacy serializer puts a single-format + * line's style on the root component, so {@code decoration(ITALIC, false)} would overwrite an + * explicit {@code &o} and make italic text impossible to configure. */ @NotNull public static Component colorNonItalic(@NotNull final String input) { - return color(input).decoration(TextDecoration.ITALIC, false); + return color(input).applyFallbackStyle(TextDecoration.ITALIC.withState(false)); } /** From 0039131c79a10a0eb1db95dbd4884d4f24bda6ef Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:29:57 +0300 Subject: [PATCH 15/16] Add new config option to suppress default italics --- .../deluxemenus/config/GeneralConfig.java | 7 ++++++ .../deluxemenus/menu/MenuHolder.java | 2 +- .../deluxemenus/menu/MenuItem.java | 6 +++-- .../deluxemenus/utils/StringUtils.java | 22 +++++++++++-------- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java index 00619a37..8bfe8cbf 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java @@ -13,6 +13,7 @@ public class GeneralConfig { private int menusListPageSize = 10; private int metasListPageSize = 15; private int maxEphemeralCooldownSeconds = -1; + private boolean suppressDefaultItalics = true; public GeneralConfig(final @NotNull DeluxeMenus plugin) { this.plugin = plugin; @@ -25,6 +26,7 @@ public void load() { plugin.getConfig().addDefault("menus_list_page_size", menusListPageSize); plugin.getConfig().addDefault("metas_list_page_size", metasListPageSize); plugin.getConfig().addDefault("max_ephemeral_cooldown_seconds", maxEphemeralCooldownSeconds); + plugin.getConfig().addDefault("suppress_default_italics", suppressDefaultItalics); checkForUpdates = plugin.getConfig().getBoolean("check_updates", false); debugLevel = loadDebugLevel(); @@ -32,6 +34,7 @@ public void load() { menusListPageSize = plugin.getConfig().getInt("menus_list_page_size", 10); metasListPageSize = plugin.getConfig().getInt("metas_list_page_size", 15); maxEphemeralCooldownSeconds = plugin.getConfig().getInt("max_ephemeral_cooldown_seconds", maxEphemeralCooldownSeconds); + suppressDefaultItalics = plugin.getConfig().getBoolean("suppress_default_italics", true); } public void reload() { @@ -67,6 +70,10 @@ public int maxEphemeralCooldownSeconds() { return maxEphemeralCooldownSeconds; } + public boolean suppressDefaultItalics() { + return suppressDefaultItalics; + } + private @NotNull DebugLevel loadDebugLevel() { String configDebugLevel = plugin.getConfig().getString("debug", "HIGHEST"); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 7799c0db..a84f3fb5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -304,7 +304,7 @@ public void run() { ItemMeta meta = i.getItemMeta(); if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - meta.displayName(StringUtils.colorNonItalic(setPlaceholdersAndArguments(item.options().displayName().get()))); + meta.displayName(StringUtils.colorItemText(setPlaceholdersAndArguments(item.options().displayName().get()), plugin.getGeneralConfig().suppressDefaultItalics())); } 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 8206c9ff..f999fc36 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -271,7 +271,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { if (this.options.displayName().isPresent()) { final String displayName = holder.setPlaceholdersAndArguments(this.options.displayName().get()); - itemMeta.displayName(StringUtils.colorNonItalic(displayName)); + itemMeta.displayName(StringUtils.colorItemText(displayName, plugin.getGeneralConfig().suppressDefaultItalics())); } List lore = new ArrayList<>(); @@ -503,13 +503,15 @@ private boolean isHeadItem(@NotNull final String material) { } protected List getMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List lore) { + final boolean suppressDefaultItalics = plugin.getGeneralConfig().suppressDefaultItalics(); + return lore.stream() .map(holder::setPlaceholdersAndArguments) .map(line -> line.split("\n")) .flatMap(Arrays::stream) .map(line -> line.split("\\\\n")) .flatMap(Arrays::stream) - .map(StringUtils::colorNonItalic) + .map(line -> StringUtils.colorItemText(line, suppressDefaultItalics)) .collect(Collectors.toList()); } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java index 82dff93a..9e8bb5a1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -70,17 +70,21 @@ public static Component color(@NotNull final String input) { } /** - * As {@link #color(String)}, but with italics disabled by default. Item display names - * and lore render italic when set as components, which the legacy string setters suppressed by - * building on a {@code Style.EMPTY.withItalic(false)} base. - *

- * This must be a fallback, not an override. The legacy serializer puts a single-format - * line's style on the root component, so {@code decoration(ITALIC, false)} would overwrite an - * explicit {@code &o} and make italic text impossible to configure. + * Parses item display name / lore text. + * + * @param suppressDefaultItalics when {@code true}, italics are turned off for any part of the + * text that does not set them, so item text is non-italic unless + * the author writes {@code &o}. When {@code false} italics are + * left untouched and the server's default applies. On 1.20.6 + * that renders unformatted text italic. See + * {@code GeneralConfig#suppressDefaultItalics}. */ @NotNull - public static Component colorNonItalic(@NotNull final String input) { - return color(input).applyFallbackStyle(TextDecoration.ITALIC.withState(false)); + public static Component colorItemText(@NotNull final String input, final boolean suppressDefaultItalics) { + final Component component = color(input); + return suppressDefaultItalics + ? component.applyFallbackStyle(TextDecoration.ITALIC.withState(false)) + : component; } /** From 6be2990d250f1d83bff90337c8b50dac1c651503 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:58:09 +0300 Subject: [PATCH 16/16] Remove remaining TODOs --- .../deluxemenus/persistentmeta/PersistentMetaHandler.java | 1 - src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java index 11182d55..bab7d857 100644 --- a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java +++ b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java @@ -145,7 +145,6 @@ public Map getMetaValues( return OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; } - // TODO: Blitz: It seems that PersistentDataContainer#has(NamespacedKey) does not exist in 1.17.1. if (player.getPersistentDataContainer().has(key) && (!player.getPersistentDataContainer().has(key, type.getPDType()) || !type.isSupported(player.getPersistentDataContainer().get(key, type.getPDType())))) { return OperationResult.EXISTENT_VALUE_IS_DIFFERENT_TYPE; diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java index 7345d2c5..870e8312 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java @@ -105,7 +105,7 @@ public static boolean hasPotionMeta(@NotNull final ItemStack itemStack) { final PotionMeta itemMeta = (PotionMeta) itemStack.getItemMeta(); if (itemMeta != null) { - itemMeta.setBasePotionType(PotionType.WATER); // TODO: Blitz: Check if this works in 1.17.1 (seems that the setBasePotionType method was only added later) + itemMeta.setBasePotionType(PotionType.WATER); itemStack.setItemMeta(itemMeta); }