diff --git a/platform-bungeecord/build.gradle b/platform-bungeecord/build.gradle index aad0af0b..56a2736a 100644 --- a/platform-bungeecord/build.gradle +++ b/platform-bungeecord/build.gradle @@ -6,5 +6,5 @@ dependencies { } implementation project(":adventure-platform-facet") api project(":adventure-text-serializer-bungeecord") - compileOnly 'net.md-5:bungeecord-api:1.16-R0.4' + compileOnly 'net.md-5:bungeecord-api:1.20-R0.2' } diff --git a/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeFacet.java b/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeFacet.java index 96ad3220..2690bd8b 100644 --- a/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeFacet.java +++ b/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeFacet.java @@ -23,6 +23,7 @@ */ package net.kyori.adventure.platform.bungeecord; +import java.lang.invoke.MethodHandle; import java.util.Collection; import java.util.Set; import java.util.UUID; @@ -44,6 +45,7 @@ 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.chat.TextComponent; import net.md_5.bungee.api.connection.Connection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.chat.ComponentSerializer; @@ -51,6 +53,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static net.kyori.adventure.platform.bungeecord.BungeeReflection.findMethod; +import static net.kyori.adventure.platform.bungeecord.BungeeReflection.hasMethod; +import static net.kyori.adventure.platform.facet.Knob.logError; import static net.kyori.adventure.platform.facet.Knob.logUnsupported; class BungeeFacet extends FacetBase { @@ -219,6 +224,18 @@ public void resetTitle(final @NotNull ProxiedPlayer viewer) { } static class BossBar extends Message implements BossBarPacket { + private static MethodHandle SET_TITLE_STRING; + private static MethodHandle SET_TITLE_COMPONENT; + + static { + final Class bossBarClass = net.md_5.bungee.protocol.packet.BossBar.class; + if (hasMethod(bossBarClass, "setTitle", String.class)) { + SET_TITLE_STRING = findMethod(bossBarClass, "setTitle", void.class, String.class); + } else { + SET_TITLE_COMPONENT = findMethod(bossBarClass, "setTitle", void.class, BaseComponent.class); + } + } + private final Set viewers; private final net.md_5.bungee.protocol.packet.BossBar bar; private volatile boolean initialized = false; @@ -255,7 +272,8 @@ public void bossBarInitialized(final net.kyori.adventure.bossbar.@NotNull BossBa @Override public void bossBarNameChanged(final net.kyori.adventure.bossbar.@NotNull BossBar bar, final @NotNull Component oldName, final @NotNull Component newName) { if (!this.viewers.isEmpty()) { - this.bar.setTitle(ComponentSerializer.toString(this.createMessage(this.viewers.iterator().next(), newName))); + final BaseComponent[] message = this.createMessage(this.viewers.iterator().next(), newName); + this.updateBarTitle(message); this.broadcastPacket(ACTION_TITLE); } } @@ -318,6 +336,18 @@ private void broadcastPacket(final int action) { } } + private void updateBarTitle(final BaseComponent[] message) { + try { + if (SET_TITLE_STRING != null) { + SET_TITLE_STRING.invoke(this.bar, ComponentSerializer.toString(message)); + } else { + SET_TITLE_COMPONENT.invoke(this.bar, TextComponent.fromArray(message)); + } + } catch (final Throwable throwable) { + logError(throwable, "Cannot update the BossBar title"); + } + } + private void sendPacket(final int action, final ProxiedPlayer... viewers) { synchronized (this.bar) { final int lastAction = this.bar.getAction(); diff --git a/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeReflection.java b/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeReflection.java new file mode 100644 index 00000000..f263a200 --- /dev/null +++ b/platform-bungeecord/src/main/java/net/kyori/adventure/platform/bungeecord/BungeeReflection.java @@ -0,0 +1,82 @@ +/* + * This file is part of adventure-platform, licensed under the MIT License. + * + * Copyright (c) 2018-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.platform.bungeecord; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import org.jetbrains.annotations.Nullable; + +/** + * Reflection utilities for accessing legacy BungeeCord methods. + */ +final class BungeeReflection { + private BungeeReflection() { + } + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + /** + * Checks if the specified class has a method with the given name and parameter types. + * + * @param holderClass The class to check for the method. Can be {@code null}, in which case the method returns {@code false}. + * @param methodName The name of the method to check for. + * @param parameters The parameter types of the method. The method returns {@code false} if any parameter type is {@code null}. + * @return {@code true} if the method exists in the class; {@code false} otherwise. + */ + public static boolean hasMethod(final @Nullable Class holderClass, final String methodName, final Class... parameters) { + if (holderClass == null) return false; + for (final Class parameter : parameters) { + if (parameter == null) return false; + } + try { + holderClass.getMethod(methodName, parameters); + return true; + } catch (final NoSuchMethodException ignored) { + } + return false; + } + + /** + * Finds and returns a {@link MethodHandle} for the specified method of the given class. This allows for dynamic method invocation. + * + * @param holderClass The class containing the method. + * @param methodName The name of the method to find. + * @param returnType The return type of the method. + * @param parameters The parameter types of the method. + * @return A {@link MethodHandle} for the specified method, or {@code null} if the method cannot be found or if any parameter is {@code null}. + */ + public static MethodHandle findMethod(final @Nullable Class holderClass, final String methodName, final Class returnType, final Class... parameters) { + if (holderClass == null || returnType == null) return null; + for (final Class parameter : parameters) { + if (parameter == null) return null; + } + try { + return LOOKUP.findVirtual(holderClass, methodName, MethodType.methodType(returnType, parameters)); + } catch (NoSuchMethodException | IllegalAccessException ignored) { + } + return null; + } + +} diff --git a/settings.gradle b/settings.gradle index 3e856a46..69522c11 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,13 @@ dependencyResolutionManagement { includeGroup "com.destroystokyo.paper" } } + maven { + name "minecraft-libraries" + url "https://libraries.minecraft.net" + mavenContent { + includeGroup "com.mojang" + } + } } }