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 d5047f13..53a68324 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,21 +14,20 @@ 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.momirealms.net/releases/") 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) compileOnly(libs.headdb) compileOnly(libs.headdb.api) @@ -45,29 +44,32 @@ 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") } +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") - relocate("net.kyori", "com.extendedclip.deluxemenus.libs.adventure") relocate("org.bstats", "com.extendedclip.deluxemenus.libs.bstats") archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } - java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - disableAutoTargetJvm() - } processResources { - filesMatching("plugin.yml") { + filesMatching("paper-plugin.yml") { expand("version" to rootProject.version) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd7fc593..a2a6b17a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,7 @@ [versions] # Compile only -spigot = "26.2-R0.1-SNAPSHOT" +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" @@ -18,14 +17,11 @@ bstats = "3.2.1" # Implementation nashorn = "15.7" -adventure-platform = "4.4.1" -adventure-minimessage = "5.2.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" } headdb-api = { module = "com.github.SilentDevelopment.HeadDB:headdb-api", version.ref = "headdb-api" } craftengine-core = { module = "net.momirealms:craft-engine-core", version.ref = "craftengine"} @@ -41,6 +37,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..d0079c8c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -10,19 +10,16 @@ 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; 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.platform.bukkit.BukkitAudiences; +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; @@ -51,8 +48,6 @@ public class DeluxeMenus extends JavaPlugin { private MenuItemMarker menuItemMarker; private EphemeralCooldownManager ephemeralCooldownManager; - private BukkitAudiences audiences; - private VaultHook vaultHook; private ItemStack head; @@ -61,20 +56,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(); @@ -91,8 +72,6 @@ public void onEnable() { this.ephemeralCooldownManager = new EphemeralCooldownManager(this); this.ephemeralCooldownManager.startSweepTask(); - this.audiences = BukkitAudiences.create(this); - hookIntoVault(); setUpItemHooks(); @@ -104,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(); @@ -120,11 +97,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 +146,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 +183,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); } @@ -256,13 +221,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<>(); @@ -325,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"); } @@ -353,25 +327,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/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index e9cb9d3b..0518fb09 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; @@ -69,7 +67,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; } @@ -95,6 +93,8 @@ public void run() { break; case PLAYER: + player.performCommand(executable); + break; case PLAYER_COMMAND_EVENT: player.chat("/" + executable); break; @@ -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: @@ -124,7 +124,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 +156,7 @@ public void run() { break; case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); + AdventureUtils.broadcast(StringUtils.color(executable)); break; case CLOSE: @@ -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: @@ -438,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) { diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index 9aa91a97..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.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())); - return true; + 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; } 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/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 631aa206..b17a3bfc 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; } @@ -307,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, diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 15c31ae1..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; @@ -160,7 +162,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" + @@ -683,10 +685,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")) { @@ -706,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!"); @@ -718,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)); } @@ -1097,10 +1104,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/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/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/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/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..a84f3fb5 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.colorItemText(setPlaceholdersAndArguments(item.options().displayName().get()), plugin.getGeneralConfig().suppressDefaultItalics())); } 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 79fbd8eb..f999fc36 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -6,12 +6,12 @@ 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; 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; @@ -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); @@ -271,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.colorItemText(displayName, plugin.getGeneralConfig().suppressDefaultItalics())); } - 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; @@ -298,34 +298,33 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { break; } - itemMeta.setLore(lore); + itemMeta.lore(lore); if (this.options.unbreakable()) { 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()); } } @@ -477,72 +476,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; } @@ -569,14 +502,16 @@ 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) { + final boolean suppressDefaultItalics = plugin.getGeneralConfig().suppressDefaultItalics(); + 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(line -> StringUtils.colorItemText(line, suppressDefaultItalics)) .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 286877dd..181fd69b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -3,17 +3,15 @@ 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 org.bukkit.Bukkit; 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; @@ -33,7 +30,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; @@ -43,13 +40,13 @@ 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)) { - Msg.msg(sender, "Menus can only be opened by players!"); + if (!(sender instanceof Player player)) { + plugin.sms(sender, Messages.MENUS_ARE_PLAYER_ONLY); return true; } @@ -60,7 +57,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(); - Msg.msg(sender, StringUtils.replacePlaceholders(usageMessage, (Player) sender)); + plugin.sms(sender, StringUtils.color(StringUtils.replacePlaceholders(usageMessage, player))); } return true; } @@ -79,7 +76,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; @@ -89,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, @@ -151,20 +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); - - 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, 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/placeholder/Expansion.java b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java index 93e86fd2..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; @@ -36,12 +35,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 @@ -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..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; @@ -85,7 +87,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 +102,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())) { @@ -121,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; @@ -136,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; @@ -154,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); @@ -172,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/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/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/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/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/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/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index 38ad9457..059bb5f5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -1,239 +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 com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -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.lang.reflect.Field; -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; - } - - 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 - ); - } - 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(); - - if (VersionHelper.HAS_PLAYER_PROFILES) { - 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); - } - - 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; - } - - for (Property property : profile.getProperties().get("textures")) { - if (property.getName().equals("textures")) { - return decodeSkinUrl(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); - - if (VersionHelper.HAS_PLAYER_PROFILES && 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()); - } - - 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 (!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; - } - - /** - * 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 - */ - @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/SoundUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java index 3ffc4842..9ed4de2d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java @@ -1,21 +1,24 @@ package com.extendedclip.deluxemenus.utils; +import org.bukkit.Registry; import org.bukkit.Sound; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; 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); - } + /** + * 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. + * + * @return the sound, or {@code null} if no sound matches + */ + public static @Nullable Sound getSound(@NotNull final String name) { + return RegistryUtils.byNameOrKey(Registry.SOUNDS, 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..9e8bb5a1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -1,91 +1,150 @@ -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); - if (VersionHelper.IS_HEX_VERSION) { - 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)); + } + + /** + * 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 colorItemText(@NotNull final String input, final boolean suppressDefaultItalics) { + final Component component = color(input); + return suppressDefaultItalics + ? component.applyFallbackStyle(TextDecoration.ITALIC.withState(false)) + : component; + } + + /** + * 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; + } + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 87e9a943..a941592a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -1,223 +1,86 @@ -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; + +/** + * Class for detecting server version. + * + * @author Matt from triumph-gui + */ +public final class VersionHelper { + + // 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; + + 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; + + 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; + } + +} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml new file mode 100644 index 00000000..b04ba96e --- /dev/null +++ b/src/main/resources/paper-plugin.yml @@ -0,0 +1,87 @@ +name: DeluxeMenus +version: '${version}' +main: com.extendedclip.deluxemenus.DeluxeMenus +description: All in one inventory menu system +authors: [ HelpChat ] +api-version: '1.20.6' + +dependencies: + server: + PlaceholderAPI: + 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