From eaf035beab8fe7ca75ba294b7a9c57c4cb35932e Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Sun, 11 Oct 2026 17:35:50 +0200 Subject: [PATCH 01/14] Remove adventure platform dependencies Removed adventure-platform-bukkit and adventure-platform-bungee dependencies. --- gradle/libs.versions.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3122068a..1a3a0eb9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,8 +19,6 @@ netty = { module = "io.netty:netty-all", version.ref = "netty" } adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-serializer = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" } -adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } -adventure-platform-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" } libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" } libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" } libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" } From 8e13b2854d43b7e3bfb2e829045c0d86abec28dc Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Sun, 11 Oct 2026 17:51:56 +0200 Subject: [PATCH 02/14] fix(bungee): remove adventure-platform to prevent Adventure 4.x/5.x class conflict --- bungeecord/build.gradle.kts | 3 - .../jonesdev/sonar/bungee/BungeeAudience.java | 96 +++++++++++++++++++ .../jonesdev/sonar/bungee/SonarBungee.java | 12 +-- .../sonar/common/boot/LibraryLoader.java | 39 ++++---- 4 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java diff --git a/bungeecord/build.gradle.kts b/bungeecord/build.gradle.kts index 05ef2715..cc09077b 100644 --- a/bungeecord/build.gradle.kts +++ b/bungeecord/build.gradle.kts @@ -15,9 +15,6 @@ dependencies { compileOnly(rootProject.libs.bungeecord) - implementation(rootProject.libs.adventure.platform.bungee) { - exclude(module = "adventure-nbt") - } implementation(rootProject.libs.bstats.bungee) implementation(rootProject.libs.libby.bungee) } diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java new file mode 100644 index 00000000..25160c54 --- /dev/null +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 Sonar Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.jonesdev.sonar.bungee; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.title.Title; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.chat.ComponentSerializer; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; + +/** + * Minimal {@link Audience} implementation backed by the native BungeeCord chat API. + *
+ * BungeeCord does not ship Adventure, so we used to bundle adventure-platform-bungeecord (Adventure 4.x). + * That conflicted with the Adventure 5.x libraries Sonar loads at runtime, because both end up + * in the same relocated package. Converting components to JSON and passing them to BungeeCord's own + * {@link ComponentSerializer} allows us to depend on exactly one Adventure version. + */ +final class BungeeAudience implements Audience { + /** + * Protocol version of Minecraft 1.16, the first version with support for hex colors + */ + private static final int MINECRAFT_1_16_PROTOCOL = 735; + + private final CommandSender sender; + + BungeeAudience(final @NotNull CommandSender sender) { + this.sender = sender; + } + + @Override + public void sendMessage(final @NotNull Component message) { + sender.sendMessage(toBungee(message)); + } + + @Override + public void sendActionBar(final @NotNull Component message) { + // Only players have an action bar + if (sender instanceof ProxiedPlayer) { + ((ProxiedPlayer) sender).sendMessage(ChatMessageType.ACTION_BAR, toBungee(message)); + } + } + + @Override + public void showTitle(final @NotNull Title title) { + // Only players can see titles + if (!(sender instanceof ProxiedPlayer)) return; + + final Title.Times times = title.times(); + final net.md_5.bungee.api.Title bungeeTitle = ProxyServer.getInstance().createTitle() + .title(toBungee(title.title())) + .subTitle(toBungee(title.subtitle())) + // Use the same defaults as Title#DEFAULT_TIMES (0.5s, 3.5s, 1s) + .fadeIn(times == null ? 10 : toTicks(times.fadeIn())) + .stay(times == null ? 70 : toTicks(times.stay())) + .fadeOut(times == null ? 20 : toTicks(times.fadeOut())); + bungeeTitle.send((ProxiedPlayer) sender); + } + + private static int toTicks(final @NotNull Duration duration) { + return (int) (duration.toMillis() / 50L); + } + + private @NotNull BaseComponent[] toBungee(final @NotNull Component component) { + // Downsample hex colors for clients that do not support them + final boolean legacyClient = sender instanceof ProxiedPlayer + && ((ProxiedPlayer) sender).getPendingConnection().getVersion() < MINECRAFT_1_16_PROTOCOL; + final GsonComponentSerializer serializer = legacyClient + ? GsonComponentSerializer.colorDownsamplingGson() + : GsonComponentSerializer.gson(); + return ComponentSerializer.parse(serializer.serialize(component)); + } +} diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java index 77cc5fff..9288896d 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java @@ -20,8 +20,8 @@ import com.alessiodp.libby.BungeeLibraryManager; import lombok.Getter; import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; import org.bstats.bungeecord.Metrics; import org.bstats.charts.SimplePie; import org.jetbrains.annotations.NotNull; @@ -40,22 +40,18 @@ public SonarBungee(final @NotNull SonarBungeePlugin plugin) { super(plugin, SonarPlatform.BUNGEE, plugin.getDataFolder(), new BungeeLibraryManager(plugin)); } - /** - * Wrapper for BungeeCord audiences - */ - private final BungeeAudiences bungeeAudiences = BungeeAudiences.create(getPlugin()); - @Override public @Nullable Audience audience(final @Nullable UUID uniqueId) { if (uniqueId == null) { return null; } - return bungeeAudiences.player(uniqueId); + final ProxiedPlayer player = getPlugin().getServer().getPlayer(uniqueId); + return player == null ? null : new BungeeAudience(player); } @Override public @NotNull Audience sender(final @NotNull Object object) { - return bungeeAudiences.sender((CommandSender) object); + return new BungeeAudience((CommandSender) object); } /** diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java index dec610f6..a1e865ea 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java @@ -69,25 +69,14 @@ void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull // Only load adventure if not on Velocity if (platform != SonarPlatform.VELOCITY) { if (platform != SonarPlatform.PAPER) { + // BungeeCord does not ship Adventure, and we do not bundle adventure-platform there anymore + // (mixing Adventure 4.x and 5.x in the same relocated package causes NoSuchMethodErrors). + // Therefore, adventure-api and everything else Adventure needs has to be resolved at runtime. + final boolean resolveTransitive = platform == SonarPlatform.BUNGEE; libraryManager.loadLibraries( - Library.builder() - .groupId("net{}kyori") - .artifactId("adventure-text-minimessage") - .version("5.2.0") - .relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori") - .build(), - Library.builder() - .groupId("net{}kyori") - .artifactId("adventure-text-serializer-gson") - .version("5.2.0") - .relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori") - .build(), - Library.builder() - .groupId("net{}kyori") - .artifactId("adventure-nbt") - .version("5.2.0") - .relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori") - .build() + adventureLibrary("adventure-text-minimessage", resolveTransitive), + adventureLibrary("adventure-text-serializer-gson", resolveTransitive), + adventureLibrary("adventure-nbt", resolveTransitive) ); } else { // On Paper, we only relocate the nbt package since the rest of adventure is bundled with the server. @@ -102,4 +91,18 @@ void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull } } } + + private @NotNull Library adventureLibrary(final @NotNull String artifactId, final boolean resolveTransitive) { + final Library.Builder builder = Library.builder() + .groupId("net{}kyori") + .artifactId(artifactId) + .version("5.2.0") + .relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori"); + if (resolveTransitive) { + builder.resolveTransitiveDependencies(true) + // Gson is already provided by the proxy, so we must not load another copy + .excludeTransitiveDependency("com.google.code.gson", "gson"); + } + return builder.build(); + } } From d8efc7aeb643e837f536d4146fc4afa20748ab01 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Sun, 11 Oct 2026 17:51:56 +0200 Subject: [PATCH 03/14] fix(bukkit): restore missing adventure-platform-bukkit catalog entry --- gradle/libs.versions.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a3a0eb9..6202c34e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ netty = { module = "io.netty:netty-all", version.ref = "netty" } adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-serializer = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" } +adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" } libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" } libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" } From 60b8f7f7a975fb26bf7105a3a5e1ef57d1dd3dd2 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Sun, 11 Oct 2026 19:17:56 +0200 Subject: [PATCH 04/14] Remove adventure-platform-bukkit dependency Removed adventure-platform-bukkit dependency from libs.versions.toml --- gradle/libs.versions.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6202c34e..1a3a0eb9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,6 @@ netty = { module = "io.netty:netty-all", version.ref = "netty" } adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-serializer = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" } -adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" } libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" } libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" } From 305b44d4457f02677f40171710dd40d8e4f3d713 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Mon, 12 Oct 2026 17:00:56 +0200 Subject: [PATCH 05/14] Readd adventure-platform-bukkit dependency --- gradle/libs.versions.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a3a0eb9..6202c34e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ netty = { module = "io.netty:netty-all", version.ref = "netty" } adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-serializer = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" } +adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" } libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" } libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" } From 1a91a0e9dc2bad7a325892b9533e5cdaf505cf2c Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Mon, 12 Oct 2026 17:02:53 +0200 Subject: [PATCH 06/14] Update copyright year and refactor action bar method Updated copyright year from 2025 to 2026 and refactored action bar message sending as commented by @jonesdevelopment --- .../main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java index 25160c54..4d5488d8 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 Sonar Contributors + * Copyright (C) 2026 Sonar Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,8 +59,8 @@ public void sendMessage(final @NotNull Component message) { @Override public void sendActionBar(final @NotNull Component message) { // Only players have an action bar - if (sender instanceof ProxiedPlayer) { - ((ProxiedPlayer) sender).sendMessage(ChatMessageType.ACTION_BAR, toBungee(message)); + if (sender instanceof ProxiedPlayer player) { + player.sendMessage(ChatMessageType.ACTION_BAR, toBungee(message)); } } From 00cb2c29109d30b658fa46903c085a56a8288234 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Mon, 12 Oct 2026 17:06:02 +0200 Subject: [PATCH 07/14] Refactor title code as commented --- .../java/xyz/jonesdev/sonar/bungee/BungeeAudience.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java index 4d5488d8..f9e34055 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -70,15 +70,14 @@ public void showTitle(final @NotNull Title title) { if (!(sender instanceof ProxiedPlayer)) return; final Title.Times times = title.times(); - final net.md_5.bungee.api.Title bungeeTitle = ProxyServer.getInstance().createTitle() + ProxyServer.getInstance().createTitle() .title(toBungee(title.title())) .subTitle(toBungee(title.subtitle())) // Use the same defaults as Title#DEFAULT_TIMES (0.5s, 3.5s, 1s) .fadeIn(times == null ? 10 : toTicks(times.fadeIn())) .stay(times == null ? 70 : toTicks(times.stay())) - .fadeOut(times == null ? 20 : toTicks(times.fadeOut())); - bungeeTitle.send((ProxiedPlayer) sender); - } + .fadeOut(times == null ? 20 : toTicks(times.fadeOut())) + .send((ProxiedPlayer) sender); private static int toTicks(final @NotNull Duration duration) { return (int) (duration.toMillis() / 50L); From 834117864ec8b40429d81195eb89ad75bbcf9b4b Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Mon, 12 Oct 2026 17:49:13 +0200 Subject: [PATCH 08/14] I forgot a closing bracket :( --- .../src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java index f9e34055..4a485deb 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -78,6 +78,7 @@ public void showTitle(final @NotNull Title title) { .stay(times == null ? 70 : toTicks(times.stay())) .fadeOut(times == null ? 20 : toTicks(times.fadeOut())) .send((ProxiedPlayer) sender); + } private static int toTicks(final @NotNull Duration duration) { return (int) (duration.toMillis() / 50L); From 60256a0cbc181a53e7af349ae8df6306cbb1e4d4 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Tue, 13 Oct 2026 13:09:17 +0200 Subject: [PATCH 09/14] Change LibraryLoader to public class with state management Refactor LibraryLoader to a public class and add loaded state management. --- .../xyz/jonesdev/sonar/common/boot/LibraryLoader.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java index a1e865ea..bd46b3e3 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java @@ -24,8 +24,14 @@ import xyz.jonesdev.sonar.api.SonarPlatform; @UtilityClass -class LibraryLoader { - void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull SonarPlatform platform) { +public class LibraryLoader { + private boolean loaded; + + public void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull SonarPlatform platform) { + // The platform entry point might already have loaded all libraries + if (loaded) return; + loaded = true; + libraryManager.addMavenCentral(); libraryManager.addJitPack(); libraryManager.loadLibraries( From d47fce69cc6705984d037b0ec6a9ce16aa0c32f6 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Tue, 13 Oct 2026 13:12:25 +0200 Subject: [PATCH 10/14] Update copyright year and load libraries earlier --- .../xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java index 3f79cb2e..0a9f9efd 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 Sonar Contributors + * Copyright (C) 2026 Sonar Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,15 +25,20 @@ public final class SonarBungeePlugin extends Plugin { private ProxyServer server; private SonarBungee bootstrap; - + @Override public void onEnable() { server = ProxyServer.getInstance(); + // Adventure is not bundled in the jar, so the libraries have to be on the classpath + // before SonarBungee is loaded. The JVM resolves its Adventure types during class verification. + LibraryLoader.loadLibraries(new BungeeLibraryManager(this), SonarPlatform.BUNGEE); + bootstrap = new SonarBungee(this); bootstrap.initialize(); } + @Override public void onDisable() { bootstrap.shutdown(); From ba675a55681cfe8aa65736b23d4cb242d1e68192 Mon Sep 17 00:00:00 2001 From: FeGmbH Date: Tue, 13 Oct 2026 13:19:02 +0200 Subject: [PATCH 11/14] Add imports for BungeeLibraryManager and SonarPlatform --- .../main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java index 0a9f9efd..490ae978 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java @@ -17,9 +17,12 @@ package xyz.jonesdev.sonar.bungee; +import com.alessiodp.libby.BungeeLibraryManager; import lombok.Getter; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; +import xyz.jonesdev.sonar.api.SonarPlatform; +import xyz.jonesdev.sonar.common.boot.LibraryLoader; @Getter public final class SonarBungeePlugin extends Plugin { From 0a484ccea9e926fef22b21f89229c885266e0585 Mon Sep 17 00:00:00 2001 From: Jones Date: Tue, 13 Oct 2026 13:40:57 +0200 Subject: [PATCH 12/14] Avoid double library loading without tracking state --- .../main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java | 4 ++-- .../xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java | 9 +++++---- .../xyz/jonesdev/sonar/common/boot/LibraryLoader.java | 6 ------ .../xyz/jonesdev/sonar/common/boot/SonarBootstrap.java | 8 +++++++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java index 9288896d..de373bba 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java @@ -36,8 +36,8 @@ @Getter public final class SonarBungee extends SonarBootstrap { - public SonarBungee(final @NotNull SonarBungeePlugin plugin) { - super(plugin, SonarPlatform.BUNGEE, plugin.getDataFolder(), new BungeeLibraryManager(plugin)); + public SonarBungee(final @NotNull SonarBungeePlugin plugin, final @NotNull BungeeLibraryManager libraryManager) { + super(plugin, SonarPlatform.BUNGEE, plugin.getDataFolder(), libraryManager); } @Override diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java index 490ae978..fe80ca34 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungeePlugin.java @@ -28,20 +28,21 @@ public final class SonarBungeePlugin extends Plugin { private ProxyServer server; private SonarBungee bootstrap; - + @Override public void onEnable() { server = ProxyServer.getInstance(); + // HACK: // Adventure is not bundled in the jar, so the libraries have to be on the classpath // before SonarBungee is loaded. The JVM resolves its Adventure types during class verification. - LibraryLoader.loadLibraries(new BungeeLibraryManager(this), SonarPlatform.BUNGEE); + final BungeeLibraryManager libraryManager = new BungeeLibraryManager(this); + LibraryLoader.loadLibraries(libraryManager, SonarPlatform.BUNGEE); - bootstrap = new SonarBungee(this); + bootstrap = new SonarBungee(this, libraryManager); bootstrap.initialize(); } - @Override public void onDisable() { bootstrap.shutdown(); diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java index bd46b3e3..f9d09c26 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/boot/LibraryLoader.java @@ -25,13 +25,7 @@ @UtilityClass public class LibraryLoader { - private boolean loaded; - public void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull SonarPlatform platform) { - // The platform entry point might already have loaded all libraries - if (loaded) return; - loaded = true; - libraryManager.addMavenCentral(); libraryManager.addJitPack(); libraryManager.loadLibraries( diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java b/common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java index ba7bb700..af721008 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java @@ -68,7 +68,13 @@ public SonarBootstrap(final @NotNull T plugin, final @NotNull File dataDirectory, final @NotNull LibraryManager libraryManager) { // Load all libraries before anything else - LibraryLoader.loadLibraries(libraryManager, platform); + // HACK: + // We manually load the libraries on BungeeCord beforehand, + // so we skip this call to avoid loading libraries twice. + // See SonarBungeePlugin.java#L40 + if (platform != SonarPlatform.BUNGEE) { + LibraryLoader.loadLibraries(libraryManager, platform); + } this.libraryManager = libraryManager; // Check if Netty is up to date ProtocolUtil.checkNettyVersion(); From c638197c850d1eabe271dab18dbc75d10f73e4ba Mon Sep 17 00:00:00 2001 From: Jones Date: Tue, 13 Oct 2026 13:42:32 +0200 Subject: [PATCH 13/14] Invert BungeeAudience#showTitle player check * Now makes use of Java 17+ features --- .../jonesdev/sonar/bungee/BungeeAudience.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java index 4a485deb..416bc7f4 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -67,17 +67,17 @@ public void sendActionBar(final @NotNull Component message) { @Override public void showTitle(final @NotNull Title title) { // Only players can see titles - if (!(sender instanceof ProxiedPlayer)) return; - - final Title.Times times = title.times(); - ProxyServer.getInstance().createTitle() - .title(toBungee(title.title())) - .subTitle(toBungee(title.subtitle())) - // Use the same defaults as Title#DEFAULT_TIMES (0.5s, 3.5s, 1s) - .fadeIn(times == null ? 10 : toTicks(times.fadeIn())) - .stay(times == null ? 70 : toTicks(times.stay())) - .fadeOut(times == null ? 20 : toTicks(times.fadeOut())) - .send((ProxiedPlayer) sender); + if (sender instanceof ProxiedPlayer player) { + final Title.Times times = title.times(); + ProxyServer.getInstance().createTitle() + .title(toBungee(title.title())) + .subTitle(toBungee(title.subtitle())) + // Use the same defaults as Title#DEFAULT_TIMES (0.5s, 3.5s, 1s) + .fadeIn(times == null ? 10 : toTicks(times.fadeIn())) + .stay(times == null ? 70 : toTicks(times.stay())) + .fadeOut(times == null ? 20 : toTicks(times.fadeOut())) + .send(player); + } } private static int toTicks(final @NotNull Duration duration) { @@ -86,8 +86,8 @@ private static int toTicks(final @NotNull Duration duration) { private @NotNull BaseComponent[] toBungee(final @NotNull Component component) { // Downsample hex colors for clients that do not support them - final boolean legacyClient = sender instanceof ProxiedPlayer - && ((ProxiedPlayer) sender).getPendingConnection().getVersion() < MINECRAFT_1_16_PROTOCOL; + final boolean legacyClient = sender instanceof ProxiedPlayer player + && player.getPendingConnection().getVersion() < MINECRAFT_1_16_PROTOCOL; final GsonComponentSerializer serializer = legacyClient ? GsonComponentSerializer.colorDownsamplingGson() : GsonComponentSerializer.gson(); From 72be1426956509537024c23f0ad327e5fc3b1474 Mon Sep 17 00:00:00 2001 From: Jones Date: Tue, 13 Oct 2026 13:43:42 +0200 Subject: [PATCH 14/14] Avoid storing extra 1.16 protocol version ID --- .../java/xyz/jonesdev/sonar/bungee/BungeeAudience.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java index 416bc7f4..683fb4f9 100644 --- a/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java +++ b/bungeecord/src/main/java/xyz/jonesdev/sonar/bungee/BungeeAudience.java @@ -28,6 +28,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.chat.ComponentSerializer; import org.jetbrains.annotations.NotNull; +import xyz.jonesdev.sonar.api.antibot.protocol.ProtocolVersion; import java.time.Duration; @@ -40,11 +41,6 @@ * {@link ComponentSerializer} allows us to depend on exactly one Adventure version. */ final class BungeeAudience implements Audience { - /** - * Protocol version of Minecraft 1.16, the first version with support for hex colors - */ - private static final int MINECRAFT_1_16_PROTOCOL = 735; - private final CommandSender sender; BungeeAudience(final @NotNull CommandSender sender) { @@ -87,7 +83,7 @@ private static int toTicks(final @NotNull Duration duration) { private @NotNull BaseComponent[] toBungee(final @NotNull Component component) { // Downsample hex colors for clients that do not support them final boolean legacyClient = sender instanceof ProxiedPlayer player - && player.getPendingConnection().getVersion() < MINECRAFT_1_16_PROTOCOL; + && player.getPendingConnection().getVersion() < ProtocolVersion.MINECRAFT_1_16.getProtocol(); final GsonComponentSerializer serializer = legacyClient ? GsonComponentSerializer.colorDownsamplingGson() : GsonComponentSerializer.gson();