diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 18b110f94e..64d12d28e4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -6,15 +6,15 @@ name: Java CI with Gradle on: [push, pull_request] jobs: - build-11: + build-17: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 distribution: 'temurin' cache: 'gradle' - name: Grant execute permission for gradlew diff --git a/api/build.gradle.kts b/api/build.gradle.kts index d3e55559fd..c6e35abcac 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -39,7 +39,7 @@ dependencies { api(libs.guice) api(libs.checker.qual) api(libs.brigadier) - api(libs.bundles.configurate) + api(libs.bundles.configurate4) api(libs.caffeine) } @@ -60,17 +60,13 @@ tasks { "https://www.slf4j.org/apidocs/", "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/", - "https://docs.oracle.com/en/java/javase/11/docs/api/", - "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", + "https://docs.oracle.com/en/java/javase/17/docs/api/", + //"https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", + "https://jd.advntr.dev/api/4.14.0/", "https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine" ) // Disable the crazy super-strict doclint tool in Java 8 o.addStringOption("Xdoclint:none", "-quiet") - - // Remove "undefined" from search paths when generating javadoc for a non-modular project (JDK-8215291) - if (JavaVersion.current() >= JavaVersion.VERSION_1_9 && JavaVersion.current() < JavaVersion.VERSION_12) { - o.addBooleanOption("-no-module-directories", true) - } } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerResourcePackStatusEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerResourcePackStatusEvent.java index 8e5d6887f3..8605d9bc2a 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerResourcePackStatusEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerResourcePackStatusEvent.java @@ -133,6 +133,33 @@ public enum Status { /** * The player has accepted the resource pack and is now downloading it. */ - ACCEPTED + ACCEPTED, + /** + * The player has downloaded the resource pack. + */ + DOWNLOADED, + /** + * The URL of the resource pack failed to load. + */ + INVALID_URL, + /** + * The player failed to reload the resource pack. + */ + FAILED_RELOAD, + /** + * The resource pack was discarded. + */ + DISCARDED; + + /** + * Returns true if the resource pack status is intermediate, indicating that the player has + * either accepted the resource pack and is currently downloading it or has successfully + * downloaded it. + * + * @return true if the status is intermediate (ACCEPTED or DOWNLOADED), false otherwise + */ + public boolean isIntermediate() { + return this == ACCEPTED || this == DOWNLOADED; + } } -} +} \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index ef7024efeb..9fe56010b1 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -63,7 +63,8 @@ public enum ProtocolVersion { MINECRAFT_1_19_3(761, "1.19.3"), MINECRAFT_1_19_4(762, "1.19.4"), MINECRAFT_1_20(763, "1.20", "1.20.1"), - MINECRAFT_1_20_2(764, "1.20.2"); + MINECRAFT_1_20_2(764, "1.20.2"), + MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"); private static final int SNAPSHOT_BIT = 30; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 99bf09da0e..dae239b1fb 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -19,6 +19,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ModInfo; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -236,6 +237,7 @@ default void clearHeaderAndFooter() { * @return the applied resource pack or null if none. */ @Nullable + @Deprecated ResourcePackInfo getAppliedResourcePack(); /** @@ -246,8 +248,26 @@ default void clearHeaderAndFooter() { * @return the pending resource pack or null if none */ @Nullable + @Deprecated ResourcePackInfo getPendingResourcePack(); + /** + * Gets the {@link ResourcePackInfo} of the currently applied + * resource-packs. + * + * @return collection of the applied resource packs. + */ + Collection getAppliedResourcePacks(); + + /** + * Gets the {@link ResourcePackInfo} of the resource packs + * the user is currently downloading or is currently + * prompted to install. + * + * @return collection of the pending resource packs + */ + Collection getPendingResourcePacks(); + /** * Note that this method does not send a plugin message to the server the player * is connected to. You should only use this method if you are trying to communicate @@ -279,4 +299,4 @@ default void clearHeaderAndFooter() { * @return the player's client brand */ @Nullable String getClientBrand(); -} +} \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java index c0023310c9..6c3d44591f 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java @@ -7,6 +7,7 @@ package com.velocitypowered.api.proxy.player; +import java.util.UUID; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; @@ -15,6 +16,13 @@ */ public interface ResourcePackInfo { + /** + * Gets the id of this resource-pack. + * + * @return the id of the resource-pack + */ + UUID getId(); + /** * Gets the link the resource-pack can be found at. * @@ -96,6 +104,13 @@ public interface ResourcePackInfo { */ interface Builder { + /** + * Sets the id of the resource pack. + * + * @param id the id the resource-pack + */ + Builder setId(UUID id); + /** * Sets the resource-pack as required to play on the network. * This feature was introduced in 1.17. @@ -159,4 +174,4 @@ enum Origin { */ PLUGIN_ON_PROXY } -} +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3ec6b160c0..9e862dadf0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ subprojects { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) + languageVersion.set(JavaLanguageVersion.of(17)) } } diff --git a/gradle.properties b/gradle.properties index 0f76ae1511..cd9c67796f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=3.2.0-SNAPSHOT +version=3.3.0-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index df4fb54dbe..07f96fdbdb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] -configurate = "3.7.3" +configurate3 = "3.7.3" +configurate4 = "4.1.2" flare = "2.0.1" log4j = "2.20.0" netty = "4.1.100.Final" @@ -10,7 +11,8 @@ shadow = "com.github.johnrengelman.shadow:8.1.0" spotless = "com.diffplug.spotless:6.12.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.14.0" +# See JD links in velocity-apo when moving to non-snapshot versions +adventure-bom = "net.kyori:adventure-bom:4.15.0-SNAPSHOT" adventure-facet = "net.kyori:adventure-platform-facet:4.3.0" asm = "org.ow2.asm:asm:9.5" asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3" @@ -20,9 +22,12 @@ caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5" checker-qual = "org.checkerframework:checker-qual:3.28.0" checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3" completablefutures = "com.spotify:completable-futures:0.3.5" -configurate-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate" } -configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } -configurate-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate" } +configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" } +configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" } +configurate3-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate3" } +configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate4" } +configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" } +configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" } disruptor = "com.lmax:disruptor:3.4.4" fastutil = "it.unimi.dsi:fastutil:8.5.12" flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } @@ -54,6 +59,7 @@ spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" [bundles] -configurate = ["configurate-hocon", "configurate-yaml", "configurate-gson"] +configurate3 = ["configurate3-hocon", "configurate3-yaml", "configurate3-gson"] +configurate4 = ["configurate4-hocon", "configurate4-yaml", "configurate4-gson"] flare = ["flare-core", "flare-fastutil"] log4j = ["log4j-api", "log4j-core", "log4j-slf4j-impl", "log4j-iostreams", "log4j-jul"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710..d64cd49177 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c85a1f751..1af9e0930b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d421c..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index fd7042ecdb..14eb4b19dc 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -85,6 +85,11 @@ tasks { exclude("org/checkerframework/checker/**") relocate("org.bstats", "com.velocitypowered.proxy.bstats") + + // Include Configurate 3 + val configurateBuildTask = project(":deprecated-configurate3").tasks.named("shadowJar") + dependsOn(configurateBuildTask) + from(zipTree(configurateBuildTask.map { it.outputs.files.singleFile })) } } diff --git a/proxy/deprecated/configurate3/build.gradle.kts b/proxy/deprecated/configurate3/build.gradle.kts new file mode 100644 index 0000000000..6a7d0daaed --- /dev/null +++ b/proxy/deprecated/configurate3/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.shadow) +} + +dependencies { + implementation(libs.bundles.configurate3) +} + +tasks.shadowJar { + exclude("com/google/**") + exclude("com/typesafe/**") + exclude("org/yaml/**") +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 92914ab368..6e75d8eedb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -38,6 +38,7 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.command.VelocityCommandManager; import com.velocitypowered.proxy.command.builtin.GlistCommand; +import com.velocitypowered.proxy.command.builtin.SendCommand; import com.velocitypowered.proxy.command.builtin.ServerCommand; import com.velocitypowered.proxy.command.builtin.ShutdownCommand; import com.velocitypowered.proxy.command.builtin.VelocityCommand; @@ -115,16 +116,28 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .registerTypeHierarchyAdapter(GameProfile.class, GameProfileSerializer.INSTANCE) .create(); - private static final Gson PRE_1_16_PING_SERIALIZER = ProtocolUtils - .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2) - .serializer() - .newBuilder() + private static final Gson PRE_1_16_PING_SERIALIZER = new GsonBuilder() + .registerTypeHierarchyAdapter( + Component.class, + ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2) + .serializer().getAdapter(Component.class) + ) .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .create(); - private static final Gson POST_1_16_PING_SERIALIZER = ProtocolUtils - .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_16) - .serializer() - .newBuilder() + private static final Gson PRE_1_20_3_PING_SERIALIZER = new GsonBuilder() + .registerTypeHierarchyAdapter( + Component.class, + ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_20_2) + .serializer().getAdapter(Component.class) + ) + .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .create(); + private static final Gson MODERN_PING_SERIALIZER = new GsonBuilder() + .registerTypeHierarchyAdapter( + Component.class, + ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_20_3) + .serializer().getAdapter(Component.class) + ) .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .create(); @@ -216,6 +229,7 @@ void start() { commandManager.register("shutdown", ShutdownCommand.command(this), "end", "stop"); new GlistCommand(this).register(); + new SendCommand(this).register(); this.doStartupConfigLoad(); @@ -768,8 +782,11 @@ public AdventureBossBarManager getBossBarManager() { */ public static Gson getPingGsonInstance(ProtocolVersion version) { if (version == ProtocolVersion.UNKNOWN - || version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - return POST_1_16_PING_SERIALIZER; + || version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + return MODERN_PING_SERIALIZER; + } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + return PRE_1_20_3_PING_SERIALIZER; } return PRE_1_16_PING_SERIALIZER; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java index 52da4b4051..f42a59722d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java @@ -30,4 +30,6 @@ public class CommandMessages { "velocity.command.players-only", NamedTextColor.RED); public static final TranslatableComponent SERVER_DOES_NOT_EXIST = Component.translatable( "velocity.command.server-does-not-exist", NamedTextColor.RED); + public static final TranslatableComponent PLAYER_NOT_FOUND = Component.translatable( + "velocity.command.player-not-found", NamedTextColor.RED); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java index 5d2f19fea0..0bfc0f0745 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java @@ -66,10 +66,18 @@ public void register() { ArgumentCommandNode serverNode = RequiredArgumentBuilder .argument(SERVER_ARG, StringArgumentType.string()) .suggests((context, builder) -> { + String argument = context.getArguments().containsKey(SERVER_ARG) + ? context.getArgument(SERVER_ARG, String.class) + : ""; for (RegisteredServer server : server.getAllServers()) { - builder.suggest(server.getServerInfo().getName()); + String serverName = server.getServerInfo().getName(); + if (serverName.regionMatches(true, 0, argument, 0, argument.length())) { + builder.suggest(serverName); + } + } + if ("all".regionMatches(true, 0, argument, 0, argument.length())) { + builder.suggest("all"); } - builder.suggest("all"); return builder.buildFuture(); }) .executes(this::serverCount) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java new file mode 100644 index 0000000000..a75a86412a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020-2023 Velocity 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 com.velocitypowered.proxy.command.builtin; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.velocitypowered.api.command.BrigadierCommand; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.permission.Tristate; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.util.Objects; +import java.util.Optional; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +/** + * Implements the Velocity default {@code /send} command. + */ +public class SendCommand { + private final ProxyServer server; + private static final String SERVER_ARG = "server"; + private static final String PLAYER_ARG = "player"; + + public SendCommand(ProxyServer server) { + this.server = server; + } + + /** + * Registers this command. + */ + public void register() { + LiteralCommandNode totalNode = LiteralArgumentBuilder + .literal("send") + .requires(source -> + source.getPermissionValue("velocity.command.send") == Tristate.TRUE) + .executes(this::usage) + .build(); + ArgumentCommandNode playerNode = RequiredArgumentBuilder + .argument("player", StringArgumentType.word()) + .suggests((context, builder) -> { + String argument = context.getArguments().containsKey(PLAYER_ARG) + ? context.getArgument(PLAYER_ARG, String.class) + : ""; + for (Player player : server.getAllPlayers()) { + String playerName = player.getUsername(); + if (playerName.regionMatches(true, 0, argument, 0, argument.length())) { + builder.suggest(playerName); + } + } + if ("all".regionMatches(true, 0, argument, 0, argument.length())) { + builder.suggest("all"); + } + if ("current".regionMatches(true, 0, argument, 0, argument.length()) + && context.getSource() instanceof Player) { + builder.suggest("current"); + } + return builder.buildFuture(); + }) + .executes(this::usage) + .build(); + ArgumentCommandNode serverNode = RequiredArgumentBuilder + .argument("server", StringArgumentType.word()) + .suggests((context, builder) -> { + String argument = context.getArguments().containsKey(SERVER_ARG) + ? context.getArgument(SERVER_ARG, String.class) + : ""; + for (RegisteredServer server : server.getAllServers()) { + String serverName = server.getServerInfo().getName(); + if (serverName.regionMatches(true, 0, argument, 0, argument.length())) { + builder.suggest(server.getServerInfo().getName()); + } + } + return builder.buildFuture(); + }) + .executes(this::send) + .build(); + totalNode.addChild(playerNode); + playerNode.addChild(serverNode); + server.getCommandManager().register(new BrigadierCommand(totalNode)); + } + + private int usage(CommandContext context) { + context.getSource().sendMessage( + Component.translatable("velocity.command.send-usage", NamedTextColor.YELLOW) + ); + return 1; + } + + private int send(CommandContext context) { + String serverName = context.getArgument(SERVER_ARG, String.class); + String player = context.getArgument(PLAYER_ARG, String.class); + + Optional maybeServer = server.getServer(serverName); + + if (maybeServer.isEmpty()) { + context.getSource().sendMessage( + CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)) + ); + return 0; + } + + if (server.getPlayer(player).isEmpty() + && !Objects.equals(player, "all") + && !Objects.equals(player, "current")) { + context.getSource().sendMessage( + CommandMessages.PLAYER_NOT_FOUND.args(Component.text(player)) + ); + return 0; + } + + if (Objects.equals(player, "all")) { + for (Player p : server.getAllPlayers()) { + p.createConnectionRequest(server.getServer(serverName).get()).fireAndForget(); + } + return 1; + } + + if (Objects.equals(player, "current")) { + if (!(context.getSource() instanceof Player)) { + context.getSource().sendMessage(CommandMessages.PLAYERS_ONLY); + return 0; + } + + Player source = (Player) context.getSource(); + Optional connectedServer = source.getCurrentServer(); + if (connectedServer.isPresent()) { + for (Player p : connectedServer.get().getServer().getPlayersConnected()) { + p.createConnectionRequest(maybeServer.get()).fireAndForget(); + } + return 1; + } + return 0; + } + + server.getPlayer(player).get().createConnectionRequest( + maybeServer.get()).fireAndForget(); + return 1; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index 189820e040..4e8af7f68d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -37,6 +37,7 @@ import com.velocitypowered.proxy.protocol.packet.PingIdentify; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; @@ -248,6 +249,10 @@ default boolean handle(ResourcePackRequest packet) { return false; } + default boolean handle(RemoveResourcePack packet) { + return false; + } + default boolean handle(ResourcePackResponse packet) { return false; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index d000420fb8..75819a3e92 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -28,7 +28,6 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent; -import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; @@ -49,11 +48,13 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.config.StartUpdate; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.tablist.TabListUuidRewriter; @@ -168,7 +169,8 @@ public boolean handle(BossBar packet) { public boolean handle(ResourcePackRequest packet) { ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl( Preconditions.checkNotNull(packet.getUrl())) - .setPrompt(packet.getPrompt()) + .setId(packet.getId()) + .setPrompt(packet.getPrompt() == null ? null : packet.getPrompt().getComponent()) .setShouldForce(packet.isRequired()) .setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); @@ -196,6 +198,7 @@ public boolean handle(ResourcePackRequest packet) { serverConn.getPlayer().queueResourcePack(toSend); } else if (serverConn.getConnection() != null) { serverConn.getConnection().write(new ResourcePackResponse( + packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED )); @@ -203,6 +206,7 @@ public boolean handle(ResourcePackRequest packet) { }, playerConnection.eventLoop()).exceptionally((ex) -> { if (serverConn.getConnection() != null) { serverConn.getConnection().write(new ResourcePackResponse( + packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED )); @@ -214,6 +218,11 @@ public boolean handle(ResourcePackRequest packet) { return true; } + @Override + public boolean handle(RemoveResourcePack packet) { + return false; //TODO + } + @Override public boolean handle(PluginMessage packet) { if (bungeecordMessageResponder.process(packet)) { @@ -314,7 +323,9 @@ public boolean handle(ServerData packet) { ping -> server.getEventManager() .fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)), playerConnection.eventLoop()).thenAcceptAsync(pingEvent -> this.playerConnection.write( - new ServerData(pingEvent.getPing().getDescriptionComponent(), + new ServerData(new ComponentHolder( + this.serverConn.ensureConnected().getProtocolVersion(), + pingEvent.getPing().getDescriptionComponent()), pingEvent.getPing().getFavicon().orElse(null), packet.isSecureChatEnforced())), playerConnection.eventLoop()); return true; @@ -365,7 +376,7 @@ public void disconnected() { if (server.getConfiguration().isFailoverOnUnexpectedServerDisconnect()) { serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR, - ProtocolVersion.MINECRAFT_1_16), true); + serverConn.getPlayer().getProtocolVersion(), false), true); } else { serverConn.getPlayer().disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); } @@ -387,4 +398,4 @@ public void writabilityChanged() { playerConnection.setAutoReading(writable); } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 82342ffe03..5eafa38a28 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -19,6 +19,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; +import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -81,8 +82,11 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public void activated() { - resourcePackToApply = serverConn.getPlayer().getAppliedResourcePack(); - serverConn.getPlayer().clearAppliedResourcePack(); + ConnectedPlayer player = serverConn.getPlayer(); + if (player.getProtocolVersion() == ProtocolVersion.MINECRAFT_1_20_2) { + resourcePackToApply = player.getAppliedResourcePack(); + player.clearAppliedResourcePack(); + } } @Override @@ -134,12 +138,12 @@ public boolean handle(ResourcePackRequest packet) { resourcePackToApply = null; serverConn.getPlayer().queueResourcePack(toSend); } else if (serverConn.getConnection() != null) { - serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(), + serverConn.getConnection().write(new ResourcePackResponse(packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED)); } }, playerConnection.eventLoop()).exceptionally((ex) -> { if (serverConn.getConnection() != null) { - serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(), + serverConn.getConnection().write(new ResourcePackResponse(packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED)); } logger.error("Exception while handling resource pack send for {}", playerConnection, ex); @@ -227,4 +231,4 @@ private void switchFailure(Throwable cause) { public static enum State { START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 61f733f0f8..d14e3b8234 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -23,7 +23,9 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.PingIdentify; @@ -32,6 +34,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -46,6 +49,7 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(ClientConfigSessionHandler.class); private final VelocityServer server; private final ConnectedPlayer player; + private String brandChannel = null; private CompletableFuture configSwitchFuture; @@ -87,7 +91,7 @@ public boolean handle(KeepAlive packet) { @Override public boolean handle(ClientSettings packet) { - player.setClientSettingsPacket(packet); + player.setClientSettings(packet); return true; } @@ -116,8 +120,9 @@ public boolean handle(PluginMessage packet) { String brand = PluginMessageUtil.readBrandMessage(packet.content()); server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand)); player.setClientBrand(brand); + brandChannel = packet.getChannel(); // Client sends `minecraft:brand` packet immediately after Login, - // but at this time the backend server may not be ready, just discard it. + // but at this time the backend server may not be ready } else { serverConn.ensureConnected().write(packet.retain()); } @@ -182,8 +187,21 @@ public void exception(Throwable throwable) { * @return a future that completes when the config stage is finished */ public CompletableFuture handleBackendFinishUpdate(VelocityServerConnection serverConn) { + MinecraftConnection smc = serverConn.ensureConnected(); + + String brand = serverConn.getPlayer().getClientBrand(); + if (brand != null && brandChannel != null) { + ByteBuf buf = Unpooled.buffer(); + ProtocolUtils.writeString(buf, brand); + PluginMessage brandPacket = new PluginMessage(brandChannel, buf); + smc.write(brandPacket); + } + player.getConnection().write(new FinishedUpdate()); - serverConn.ensureConnected().write(new FinishedUpdate()); + + smc.write(new FinishedUpdate()); + smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); + return configSwitchFuture; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 6004cf8345..08da36ab53 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -53,6 +53,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler; import com.velocitypowered.proxy.protocol.packet.chat.ChatTimeKeeper; import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedCommandHandler; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat; @@ -186,7 +187,7 @@ public boolean handle(KeepAlive packet) { @Override public boolean handle(ClientSettings packet) { - player.setPlayerSettings(packet); + player.setClientSettings(packet); VelocityServerConnection serverConnection = player.getConnectedServer(); if (serverConnection == null) { // No server connection yet, probably transitioning. @@ -318,9 +319,7 @@ public boolean handle(PluginMessage packet) { String brand = PluginMessageUtil.readBrandMessage(packet.content()); server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand)); player.setClientBrand(brand); - backendConn.write( - PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), - player.getProtocolVersion())); + backendConn.write(packet.retain()); } else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) { return true; } else { @@ -633,10 +632,11 @@ private boolean handleCommandTabComplete(TabCompleteRequest packet) { List offers = new ArrayList<>(); for (Suggestion suggestion : suggestions.getList()) { String offer = suggestion.getText(); - Component tooltip = null; + ComponentHolder tooltip = null; if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) { - tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent(); + tooltip = new ComponentHolder(player.getProtocolVersion(), + ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); } offers.add(new Offer(offer, tooltip)); } @@ -698,10 +698,11 @@ private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteRes if (legacy && offer.startsWith(command)) { offer = offer.substring(command.length()); } - Component tooltip = null; + ComponentHolder tooltip = null; if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) { - tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent(); + tooltip = new ComponentHolder(player.getProtocolVersion(), + ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); } response.getOffers().add(new Offer(offer, tooltip)); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 2ffeb13b57..0e78c3d135 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -58,7 +58,6 @@ import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; -import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.packet.ClientSettings; @@ -69,6 +68,7 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.config.StartUpdate; @@ -85,6 +85,7 @@ import io.netty.buffer.Unpooled; import java.net.InetSocketAddress; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -103,7 +104,6 @@ import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.title.Title.Times; @@ -173,8 +173,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private final UUID offlineUuid; ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, - @Nullable InetSocketAddress virtualHost, boolean onlineMode, - @Nullable IdentifiedKey playerKey) { + @Nullable InetSocketAddress virtualHost, boolean onlineMode, + @Nullable IdentifiedKey playerKey) { this.server = server; this.profile = profile; this.connection = connection; @@ -294,12 +294,14 @@ public boolean hasSentPlayerSettings() { return settings != null; } - public void setClientSettingsPacket(ClientSettings clientSettingsPacket) { + /** + * Sets player settings. + * + * @param clientSettingsPacket the player settings packet + */ + public void setClientSettings(final ClientSettings clientSettingsPacket) { this.clientSettingsPacket = clientSettingsPacket; - } - - void setPlayerSettings(ClientSettings settings) { - ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); + final ClientSettingsWrapper cs = new ClientSettingsWrapper(clientSettingsPacket); this.settings = cs; server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs)); } @@ -365,7 +367,7 @@ public void sendMessage(@NonNull Identity identity, @NonNull Component message) @Override public void sendMessage(@NonNull Identity identity, @NonNull Component message, - @NonNull MessageType type) { + @NonNull MessageType type) { Preconditions.checkNotNull(message, "message"); Preconditions.checkNotNull(type, "type"); @@ -386,8 +388,7 @@ public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) { // Use the title packet instead. GenericTitlePacket pkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_ACTION_BAR, playerVersion); - pkt.setComponent(ProtocolUtils.getJsonChatSerializer(playerVersion) - .serialize(translated)); + pkt.setComponent(new ComponentHolder(playerVersion, translated)); connection.write(pkt); } else { // Due to issues with action bar packets, we'll need to convert the text message into a @@ -436,8 +437,6 @@ public void sendPlayerListHeaderAndFooter(final Component header, final Componen @Override public void showTitle(net.kyori.adventure.title.@NonNull Title title) { if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { - GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this - .getProtocolVersion()); GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion()); net.kyori.adventure.title.Title.Times times = title.times(); @@ -450,12 +449,14 @@ public void showTitle(net.kyori.adventure.title.@NonNull Title title) { GenericTitlePacket subtitlePkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion()); - subtitlePkt.setComponent(serializer.serialize(translateMessage(title.subtitle()))); + subtitlePkt.setComponent(new ComponentHolder( + this.getProtocolVersion(), translateMessage(title.subtitle()))); connection.delayedWrite(subtitlePkt); GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion()); - titlePkt.setComponent(serializer.serialize(translateMessage(title.title()))); + titlePkt.setComponent(new ComponentHolder( + this.getProtocolVersion(), translateMessage(title.title()))); connection.delayedWrite(titlePkt); connection.flush(); @@ -475,18 +476,17 @@ public void sendTitlePart(@NotNull TitlePart part, @NotNull T value) { return; } - GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this - .getProtocolVersion()); - if (part == TitlePart.TITLE) { GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion()); - titlePkt.setComponent(serializer.serialize(translateMessage((Component) value))); + titlePkt.setComponent(new ComponentHolder( + this.getProtocolVersion(), translateMessage((Component) value))); connection.write(titlePkt); } else if (part == TitlePart.SUBTITLE) { GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion()); - titlePkt.setComponent(serializer.serialize(translateMessage((Component) value))); + titlePkt.setComponent(new ComponentHolder( + this.getProtocolVersion(), translateMessage((Component) value))); connection.write(titlePkt); } else if (part == TitlePart.TIMES) { Times times = (Times) value; @@ -555,7 +555,7 @@ public void setGameProfileProperties(List properties) { public void clearPlayerListHeaderAndFooter() { clearPlayerListHeaderAndFooterSilent(); if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { - this.connection.write(HeaderAndFooter.reset()); + this.connection.write(HeaderAndFooter.reset(this.getProtocolVersion())); } } @@ -591,7 +591,7 @@ public void disconnect0(Component reason, boolean duringLogin) { logger.info("{} has disconnected: {}", this, LegacyComponentSerializer.legacySection().serialize(translated)); } - connection.closeWith(Disconnect.create(translated, this.getProtocolVersion())); + connection.closeWith(Disconnect.create(translated, this.getProtocolVersion(), duringLogin)); } public @Nullable VelocityServerConnection getConnectedServer() { @@ -614,7 +614,7 @@ public void resetInFlightConnection() { * @param safe whether or not we can safely reconnect to a new server */ public void handleConnectionException(RegisteredServer server, Throwable throwable, - boolean safe) { + boolean safe) { if (!isActive()) { // If the connection is no longer active, it makes no sense to try and recover it. return; @@ -653,13 +653,13 @@ public void handleConnectionException(RegisteredServer server, Throwable throwab * @param safe whether or not we can safely reconnect to a new server */ public void handleConnectionException(RegisteredServer server, Disconnect disconnect, - boolean safe) { + boolean safe) { if (!isActive()) { // If the connection is no longer active, it makes no sense to try and recover it. return; } - Component disconnectReason = GsonComponentSerializer.gson().deserialize(disconnect.getReason()); + Component disconnectReason = disconnect.getReason().getComponent(); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), @@ -679,7 +679,8 @@ public void handleConnectionException(RegisteredServer server, Disconnect discon } private void handleConnectionException(RegisteredServer rs, - @Nullable Component kickReason, Component friendlyReason, boolean safe) { + @Nullable Component kickReason, Component friendlyReason, + boolean safe) { if (!isActive()) { // If the connection is no longer active, it makes no sense to try and recover it. return; @@ -761,7 +762,8 @@ private void handleKickEvent(KickedFromServerEvent originalEvent, Component frie Component reason = status.getReasonComponent() .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); handleConnectionException(res.getServer(), - Disconnect.create(reason, getProtocolVersion()), ((Impl) status).isSafe()); + Disconnect.create(reason, getProtocolVersion(), false), + ((Impl) status).isSafe()); break; case SUCCESS: Component requestedMessage = res.getMessageComponent(); @@ -1018,6 +1020,7 @@ private void tickResourcePackQueue() { } ResourcePackRequest request = new ResourcePackRequest(); + request.setId(queued.getId()); request.setUrl(queued.getUrl()); if (queued.getHash() != null) { request.setHash(ByteBufUtil.hexDump(queued.getHash())); @@ -1025,22 +1028,37 @@ private void tickResourcePackQueue() { request.setHash(""); } request.setRequired(queued.getShouldForce()); - request.setPrompt(queued.getPrompt()); + request.setPrompt(queued.getPrompt() == null ? null : + new ComponentHolder(getProtocolVersion(), queued.getPrompt())); connection.write(request); } } @Override + @Deprecated public @Nullable ResourcePackInfo getAppliedResourcePack() { + //TODO which resource pack should be returned here? return appliedResourcePack; } @Override + @Deprecated public @Nullable ResourcePackInfo getPendingResourcePack() { + //TODO which resource pack should be returned here? return pendingResourcePack; } + @Override + public Collection getAppliedResourcePacks() { + return Collections.EMPTY_LIST; //TODO + } + + @Override + public Collection getPendingResourcePacks() { + return Collections.EMPTY_LIST; //TODO + } + /** * Clears the applied resource pack field. */ @@ -1052,7 +1070,7 @@ public void clearAppliedResourcePack() { * Processes a client response to a sent resource-pack. */ public boolean onResourcePackResponse(PlayerResourcePackStatusEvent.Status status) { - final boolean peek = status == PlayerResourcePackStatusEvent.Status.ACCEPTED; + final boolean peek = status.isIntermediate(); final ResourcePackInfo queued = peek ? outstandingResourcePacks.peek() : outstandingResourcePacks.poll(); @@ -1099,6 +1117,7 @@ public boolean onResourcePackResponse(PlayerResourcePackStatusEvent.Status statu * Gives an indication about the previous resource pack responses. */ public @Nullable Boolean getPreviousResourceResponse() { + //TODO can probably be removed return previousResourceResponse; } @@ -1108,7 +1127,7 @@ public boolean onResourcePackResponse(PlayerResourcePackStatusEvent.Status statu */ public void sendKeepAlive() { if (connection.getState() == StateRegistry.PLAY - || connection.getState() == StateRegistry.CONFIG) { + || connection.getState() == StateRegistry.CONFIG) { KeepAlive keepAlive = new KeepAlive(); keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); connection.write(keepAlive); @@ -1122,7 +1141,7 @@ public void switchToConfigState() { CompletableFuture.runAsync(() -> { connection.write(new StartUpdate()); connection.getChannel().pipeline() - .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); + .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); // Make sure we don't send any play packets to the player after update start connection.addPlayPacketQueueHandler(); }, connection.eventLoop()).exceptionally((ex) -> { @@ -1169,7 +1188,7 @@ private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final @Nullable VelocityRegisteredServer previousServer; ConnectionRequestBuilderImpl(RegisteredServer toConnect, - @Nullable VelocityServerConnection previousConnection) { + @Nullable VelocityServerConnection previousConnection) { this.toConnect = Preconditions.checkNotNull(toConnect, "info"); this.previousServer = previousConnection == null ? null : previousConnection.getServer(); } @@ -1268,8 +1287,8 @@ public CompletableFuture connectWithIndication() { case SERVER_DISCONNECTED: Component reason = status.getReasonComponent() .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); - handleConnectionException(toConnect, Disconnect.create(reason, getProtocolVersion()), - status.isSafe()); + handleConnectionException(toConnect, + Disconnect.create(reason, getProtocolVersion(), false), status.isSafe()); break; default: // The only remaining value is successful (no need to do anything!) @@ -1283,4 +1302,4 @@ public void fireAndForget() { connectWithIndication(); } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java index ecb1cc4285..2af8c3961a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -47,7 +47,7 @@ public final class InitialInboundConnection implements VelocityInboundConnection private final Handshake handshake; InitialInboundConnection(MinecraftConnection connection, String cleanedAddress, - Handshake handshake) { + Handshake handshake) { this.connection = connection; this.cleanedAddress = cleanedAddress; this.handshake = handshake; @@ -95,7 +95,7 @@ public void disconnect(Component reason) { logger.info("{} has disconnected: {}", this, LegacyComponentSerializer.legacySection().serialize(translated)); } - connection.closeWith(Disconnect.create(translated, getProtocolVersion())); + connection.closeWith(Disconnect.create(translated, getProtocolVersion(), true)); } /** @@ -106,6 +106,6 @@ public void disconnect(Component reason) { public void disconnectQuietly(Component reason) { Component translated = GlobalTranslator.render(reason, ClosestLocaleMatcher.INSTANCE .lookupClosest(Locale.getDefault())); - connection.closeWith(Disconnect.create(translated, getProtocolVersion())); + connection.closeWith(Disconnect.create(translated, getProtocolVersion(), true)); } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java index 134aee180d..26a46c1b42 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java @@ -19,6 +19,8 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.player.ResourcePackInfo; +import java.nio.charset.StandardCharsets; +import java.util.UUID; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; @@ -27,6 +29,7 @@ */ public final class VelocityResourcePackInfo implements ResourcePackInfo { + private final UUID id; private final String url; private final @Nullable byte[] hash; private final boolean shouldForce; @@ -34,8 +37,9 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { private final Origin origin; private Origin originalOrigin; - private VelocityResourcePackInfo(String url, @Nullable byte[] hash, boolean shouldForce, - @Nullable Component prompt, Origin origin) { + private VelocityResourcePackInfo(UUID id, String url, @Nullable byte[] hash, boolean shouldForce, + @Nullable Component prompt, Origin origin) { + this.id = id; this.url = url; this.hash = hash; this.shouldForce = shouldForce; @@ -44,6 +48,11 @@ private VelocityResourcePackInfo(String url, @Nullable byte[] hash, boolean shou this.originalOrigin = origin; } + @Override + public UUID getId() { + return id; + } + @Override public String getUrl() { return url; @@ -81,6 +90,7 @@ public Origin getOriginalOrigin() { @Override public Builder asBuilder() { return new BuilderImpl(url) + .setId(id) .setShouldForce(shouldForce) .setHash(hash) .setPrompt(prompt); @@ -89,6 +99,7 @@ public Builder asBuilder() { @Override public Builder asBuilder(String newUrl) { return new BuilderImpl(newUrl) + .setId(id) .setShouldForce(shouldForce) .setHash(hash) .setPrompt(prompt); @@ -99,6 +110,7 @@ public Builder asBuilder(String newUrl) { */ public static final class BuilderImpl implements ResourcePackInfo.Builder { + private UUID id; private final String url; private boolean shouldForce; private @Nullable byte[] hash; @@ -107,6 +119,13 @@ public static final class BuilderImpl implements ResourcePackInfo.Builder { public BuilderImpl(String url) { this.url = Preconditions.checkNotNull(url, "url"); + this.id = UUID.nameUUIDFromBytes(url.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public BuilderImpl setId(UUID id) { + this.id = id; + return this; } @Override @@ -134,7 +153,7 @@ public BuilderImpl setPrompt(@Nullable Component prompt) { @Override public ResourcePackInfo build() { - return new VelocityResourcePackInfo(url, hash, shouldForce, prompt, origin); + return new VelocityResourcePackInfo(id, url, hash, shouldForce, prompt, origin); } public BuilderImpl setOrigin(Origin origin) { @@ -143,4 +162,4 @@ public BuilderImpl setOrigin(Origin origin) { } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java deleted file mode 100644 index 1bca74515b..0000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2018-2023 Velocity 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 com.velocitypowered.proxy.connection.registry; - -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.protocol.packet.config.RegistrySync; -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.Nullable; - -/** - * Holds the registry data that is sent - * to the client during the config stage. - */ -public class ClientConfigData { - - private final @Nullable VelocityResourcePackInfo resourcePackInfo; - private final DataTag tag; - private final RegistrySync registry; - private final Key[] features; - private final String brand; - - private ClientConfigData(@Nullable VelocityResourcePackInfo resourcePackInfo, DataTag tag, - RegistrySync registry, Key[] features, String brand) { - this.resourcePackInfo = resourcePackInfo; - this.tag = tag; - this.registry = registry; - this.features = features; - this.brand = brand; - } - - public RegistrySync getRegistry() { - return registry; - } - - public DataTag getTag() { - return tag; - } - - public Key[] getFeatures() { - return features; - } - - public @Nullable VelocityResourcePackInfo getResourcePackInfo() { - return resourcePackInfo; - } - - public String getBrand() { - return brand; - } - - /** - * Creates a new builder. - * - * @return ClientConfigData.Builder - */ - public static ClientConfigData.Builder builder() { - return new Builder(); - } - - /** - * Builder for ClientConfigData. - */ - public static class Builder { - private VelocityResourcePackInfo resourcePackInfo; - private DataTag tag; - private RegistrySync registry; - private Key[] features; - private String brand; - - private Builder() { - } - - /** - * Clears the builder. - */ - public void clear() { - this.resourcePackInfo = null; - this.tag = null; - this.registry = null; - this.features = null; - this.brand = null; - } - - public Builder resourcePack(@Nullable VelocityResourcePackInfo resourcePackInfo) { - this.resourcePackInfo = resourcePackInfo; - return this; - } - - public Builder dataTag(DataTag tag) { - this.tag = tag; - return this; - } - - public Builder registry(RegistrySync registry) { - this.registry = registry; - return this; - } - - public Builder features(Key[] features) { - this.features = features; - return this; - } - - public Builder brand(String brand) { - this.brand = brand; - return this; - } - - public ClientConfigData build() { - return new ClientConfigData(resourcePackInfo, tag, registry, features, brand); - } - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java deleted file mode 100644 index 9a7d0de737..0000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2019-2023 Velocity 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 com.velocitypowered.proxy.connection.registry; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.key.Keyed; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a data tag. - */ -public class DataTag { - private final ImmutableList entrySets; - - public DataTag(ImmutableList entrySets) { - this.entrySets = entrySets; - } - - /** - * Returns the entry sets. - * - * @return List of entry sets - */ - public List getEntrySets() { - return entrySets; - } - - /** - * Represents a data tag set. - */ - public static class Set implements Keyed { - - private final Key key; - private final ImmutableList entries; - - public Set(Key key, ImmutableList entries) { - this.key = key; - this.entries = entries; - } - - /** - * Returns the entries. - * - * @return List of entries - */ - public List getEntries() { - return entries; - } - - @Override - public @NotNull Key key() { - return key; - } - } - - /** - * Represents a data tag entry. - */ - public static class Entry implements Keyed { - - private final Key key; - private final int[] elements; - - public Entry(Key key, int[] elements) { - this.key = key; - this.elements = elements; - } - - public int[] getElements() { - return elements; - } - - @Override - public @NotNull Key key() { - return key; - } - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java index 7ef1601e18..a6c8824db5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java @@ -24,7 +24,6 @@ import java.util.Optional; import javax.annotation.Nullable; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; /** * Common connection request results. @@ -64,13 +63,12 @@ public static Impl forDisconnect(Component component, RegisteredServer server) { } public static Impl forDisconnect(Disconnect disconnect, RegisteredServer server) { - Component deserialized = GsonComponentSerializer.gson().deserialize(disconnect.getReason()); - return forDisconnect(deserialized, server); + return forDisconnect(disconnect.getReason().getComponent(), server); } public static Impl forUnsafeDisconnect(Disconnect disconnect, RegisteredServer server) { - Component deserialized = GsonComponentSerializer.gson().deserialize(disconnect.getReason()); - return new Impl(Status.SERVER_DISCONNECTED, deserialized, server, false); + return new Impl(Status.SERVER_DISCONNECTED, disconnect.getReason().getComponent(), server, + false); } /** @@ -115,4 +113,4 @@ public boolean isSafe() { return safe; } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index e65ee056b2..04a609e93c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -34,17 +34,20 @@ import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; -import java.io.DataInput; -import java.io.DataOutput; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.json.JSONOptions; +import net.kyori.option.OptionState; /** * Utilities for writing and reading data in the Minecraft protocol. @@ -57,13 +60,55 @@ public enum ProtocolUtils { .downsampleColors() .emitLegacyHoverEvent() .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) + .options( + OptionState.optionState() + // before 1.16 + .value(JSONOptions.EMIT_RGB, Boolean.FALSE) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.LEGACY_ONLY) + // before 1.20.3 + .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) + .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE) + .build() + ) + .build(); + private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER = + GsonComponentSerializer.builder() + .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) + .options( + OptionState.optionState() + // after 1.16 + .value(JSONOptions.EMIT_RGB, Boolean.TRUE) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) + // before 1.20.3 + .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) + .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE) + .build() + ) .build(); private static final GsonComponentSerializer MODERN_SERIALIZER = GsonComponentSerializer.builder() .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) + .options( + OptionState.optionState() + // after 1.16 + .value(JSONOptions.EMIT_RGB, Boolean.TRUE) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) + // after 1.20.3 + .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE) + .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE) + .build() + ) .build(); public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB + private static final BinaryTagType[] BINARY_TAG_TYPES = new BinaryTagType[] { + BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT, + BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE, + BinaryTagTypes.BYTE_ARRAY, BinaryTagTypes.STRING, BinaryTagTypes.LIST, + BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY}; private static final QuietDecoderException BAD_VARINT_CACHED = new QuietDecoderException("Bad VarInt decoded"); private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; @@ -367,29 +412,57 @@ public static void writeUuidIntArray(ByteBuf buf, UUID uuid) { * Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}. * * @param buf the buffer to read from - * @param reader the NBT reader to use + * @param reader the {@link BinaryTagIO.Reader} to use * @return {@link net.kyori.adventure.nbt.CompoundBinaryTag} the CompoundTag from the buffer */ - public static CompoundBinaryTag readCompoundTag(ByteBuf buf, BinaryTagIO.Reader reader) { + public static CompoundBinaryTag readCompoundTag(ByteBuf buf, ProtocolVersion version, + BinaryTagIO.Reader reader) { + BinaryTag binaryTag = readBinaryTag(buf, version, reader); + if (binaryTag.type() != BinaryTagTypes.COMPOUND) { + throw new DecoderException( + "Expected root tag to be CompoundTag, but is " + binaryTag.getClass().getSimpleName()); + } + return (CompoundBinaryTag) binaryTag; + } + + /** + * Reads a {@link net.kyori.adventure.nbt.BinaryTag} from the {@code buf}. + * + * @param buf the buffer to read from + * @param reader the {@link BinaryTagIO.Reader} to use + * @return {@link net.kyori.adventure.nbt.BinaryTag} the BinaryTag from the buffer + */ + public static BinaryTag readBinaryTag(ByteBuf buf, ProtocolVersion version, + BinaryTagIO.Reader reader) { + BinaryTagType type = BINARY_TAG_TYPES[buf.readByte()]; + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + buf.skipBytes(buf.readUnsignedShort()); + } try { - return reader.read((DataInput) new ByteBufInputStream(buf)); + return type.read(new ByteBufInputStream(buf)); } catch (IOException thrown) { - throw new DecoderException( - "Unable to parse NBT CompoundTag, full error: " + thrown.getMessage()); + throw new DecoderException("Unable to parse BinaryTag, full error: " + thrown.getMessage()); } } /** - * Writes a CompoundTag to the {@code buf}. + * Writes a {@link net.kyori.adventure.nbt.BinaryTag} to the {@code buf}. * - * @param buf the buffer to write to - * @param compoundTag the CompoundTag to write + * @param buf the buffer to write to + * @param tag the BinaryTag to write */ - public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) { + public static void writeBinaryTag(ByteBuf buf, ProtocolVersion version, + T tag) { + BinaryTagType type = (BinaryTagType) tag.type(); + buf.writeByte(type.id()); try { - BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(buf)); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + // Empty name + buf.writeShort(0); + } + type.write(tag, new ByteBufOutputStream(buf)); } catch (IOException e) { - throw new EncoderException("Unable to encode NBT CompoundTag"); + throw new EncoderException("Unable to encode BinaryTag"); } } @@ -637,9 +710,12 @@ public static String readStringWithoutLength(ByteBuf buf) { * @return the appropriate {@link GsonComponentSerializer} */ public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { return MODERN_SERIALIZER; } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + return PRE_1_20_3_SERIALIZER; + } return PRE_1_16_SERIALIZER; } @@ -677,4 +753,4 @@ public enum Direction { SERVERBOUND, CLIENTBOUND } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 8ebb7eadcd..4a4b453f70 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -34,6 +34,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -62,6 +63,7 @@ import com.velocitypowered.proxy.protocol.packet.PingIdentify; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; @@ -107,7 +109,9 @@ import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; -/** Registry of all Minecraft protocol states and the packets for each state. */ +/** + * Registry of all Minecraft protocol states and the packets for each state. + */ public enum StateRegistry { HANDSHAKE { @@ -145,7 +149,8 @@ public enum StateRegistry { clientbound.register( PluginMessage.class, PluginMessage::new, map(0x00, MINECRAFT_1_20_2, false)); - clientbound.register(Disconnect.class, Disconnect::new, map(0x01, MINECRAFT_1_20_2, false)); + clientbound.register( + Disconnect.class, () -> new Disconnect(false), map(0x01, MINECRAFT_1_20_2, false)); clientbound.register( FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false)); clientbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false)); @@ -154,10 +159,16 @@ public enum StateRegistry { clientbound.register( RegistrySync.class, RegistrySync::new, map(0x05, MINECRAFT_1_20_2, false)); clientbound.register( - ResourcePackRequest.class, ResourcePackRequest::new, map(0x06, MINECRAFT_1_20_2, false)); - clientbound.register( - ActiveFeatures.class, ActiveFeatures::new, map(0x07, MINECRAFT_1_20_2, false)); - clientbound.register(TagsUpdate.class, TagsUpdate::new, map(0x08, MINECRAFT_1_20_2, false)); + RemoveResourcePack.class, RemoveResourcePack::new, map(0x06, MINECRAFT_1_20_3, false)); + clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, + map(0x06, MINECRAFT_1_20_2, false), + map(0x07, MINECRAFT_1_20_3, false)); + clientbound.register(ActiveFeatures.class, ActiveFeatures::new, + map(0x07, MINECRAFT_1_20_2, false), + map(0x08, MINECRAFT_1_20_3, false)); + clientbound.register(TagsUpdate.class, TagsUpdate::new, + map(0x08, MINECRAFT_1_20_2, false), + map(0x09, MINECRAFT_1_20_3, false)); } }, PLAY { @@ -186,9 +197,9 @@ public enum StateRegistry { map(0x02, MINECRAFT_1_12_1, false), map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false)); serverbound.register( - ChatAcknowledgement.class, - ChatAcknowledgement::new, - map(0x03, MINECRAFT_1_19_3, false)); + ChatAcknowledgement.class, + ChatAcknowledgement::new, + map(0x03, MINECRAFT_1_19_3, false)); serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new, map(0x03, MINECRAFT_1_19, false), map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); @@ -200,7 +211,7 @@ public enum StateRegistry { serverbound.register( SessionPlayerChat.class, SessionPlayerChat::new, - map(0x05, MINECRAFT_1_19_3, MINECRAFT_1_20_2, false)); + map(0x05, MINECRAFT_1_19_3, false)); serverbound.register( ClientSettings.class, ClientSettings::new, @@ -228,7 +239,8 @@ public enum StateRegistry { map(0x0D, MINECRAFT_1_19_1, false), map(0x0C, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_4, false), - map(0x0F, MINECRAFT_1_20_2, false)); + map(0x0F, MINECRAFT_1_20_2, false), + map(0x10, MINECRAFT_1_20_3, false)); serverbound.register( KeepAlive.class, KeepAlive::new, @@ -244,7 +256,8 @@ public enum StateRegistry { map(0x12, MINECRAFT_1_19_1, false), map(0x11, MINECRAFT_1_19_3, false), map(0x12, MINECRAFT_1_19_4, false), - map(0x14, MINECRAFT_1_20_2, false)); + map(0x14, MINECRAFT_1_20_2, false), + map(0x15, MINECRAFT_1_20_3, false)); serverbound.register( ResourcePackResponse.class, ResourcePackResponse::new, @@ -257,7 +270,8 @@ public enum StateRegistry { map(0x21, MINECRAFT_1_16_2, false), map(0x23, MINECRAFT_1_19, false), map(0x24, MINECRAFT_1_19_1, false), - map(0x27, MINECRAFT_1_20_2, false)); + map(0x27, MINECRAFT_1_20_2, false), + map(0x28, MINECRAFT_1_20_3, false)); serverbound.register( FinishedUpdate.class, FinishedUpdate::new, map(0x0B, MINECRAFT_1_20_2, false)); @@ -322,7 +336,7 @@ public enum StateRegistry { map(0x18, MINECRAFT_1_20_2, false)); clientbound.register( Disconnect.class, - Disconnect::new, + () -> new Disconnect(false), map(0x40, MINECRAFT_1_7_2, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), @@ -385,7 +399,12 @@ public enum StateRegistry { map(0x3E, MINECRAFT_1_19_1, true), map(0x3D, MINECRAFT_1_19_3, true), map(0x41, MINECRAFT_1_19_4, true), - map(0x43, MINECRAFT_1_20_2, true)); + map(0x43, MINECRAFT_1_20_2, true), + map(0x45, MINECRAFT_1_20_3, true)); + clientbound.register( + RemoveResourcePack.class, + RemoveResourcePack::new, + map(0x43, MINECRAFT_1_20_3, false)); clientbound.register( ResourcePackRequest.class, ResourcePackRequest::new, @@ -403,7 +422,8 @@ public enum StateRegistry { map(0x3D, MINECRAFT_1_19_1, false), map(0x3C, MINECRAFT_1_19_3, false), map(0x40, MINECRAFT_1_19_4, false), - map(0x42, MINECRAFT_1_20_2, false)); + map(0x42, MINECRAFT_1_20_2, false), + map(0x44, MINECRAFT_1_20_3, false)); clientbound.register( HeaderAndFooter.class, HeaderAndFooter::new, @@ -422,7 +442,8 @@ public enum StateRegistry { map(0x63, MINECRAFT_1_19_1, true), map(0x61, MINECRAFT_1_19_3, true), map(0x65, MINECRAFT_1_19_4, true), - map(0x68, MINECRAFT_1_20_2, true)); + map(0x68, MINECRAFT_1_20_2, true), + map(0x6A, MINECRAFT_1_20_3, true)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -440,7 +461,8 @@ public enum StateRegistry { map(0x5B, MINECRAFT_1_19_1, true), map(0x59, MINECRAFT_1_19_3, true), map(0x5D, MINECRAFT_1_19_4, true), - map(0x5F, MINECRAFT_1_20_2, true)); + map(0x5F, MINECRAFT_1_20_2, true), + map(0x61, MINECRAFT_1_20_3, true)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -449,7 +471,8 @@ public enum StateRegistry { map(0x5D, MINECRAFT_1_19_1, true), map(0x5B, MINECRAFT_1_19_3, true), map(0x5F, MINECRAFT_1_19_4, true), - map(0x61, MINECRAFT_1_20_2, true)); + map(0x61, MINECRAFT_1_20_2, true), + map(0x63, MINECRAFT_1_20_3, true)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -458,7 +481,8 @@ public enum StateRegistry { map(0x43, MINECRAFT_1_19_1, true), map(0x42, MINECRAFT_1_19_3, true), map(0x46, MINECRAFT_1_19_4, true), - map(0x48, MINECRAFT_1_20_2, true)); + map(0x48, MINECRAFT_1_20_2, true), + map(0x4A, MINECRAFT_1_20_3, true)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -467,7 +491,8 @@ public enum StateRegistry { map(0x5E, MINECRAFT_1_19_1, true), map(0x5C, MINECRAFT_1_19_3, true), map(0x60, MINECRAFT_1_19_4, true), - map(0x62, MINECRAFT_1_20_2, true)); + map(0x62, MINECRAFT_1_20_2, true), + map(0x64, MINECRAFT_1_20_3, true)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -507,7 +532,8 @@ public enum StateRegistry { map(0x62, MINECRAFT_1_19_1, true), map(0x60, MINECRAFT_1_19_3, true), map(0x64, MINECRAFT_1_19_4, true), - map(0x67, MINECRAFT_1_20_2, true)); + map(0x67, MINECRAFT_1_20_2, true), + map(0x69, MINECRAFT_1_20_3, true)); clientbound.register( PlayerChatCompletion.class, PlayerChatCompletion::new, @@ -522,8 +548,13 @@ public enum StateRegistry { map(0x42, MINECRAFT_1_19_1, false), map(0x41, MINECRAFT_1_19_3, false), map(0x45, MINECRAFT_1_19_4, false), - map(0x47, MINECRAFT_1_20_2, false)); - clientbound.register(StartUpdate.class, StartUpdate::new, map(0x65, MINECRAFT_1_20_2, false)); + map(0x47, MINECRAFT_1_20_2, false), + map(0x49, MINECRAFT_1_20_3, false)); + clientbound.register( + StartUpdate.class, + StartUpdate::new, + map(0x65, MINECRAFT_1_20_2, false), + map(0x67, MINECRAFT_1_20_3, false)); } }, LOGIN { @@ -536,7 +567,8 @@ public enum StateRegistry { serverbound.register( LoginAcknowledged.class, LoginAcknowledged::new, map(0x03, MINECRAFT_1_20_2, false)); - clientbound.register(Disconnect.class, Disconnect::new, map(0x00, MINECRAFT_1_7_2, false)); + clientbound.register( + Disconnect.class, () -> new Disconnect(true), map(0x00, MINECRAFT_1_7_2, false)); clientbound.register( EncryptionRequest.class, EncryptionRequest::new, map(0x01, MINECRAFT_1_7_2, false)); clientbound.register( @@ -558,7 +590,9 @@ public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(Directi return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version); } - /** Packet registry. */ + /** + * Packet registry. + */ public static class PacketRegistry { private final Direction direction; @@ -592,7 +626,7 @@ ProtocolRegistry getProtocolRegistry(final ProtocolVersion version) { }

void register(Class

clazz, Supplier

packetSupplier, - PacketMapping... mappings) { + PacketMapping... mappings) { if (mappings.length == 0) { throw new IllegalArgumentException("At least one mapping must be provided."); } @@ -656,7 +690,9 @@

void register(Class

clazz, Supplier

packetSupp } } - /** Protocol registry. */ + /** + * Protocol registry. + */ public class ProtocolRegistry { public final ProtocolVersion version; @@ -697,7 +733,7 @@ public int getPacketId(final MinecraftPacket packet) { throw new IllegalArgumentException(String.format( "Unable to find id for packet of type %s in %s protocol %s phase %s", packet.getClass().getName(), PacketRegistry.this.direction, - this.version, PacketRegistry.this.registry + this.version, PacketRegistry.this.registry )); } return id; @@ -715,7 +751,9 @@ public boolean containsPacket(final MinecraftPacket packet) { } } - /** Packet mapping. */ + /** + * Packet mapping. + */ public static final class PacketMapping { private final int id; @@ -724,7 +762,7 @@ public static final class PacketMapping { private final @Nullable ProtocolVersion lastValidProtocolVersion; PacketMapping(int id, ProtocolVersion protocolVersion, - ProtocolVersion lastValidProtocolVersion, boolean packetDecoding) { + ProtocolVersion lastValidProtocolVersion, boolean packetDecoding) { this.id = id; this.protocolVersion = protocolVersion; this.lastValidProtocolVersion = lastValidProtocolVersion; @@ -786,8 +824,8 @@ private static PacketMapping map(int id, ProtocolVersion version, boolean encode * @return PacketMapping with the provided arguments */ private static PacketMapping map(int id, ProtocolVersion version, - ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) { + ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) { return new PacketMapping(id, version, lastValidProtocolVersion, encodeOnly); } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java index 91f4bacffc..569fcac73e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java @@ -21,6 +21,7 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,7 +36,7 @@ public class BossBar implements MinecraftPacket { public static final int UPDATE_PROPERTIES = 5; private @Nullable UUID uuid; private int action; - private @Nullable String name; + private @Nullable ComponentHolder name; private float percent; private int color; private int overlay; @@ -60,11 +61,11 @@ public void setAction(int action) { this.action = action; } - public @Nullable String getName() { + public @Nullable ComponentHolder getName() { return name; } - public void setName(String name) { + public void setName(ComponentHolder name) { this.name = name; } @@ -119,7 +120,7 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi this.action = ProtocolUtils.readVarInt(buf); switch (action) { case ADD: - this.name = ProtocolUtils.readString(buf); + this.name = ComponentHolder.read(buf, version); this.percent = buf.readFloat(); this.color = ProtocolUtils.readVarInt(buf); this.overlay = ProtocolUtils.readVarInt(buf); @@ -131,7 +132,7 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi this.percent = buf.readFloat(); break; case UPDATE_NAME: - this.name = ProtocolUtils.readString(buf); + this.name = ComponentHolder.read(buf, version); break; case UPDATE_STYLE: this.color = ProtocolUtils.readVarInt(buf); @@ -157,7 +158,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (name == null) { throw new IllegalStateException("No name specified!"); } - ProtocolUtils.writeString(buf, name); + name.write(buf); buf.writeFloat(percent); ProtocolUtils.writeVarInt(buf, color); ProtocolUtils.writeVarInt(buf, overlay); @@ -172,7 +173,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (name == null) { throw new IllegalStateException("No name specified!"); } - ProtocolUtils.writeString(buf, name); + name.write(buf); break; case UPDATE_STYLE: ProtocolUtils.writeVarInt(buf, color); @@ -197,4 +198,4 @@ public static BossBar createRemovePacket(UUID id) { packet.setAction(REMOVE); return packet; } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java index cdc9d0d753..fd24b71b7c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java @@ -22,28 +22,33 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; public class Disconnect implements MinecraftPacket { - private @Nullable String reason; + private @Nullable ComponentHolder reason; + private final boolean login; - public Disconnect() { + public Disconnect(boolean login) { + this.login = login; } - public Disconnect(String reason) { + private Disconnect(boolean login, ComponentHolder reason) { + this.login = login; this.reason = Preconditions.checkNotNull(reason, "reason"); } - public String getReason() { + public ComponentHolder getReason() { if (reason == null) { throw new IllegalStateException("No reason specified"); } return reason; } - public void setReason(@Nullable String reason) { + public void setReason(@Nullable ComponentHolder reason) { this.reason = reason; } @@ -56,15 +61,12 @@ public String toString() { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - reason = ProtocolUtils.readString(buf); + reason = ComponentHolder.read(buf, login ? ProtocolVersion.MINECRAFT_1_20_2 : version); } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (reason == null) { - throw new IllegalStateException("No reason specified."); - } - ProtocolUtils.writeString(buf, reason); + getReason().write(buf); } @Override @@ -72,9 +74,8 @@ public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - public static Disconnect create(net.kyori.adventure.text.Component component, - ProtocolVersion version) { + public static Disconnect create(Component component, ProtocolVersion version, boolean login) { Preconditions.checkNotNull(component, "component"); - return new Disconnect(ProtocolUtils.getJsonChatSerializer(version).serialize(component)); + return new Disconnect(login, new ComponentHolder(login ? ProtocolVersion.MINECRAFT_1_20_2 : version, component)); } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java index 43e1ac7e87..e509dbe3cf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java @@ -17,39 +17,34 @@ package com.velocitypowered.proxy.protocol.packet; -import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; - import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; public class HeaderAndFooter implements MinecraftPacket { - private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}"; - private static final HeaderAndFooter RESET = new HeaderAndFooter(); - - private final String header; - private final String footer; + private final ComponentHolder header; + private final ComponentHolder footer; public HeaderAndFooter() { - this(EMPTY_COMPONENT, EMPTY_COMPONENT); + throw new UnsupportedOperationException("Decode is not implemented"); } - public HeaderAndFooter(String header, String footer) { + public HeaderAndFooter(ComponentHolder header, ComponentHolder footer) { this.header = Preconditions.checkNotNull(header, "header"); this.footer = Preconditions.checkNotNull(footer, "footer"); } - public String getHeader() { + public ComponentHolder getHeader() { return header; } - public String getFooter() { + public ComponentHolder getFooter() { return footer; } @@ -60,8 +55,8 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - writeString(buf, header); - writeString(buf, footer); + header.write(buf); + footer.write(buf); } @Override @@ -71,11 +66,12 @@ public boolean handle(MinecraftSessionHandler handler) { public static HeaderAndFooter create(Component header, Component footer, ProtocolVersion protocolVersion) { - GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(protocolVersion); - return new HeaderAndFooter(serializer.serialize(header), serializer.serialize(footer)); + return new HeaderAndFooter(new ComponentHolder(protocolVersion, header), + new ComponentHolder(protocolVersion, footer)); } - public static HeaderAndFooter reset() { - return RESET; + public static HeaderAndFooter reset(ProtocolVersion version) { + ComponentHolder empty = new ComponentHolder(version, Component.empty()); + return new HeaderAndFooter(empty, empty); } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index 938d1190aa..6c4b19e970 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -255,12 +255,12 @@ private void decode116Up(ByteBuf buf, ProtocolVersion version) { this.previousGamemode = buf.readByte(); this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); - this.registry = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); + this.registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); String dimensionIdentifier; String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); + this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); dimensionIdentifier = ProtocolUtils.readString(buf); } else { dimensionIdentifier = ProtocolUtils.readString(buf); @@ -390,10 +390,10 @@ private void encode116Up(ByteBuf buf, ProtocolVersion version) { buf.writeByte(previousGamemode); ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new)); - ProtocolUtils.writeCompoundTag(buf, this.registry); + ProtocolUtils.writeBinaryTag(buf, version, this.registry); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - ProtocolUtils.writeCompoundTag(buf, currentDimensionData); + ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); } else { ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java new file mode 100644 index 0000000000..966620de93 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018-2021 Velocity 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 com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class RemoveResourcePack implements MinecraftPacket { + + private UUID id; + + public RemoveResourcePack() { + } + + public RemoveResourcePack(UUID id) { + this.id = id; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (buf.readBoolean()) { + this.id = ProtocolUtils.readUuid(buf); + } + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + buf.writeBoolean(id != null); + + if (id != null) { + ProtocolUtils.writeUuid(buf, id); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java index c80b97c162..72832fbbaa 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java @@ -25,24 +25,33 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; import java.util.regex.Pattern; public class ResourcePackRequest implements MinecraftPacket { + private @MonotonicNonNull UUID id; // 1.20.3+ private @MonotonicNonNull String url; private @MonotonicNonNull String hash; private boolean isRequired; // 1.17+ - private @Nullable Component prompt; // 1.17+ + private @Nullable ComponentHolder prompt; // 1.17+ private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+ + public @Nullable UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + public @Nullable String getUrl() { return url; } @@ -67,22 +76,25 @@ public void setRequired(boolean required) { isRequired = required; } - public @Nullable Component getPrompt() { + public @Nullable ComponentHolder getPrompt() { return prompt; } - public void setPrompt(Component prompt) { + public void setPrompt(ComponentHolder prompt) { this.prompt = prompt; } @Override public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + this.id = ProtocolUtils.readUuid(buf); + } this.url = ProtocolUtils.readString(buf); this.hash = ProtocolUtils.readString(buf); if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { this.isRequired = buf.readBoolean(); if (buf.readBoolean()) { - this.prompt = GsonComponentSerializer.gson().deserialize(ProtocolUtils.readString(buf)); + this.prompt = ComponentHolder.read(buf, protocolVersion); } else { this.prompt = null; } @@ -91,6 +103,12 @@ public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer @Override public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + if (id == null) { + throw new IllegalStateException("Resource pack id not set yet!"); + } + ProtocolUtils.writeUuid(buf, id); + } if (url == null || hash == null) { throw new IllegalStateException("Packet not fully filled in yet!"); } @@ -100,7 +118,7 @@ public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer buf.writeBoolean(isRequired); if (prompt != null) { buf.writeBoolean(true); - ProtocolUtils.writeString(buf, GsonComponentSerializer.gson().serialize(prompt)); + prompt.write(buf); } else { buf.writeBoolean(false); } @@ -109,7 +127,8 @@ public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer public VelocityResourcePackInfo toServerPromptedPack() { ResourcePackInfo.Builder builder = - new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)).setPrompt(prompt) + new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)) + .setId(id).setPrompt(prompt == null ? null : prompt.getComponent()) .setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); if (hash != null && !hash.isEmpty()) { @@ -130,4 +149,4 @@ public String toString() { return "ResourcePackRequest{" + "url='" + url + '\'' + ", hash='" + hash + '\'' + ", isRequired=" + isRequired + ", prompt='" + prompt + '\'' + '}'; } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java index 4b67cf6954..ae79b82490 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java @@ -26,15 +26,19 @@ import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import java.util.UUID; + public class ResourcePackResponse implements MinecraftPacket { + private UUID id; private String hash = ""; private @MonotonicNonNull Status status; public ResourcePackResponse() { } - public ResourcePackResponse(String hash, @MonotonicNonNull Status status) { + public ResourcePackResponse(UUID id, String hash, @MonotonicNonNull Status status) { + this.id = id; this.hash = hash; this.status = status; } @@ -52,6 +56,9 @@ public String getHash() { @Override public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + this.id = ProtocolUtils.readUuid(buf); + } if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9_4) <= 0) { this.hash = ProtocolUtils.readString(buf); } @@ -60,6 +67,9 @@ public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer @Override public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + ProtocolUtils.writeUuid(buf, id); + } if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9_4) <= 0) { ProtocolUtils.writeString(buf, hash); } @@ -75,4 +85,4 @@ public boolean handle(MinecraftSessionHandler handler) { public String toString() { return "ResourcePackResponse{" + "hash=" + hash + ", " + "status=" + status + '}'; } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 712024f635..e302cfbb46 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -165,7 +165,7 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader()); + this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader()); dimensionIdentifier = ProtocolUtils.readString(buf); } else { dimensionIdentifier = ProtocolUtils.readString(buf); @@ -186,12 +186,10 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) { + this.dataToKeep = (byte) (buf.readBoolean() ? 1 : 0); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { this.dataToKeep = buf.readByte(); - } else if (buf.readBoolean()) { - this.dataToKeep = 1; - } else { - this.dataToKeep = 0; } } else { this.levelType = ProtocolUtils.readString(buf, 16); @@ -202,6 +200,9 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) { this.portalCooldown = ProtocolUtils.readVarInt(buf); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + this.dataToKeep = buf.readByte(); + } } @Override @@ -209,7 +210,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - ProtocolUtils.writeCompoundTag(buf, currentDimensionData); + ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); } else { ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); @@ -229,10 +230,10 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi buf.writeByte(previousGamemode); buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isFlat()); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { - buf.writeByte(dataToKeep); - } else { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) { buf.writeBoolean(dataToKeep != 0); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + buf.writeByte(dataToKeep); } } else { ProtocolUtils.writeString(buf, levelType); @@ -252,6 +253,10 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) { ProtocolUtils.writeVarInt(buf, portalCooldown); } + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + buf.writeByte(dataToKeep); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java index 4223c488f9..cbb7686746 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java @@ -22,23 +22,23 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; import java.nio.charset.StandardCharsets; import java.util.Base64; public class ServerData implements MinecraftPacket { - private @Nullable Component description; + private @Nullable ComponentHolder description; private @Nullable Favicon favicon; private boolean secureChatEnforced; // Added in 1.19.1 public ServerData() { } - public ServerData(@Nullable Component description, @Nullable Favicon favicon, - boolean secureChatEnforced) { + public ServerData(@Nullable ComponentHolder description, @Nullable Favicon favicon, + boolean secureChatEnforced) { this.description = description; this.favicon = favicon; this.secureChatEnforced = secureChatEnforced; @@ -48,8 +48,7 @@ public ServerData(@Nullable Component description, @Nullable Favicon favicon, public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_4) >= 0 || buf.readBoolean()) { - this.description = ProtocolUtils.getJsonChatSerializer(protocolVersion) - .deserialize(ProtocolUtils.readString(buf)); + this.description = ComponentHolder.read(buf, protocolVersion); } if (buf.readBoolean()) { String iconBase64; @@ -77,10 +76,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, buf.writeBoolean(hasDescription); } if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_4) >= 0 || hasDescription) { - ProtocolUtils.writeString( - buf, - ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.description) - ); + this.description.write(buf); } boolean hasFavicon = this.favicon != null; @@ -108,7 +104,7 @@ public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - public @Nullable Component getDescription() { + public @Nullable ComponentHolder getDescription() { return description; } @@ -119,4 +115,4 @@ public boolean handle(MinecraftSessionHandler handler) { public boolean isSecureChatEnforced() { return secureChatEnforced; } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java index 835c39a00c..f6a3aabbbe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java @@ -24,10 +24,10 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.List; -import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; public class TabCompleteResponse implements MinecraftPacket { @@ -84,8 +84,7 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi int offersAvailable = ProtocolUtils.readVarInt(buf); for (int i = 0; i < offersAvailable; i++) { String offer = ProtocolUtils.readString(buf); - Component tooltip = buf.readBoolean() ? ProtocolUtils.getJsonChatSerializer(version) - .deserialize(ProtocolUtils.readString(buf)) : null; + ComponentHolder tooltip = buf.readBoolean() ? ComponentHolder.read(buf, version) : null; offers.add(new Offer(offer, tooltip)); } } else { @@ -107,8 +106,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi ProtocolUtils.writeString(buf, offer.text); buf.writeBoolean(offer.tooltip != null); if (offer.tooltip != null) { - ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) - .serialize(offer.tooltip)); + offer.tooltip.write(buf); } } } else { @@ -127,14 +125,13 @@ public boolean handle(MinecraftSessionHandler handler) { public static class Offer implements Comparable { private final String text; - private final @Nullable Component tooltip; + private final @Nullable ComponentHolder tooltip; public Offer(String text) { this(text, null); } - public Offer(String text, - @Nullable Component tooltip) { + public Offer(String text, @Nullable ComponentHolder tooltip) { this.text = text; this.tooltip = tooltip; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java index a63eb69412..e323f5e8f1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java @@ -22,6 +22,7 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import io.netty.buffer.ByteBuf; import java.util.ArrayList; @@ -31,7 +32,6 @@ import java.util.EnumSet; import java.util.List; import java.util.UUID; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; public class UpsertPlayerInfo implements MinecraftPacket { @@ -179,16 +179,14 @@ public enum Action { }), UPDATE_DISPLAY_NAME((version, buf, info) -> { // read if (buf.readBoolean()) { - info.displayName = ProtocolUtils.getJsonChatSerializer(version) - .deserialize(ProtocolUtils.readString(buf)); + info.displayName = ComponentHolder.read(buf, version); } else { info.displayName = null; } }, (version, buf, info) -> { // write buf.writeBoolean(info.displayName != null); if (info.displayName != null) { - ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) - .serialize(info.displayName)); + info.displayName.write(buf); } }); @@ -219,7 +217,7 @@ public static class Entry { private int latency; private int gameMode; @Nullable - private Component displayName; + private ComponentHolder displayName; @Nullable private RemoteChatSession chatSession; @@ -248,7 +246,7 @@ public int getGameMode() { } @Nullable - public Component getDisplayName() { + public ComponentHolder getDisplayName() { return displayName; } @@ -273,7 +271,7 @@ public void setGameMode(int gameMode) { this.gameMode = gameMode; } - public void setDisplayName(@Nullable Component displayName) { + public void setDisplayName(@Nullable ComponentHolder displayName) { this.displayName = displayName; } @@ -294,4 +292,4 @@ public String toString() { '}'; } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index df877b0b56..cd8e5b57e8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -20,6 +20,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -206,54 +207,75 @@ public void serialize(BoolArgumentType object, ByteBuf buf, empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15))); empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16))); empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17))); - empty(id("minecraft:message", mapSet(MINECRAFT_1_19, 18))); - empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_19, 19))); // added in 1.14 - empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_19, 20))); // added in 1.14 - empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_19, 21))); - empty(id("minecraft:objective", mapSet(MINECRAFT_1_19, 22))); - empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_19, 23))); - empty(id("minecraft:operation", mapSet(MINECRAFT_1_19, 24))); - empty(id("minecraft:particle", mapSet(MINECRAFT_1_19, 25))); - empty(id("minecraft:angle", mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 - empty(id("minecraft:rotation", mapSet(MINECRAFT_1_19, 27))); - empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_19, 28))); - empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_19, 29)), + empty(id("minecraft:style", mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 + empty(id("minecraft:message", mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); + empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), + mapSet(MINECRAFT_1_19, 19))); // added in 1.14 + empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), + mapSet(MINECRAFT_1_19, 20))); // added in 1.14 + empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21))); + empty(id("minecraft:objective", mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22))); + empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), + mapSet(MINECRAFT_1_19, 23))); + empty(id("minecraft:operation", mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24))); + empty(id("minecraft:particle", mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25))); + empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), + mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 + empty(id("minecraft:rotation", mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27))); + empty( + id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); + empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE); - empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_19, 30))); - empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31))); - empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32))); - empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33))); + empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30))); + empty(id("minecraft:team", mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31))); + empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32))); + empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_3, 34), + mapSet(MINECRAFT_1_19, 33))); empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34))); - empty(id("minecraft:function", mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35))); - empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36))); - empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37))); - empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38))); + empty(id("minecraft:function", mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34), + mapSet(MINECRAFT_1_19, 35))); + empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35), + mapSet(MINECRAFT_1_19, 36))); + empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36), + mapSet(MINECRAFT_1_19, 37))); + empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37), + mapSet(MINECRAFT_1_19, 38))); empty( id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39))); empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40))); - empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41))); - empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 + empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38), + mapSet(MINECRAFT_1_19, 41))); + empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_3, 40), + mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 - empty(id("minecraft:time", mapSet(MINECRAFT_1_19_3, 40), + empty(id("minecraft:time", mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14 register( - id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)), + id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41), + mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_19_3, 42)), + register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_3, 43), + mapSet(MINECRAFT_1_19_3, 42)), RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY); - register(id("minecraft:resource", mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)), + register(id("minecraft:resource", mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43), + mapSet(MINECRAFT_1_19, 44)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register(id("minecraft:resource_key", mapSet(MINECRAFT_1_19_3, 44)), + register( + id("minecraft:resource_key", mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY); - empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_19, 45))); // 1.19 - empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19 - empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 + empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_3, 46), + mapSet(MINECRAFT_1_19, 45))); // 1.19 + empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_3, 47), + mapSet(MINECRAFT_1_19, 46))); // 1.19 + empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), + mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 - empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16 + empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), + mapSet(MINECRAFT_1_19, 47))); // added in 1.16 // Crossstitch support register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), @@ -261,4 +283,4 @@ public void serialize(BoolArgumentType object, ByteBuf buf, empty(id("minecraft:nbt")); // No longer in 1.19+ } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java new file mode 100644 index 0000000000..2466761548 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2019-2023 Velocity 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 com.velocitypowered.proxy.protocol.packet.chat; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.internal.LazilyParsedNumber; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.ByteArrayBinaryTag; +import net.kyori.adventure.nbt.ByteBinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.DoubleBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import net.kyori.adventure.nbt.FloatBinaryTag; +import net.kyori.adventure.nbt.IntArrayBinaryTag; +import net.kyori.adventure.nbt.IntBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.LongArrayBinaryTag; +import net.kyori.adventure.nbt.LongBinaryTag; +import net.kyori.adventure.nbt.ShortBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ComponentHolder { + private static final Logger logger = LogManager.getLogger(ComponentHolder.class); + + private final ProtocolVersion version; + private @MonotonicNonNull Component component; + private @MonotonicNonNull String json; + private @MonotonicNonNull BinaryTag binaryTag; + + public ComponentHolder(ProtocolVersion version, Component component) { + this.version = version; + this.component = component; + } + + public ComponentHolder(ProtocolVersion version, String json) { + this.version = version; + this.json = json; + } + + public ComponentHolder(ProtocolVersion version, BinaryTag binaryTag) { + this.version = version; + this.binaryTag = binaryTag; + } + + public Component getComponent() { + if (component == null) { + if (json != null) { + component = ProtocolUtils.getJsonChatSerializer(version).deserialize(json); + } else if (binaryTag != null) { + // TODO: replace this with adventure-text-serializer-nbt + try { + json = deserialize(binaryTag).toString(); + component = ProtocolUtils.getJsonChatSerializer(version).deserialize(json); + } catch (Exception ex) { + logger.error( + "Error converting binary component to JSON component! " + + "Binary: " + binaryTag + " JSON: " + json, ex); + throw ex; + } + } + } + return component; + } + + public String getJson() { + if (json == null) { + json = ProtocolUtils.getJsonChatSerializer(version).serialize(getComponent()); + } + return json; + } + + public BinaryTag getBinaryTag() { + if (binaryTag == null) { + // TODO: replace this with adventure-text-serializer-nbt + binaryTag = serialize(GsonComponentSerializer.gson().serializeToTree(getComponent())); + } + return binaryTag; + } + + public static BinaryTag serialize(JsonElement json) { + if (json instanceof JsonPrimitive) { + JsonPrimitive jsonPrimitive = (JsonPrimitive) json; + + if (jsonPrimitive.isNumber()) { + Number number = json.getAsNumber(); + + if (number instanceof Byte) { + return ByteBinaryTag.byteBinaryTag((Byte) number); + } else if (number instanceof Short) { + return ShortBinaryTag.shortBinaryTag((Short) number); + } else if (number instanceof Integer) { + return IntBinaryTag.intBinaryTag((Integer) number); + } else if (number instanceof Long) { + return LongBinaryTag.longBinaryTag((Long) number); + } else if (number instanceof Float) { + return FloatBinaryTag.floatBinaryTag((Float) number); + } else if (number instanceof Double) { + return DoubleBinaryTag.doubleBinaryTag((Double) number); + } else if (number instanceof LazilyParsedNumber) { + return IntBinaryTag.intBinaryTag(number.intValue()); + } + } else if (jsonPrimitive.isString()) { + return StringBinaryTag.stringBinaryTag(jsonPrimitive.getAsString()); + } else if (jsonPrimitive.isBoolean()) { + return ByteBinaryTag.byteBinaryTag((byte) (jsonPrimitive.getAsBoolean() ? 1 : 0)); + } else { + throw new IllegalArgumentException("Unknown JSON primitive: " + jsonPrimitive); + } + } else if (json instanceof JsonObject) { + CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder(); + + for (Map.Entry property : ((JsonObject) json).entrySet()) { + compound.put(property.getKey(), serialize(property.getValue())); + } + + return compound.build(); + } else if (json instanceof JsonArray) { + List jsonArray = ((JsonArray) json).asList(); + + if (jsonArray.isEmpty()) { + return ListBinaryTag.empty(); + } + + List tagItems = new ArrayList<>(jsonArray.size()); + BinaryTagType listType = null; + + for (JsonElement jsonEl : jsonArray) { + BinaryTag tag = serialize(jsonEl); + tagItems.add(tag); + + if (listType == null) { + listType = tag.type(); + } else if (listType != tag.type()) { + listType = BinaryTagTypes.COMPOUND; + } + } + + switch (listType.id()) { + case 1://BinaryTagTypes.BYTE: + byte[] bytes = new byte[jsonArray.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (Byte) jsonArray.get(i).getAsNumber(); + } + + return ByteArrayBinaryTag.byteArrayBinaryTag(bytes); + case 3://BinaryTagTypes.INT: + int[] ints = new int[jsonArray.size()]; + for (int i = 0; i < ints.length; i++) { + ints[i] = (Integer) jsonArray.get(i).getAsNumber(); + } + + return IntArrayBinaryTag.intArrayBinaryTag(ints); + case 4://BinaryTagTypes.LONG: + long[] longs = new long[jsonArray.size()]; + for (int i = 0; i < longs.length; i++) { + longs[i] = (Long) jsonArray.get(i).getAsNumber(); + } + + return LongArrayBinaryTag.longArrayBinaryTag(longs); + case 10://BinaryTagTypes.COMPOUND: + tagItems.replaceAll(tag -> { + if (tag.type() == BinaryTagTypes.COMPOUND) { + return tag; + } else { + return CompoundBinaryTag.builder().put("", tag).build(); + } + }); + break; + } + + return ListBinaryTag.listBinaryTag(listType, tagItems); + } + + return EndBinaryTag.endBinaryTag(); + } + + public static JsonElement deserialize(BinaryTag tag) { + switch (tag.type().id()) { + case 1://BinaryTagTypes.BYTE: + return new JsonPrimitive(((ByteBinaryTag) tag).value()); + case 2://BinaryTagTypes.SHORT: + return new JsonPrimitive(((ShortBinaryTag) tag).value()); + case 3://BinaryTagTypes.INT: + return new JsonPrimitive(((IntBinaryTag) tag).value()); + case 4://BinaryTagTypes.LONG: + return new JsonPrimitive(((LongBinaryTag) tag).value()); + case 5://BinaryTagTypes.FLOAT: + return new JsonPrimitive(((FloatBinaryTag) tag).value()); + case 6://BinaryTagTypes.DOUBLE: + return new JsonPrimitive(((DoubleBinaryTag) tag).value()); + case 7://BinaryTagTypes.BYTE_ARRAY: + byte[] byteArray = ((ByteArrayBinaryTag) tag).value(); + + JsonArray jsonByteArray = new JsonArray(byteArray.length); + for (byte b : byteArray) { + jsonByteArray.add(new JsonPrimitive(b)); + } + + return jsonByteArray; + case 8://BinaryTagTypes.STRING: + return new JsonPrimitive(((StringBinaryTag) tag).value()); + case 9://BinaryTagTypes.LIST: + ListBinaryTag items = (ListBinaryTag) tag; + JsonArray jsonList = new JsonArray(items.size()); + + for (BinaryTag subTag : items) { + jsonList.add(deserialize(subTag)); + } + + return jsonList; + case 10://BinaryTagTypes.COMPOUND: + CompoundBinaryTag compound = (CompoundBinaryTag) tag; + JsonObject jsonObject = new JsonObject(); + + compound.keySet().forEach(key -> { + // [{"text":"test1"},"test2"] can't be represented as a binary list tag + // it is represented by a list tag with two compound tags + // the second compound tag will have an empty key mapped to "test2" + // without this fix this would lead to an invalid json component: + // [{"text":"test1"},{"":"test2"}] + jsonObject.add(key.isEmpty() ? "text" : key, deserialize(compound.get(key))); + }); + + return jsonObject; + case 11://BinaryTagTypes.INT_ARRAY: + int[] intArray = ((IntArrayBinaryTag) tag).value(); + + JsonArray jsonIntArray = new JsonArray(intArray.length); + for (int i : intArray) { + jsonIntArray.add(new JsonPrimitive(i)); + } + + return jsonIntArray; + case 12://BinaryTagTypes.LONG_ARRAY: + long[] longArray = ((LongArrayBinaryTag) tag).value(); + + JsonArray jsonLongArray = new JsonArray(longArray.length); + for (long l : longArray) { + jsonLongArray.add(new JsonPrimitive(l)); + } + + return jsonLongArray; + default: + throw new IllegalArgumentException("Unknown NBT tag: " + tag); + } + } + + public static ComponentHolder read(ByteBuf buf, ProtocolVersion version) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + return new ComponentHolder(version, + ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader())); + } else { + return new ComponentHolder(version, ProtocolUtils.readString(buf)); + } + } + + public void write(ByteBuf buf) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + ProtocolUtils.writeBinaryTag(buf, version, getBinaryTag()); + } else { + ProtocolUtils.writeString(buf, getJson()); + } + } +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java index 48b19450f1..8ba129a5eb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java @@ -22,44 +22,39 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import net.kyori.adventure.text.Component; public class SystemChat implements MinecraftPacket { public SystemChat() { } - public SystemChat(Component component, ChatType type) { + public SystemChat(ComponentHolder component, ChatType type) { this.component = component; this.type = type; } - private Component component; + private ComponentHolder component; private ChatType type; public ChatType getType() { return type; } - public Component getComponent() { + public ComponentHolder getComponent() { return component; } @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - component = ProtocolUtils.getJsonChatSerializer(protocolVersion) - .deserialize(ProtocolUtils.readString(buf)); + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + component = ComponentHolder.read(buf, version); // System chat is never decoded so this doesn't matter for now type = ChatType.values()[ProtocolUtils.readVarInt(buf)]; } @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, - ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(component)); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + component.write(buf); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { switch (type) { case SYSTEM: buf.writeBoolean(false); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java index 830862cabc..8d44ba2814 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2; import net.kyori.adventure.text.Component; @@ -35,7 +36,7 @@ public KeyedChatBuilder(ProtocolVersion version) { public MinecraftPacket toClient() { // This is temporary Component msg = component == null ? Component.text(message) : component; - return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type); + return new SystemChat(new ComponentHolder(version, msg), type == ChatType.CHAT ? ChatType.SYSTEM : type); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java index 74bd945f8f..29d3715103 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java @@ -20,6 +20,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2; @@ -35,7 +36,7 @@ public SessionChatBuilder(ProtocolVersion version) { public MinecraftPacket toClient() { // This is temporary Component msg = component == null ? Component.text(message) : component; - return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type); + return new SystemChat(new ComponentHolder(version, msg), type == ChatType.CHAT ? ChatType.SYSTEM : type); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java index 02b125ab89..95a55a4e0b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java @@ -20,6 +20,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; public abstract class GenericTitlePacket implements MinecraftPacket { @@ -55,11 +56,11 @@ public final ActionType getAction() { return action; } - public String getComponent() { + public ComponentHolder getComponent() { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } - public void setComponent(String component) { + public void setComponent(ComponentHolder component) { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java index ae4bebeeb0..904671903f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java @@ -20,12 +20,13 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; public class LegacyTitlePacket extends GenericTitlePacket { - private @Nullable String component; + private @Nullable ComponentHolder component; private int fadeIn; private int stay; private int fadeOut; @@ -45,7 +46,7 @@ && getAction() == ActionType.SET_ACTION_BAR) { if (component == null) { throw new IllegalStateException("No component found for " + getAction()); } - ProtocolUtils.writeString(buf, component); + component.write(buf); break; case SET_TIMES: buf.writeInt(fadeIn); @@ -67,12 +68,12 @@ public void setAction(ActionType action) { } @Override - public @Nullable String getComponent() { + public @Nullable ComponentHolder getComponent() { return component; } @Override - public void setComponent(@Nullable String component) { + public void setComponent(@Nullable ComponentHolder component) { this.component = component; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java index a9f01245ac..f34983eaf9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java @@ -20,11 +20,12 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; public class TitleActionbarPacket extends GenericTitlePacket { - private String component; + private ComponentHolder component; public TitleActionbarPacket() { setAction(ActionType.SET_TITLE); @@ -32,16 +33,16 @@ public TitleActionbarPacket() { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeString(buf, component); + component.write(buf); } @Override - public String getComponent() { + public ComponentHolder getComponent() { return component; } @Override - public void setComponent(String component) { + public void setComponent(ComponentHolder component) { this.component = component; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java index d92a44d537..0f375ae20a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java @@ -20,11 +20,12 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; public class TitleSubtitlePacket extends GenericTitlePacket { - private String component; + private ComponentHolder component; public TitleSubtitlePacket() { setAction(ActionType.SET_SUBTITLE); @@ -32,16 +33,16 @@ public TitleSubtitlePacket() { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeString(buf, component); + component.write(buf); } @Override - public String getComponent() { + public ComponentHolder getComponent() { return component; } @Override - public void setComponent(String component) { + public void setComponent(ComponentHolder component) { this.component = component; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java index 8ad9b4b8b3..ae75f5d618 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java @@ -20,11 +20,12 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; public class TitleTextPacket extends GenericTitlePacket { - private String component; + private ComponentHolder component; public TitleTextPacket() { setAction(ActionType.SET_TITLE); @@ -32,16 +33,16 @@ public TitleTextPacket() { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeString(buf, component); + component.write(buf); } @Override - public String getComponent() { + public ComponentHolder getComponent() { return component; } @Override - public void setComponent(String component) { + public void setComponent(ComponentHolder component) { this.component = component; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java index 4ed43e86b1..ae93120aaf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java @@ -33,7 +33,7 @@ /** * Session handler used to implement {@link VelocityRegisteredServer#ping(EventLoop, - * ProtocolVersion)}. + * com.velocitypowered.api.proxy.server.PingOptions)}. */ public class PingSessionHandler implements MinecraftSessionHandler { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index 928b4ee726..837c46a252 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -124,9 +124,9 @@ protected void initChannel(Channel ch) throws Exception { }).connect(serverInfo.getAddress()).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); - conn.setActiveSessionHandler(StateRegistry.HANDSHAKE, - new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn, - pingOptions.getProtocolVersion())); + PingSessionHandler handler = new PingSessionHandler(pingFuture, + VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion()); + conn.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler); } else { pingFuture.completeExceptionally(future.cause()); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java index dcf00ecaee..25d82d87a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.tablist; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; @@ -27,6 +28,8 @@ */ public interface InternalTabList extends TabList { + Player getPlayer(); + default void processLegacy(LegacyPlayerListItem packet) { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java index e24c337ed0..69080af479 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java @@ -59,6 +59,11 @@ public KeyedVelocityTabList(final ConnectedPlayer player, final ProxyServer prox this.connection = player.getConnection(); } + @Override + public Player getPlayer() { + return player; + } + @Deprecated @Override public void setHeaderAndFooter(Component header, Component footer) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index 4a95b00ded..b207ce4bf3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -19,6 +19,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.player.ChatSession; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; @@ -27,6 +28,7 @@ import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import java.util.ArrayList; import java.util.Collection; @@ -64,6 +66,11 @@ public VelocityTabList(ConnectedPlayer player) { this.entries = Maps.newHashMap(); } + @Override + public Player getPlayer() { + return player; + } + @Override public void setHeaderAndFooter(Component header, Component footer) { Preconditions.checkNotNull(header, "header"); @@ -103,7 +110,8 @@ public void addEntry(TabListEntry entry1) { if (!Objects.equals(previousEntry.getDisplayNameComponent().orElse(null), entry.getDisplayNameComponent().orElse(null))) { actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME); - playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get()); + playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(), + entry.getDisplayNameComponent().get())); } if (!Objects.equals(previousEntry.getLatency(), entry.getLatency())) { actions.add(UpsertPlayerInfo.Action.UPDATE_LATENCY); @@ -132,7 +140,8 @@ public void addEntry(TabListEntry entry1) { playerInfoEntry.setProfile(entry.getProfile()); if (entry.getDisplayNameComponent().isPresent()) { actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME); - playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get()); + playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(), + entry.getDisplayNameComponent().get())); } if (entry.getChatSession() != null) { actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT); @@ -243,7 +252,8 @@ private void processUpsert(EnumSet actions, currentEntry.setLatencyWithoutUpdate(entry.getLatency()); } if (actions.contains(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME)) { - currentEntry.setDisplayNameWithoutUpdate(entry.getDisplayName()); + currentEntry.setDisplayNameWithoutUpdate(entry.getDisplayName() != null + ? entry.getDisplayName().getComponent() : null); } if (actions.contains(UpsertPlayerInfo.Action.INITIALIZE_CHAT)) { currentEntry.setChatSession(entry.getChatSession()); @@ -259,4 +269,4 @@ public void processRemove(RemovePlayerInfo infoPacket) { this.entries.remove(uuid); } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java index f8bd9760a2..c6adc7d21a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -22,6 +22,7 @@ import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import java.util.Optional; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; @@ -43,8 +44,8 @@ public class VelocityTabListEntry implements TabListEntry { * Constructs the instance. */ public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, - int latency, - int gameMode, @Nullable ChatSession session, boolean listed) { + int latency, + int gameMode, @Nullable ChatSession session, boolean listed) { this.tabList = tabList; this.profile = profile; this.displayName = displayName; @@ -78,7 +79,8 @@ public Optional getDisplayNameComponent() { public TabListEntry setDisplayName(@Nullable Component displayName) { this.displayName = displayName; UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this); - upsertEntry.setDisplayName(displayName); + upsertEntry.setDisplayName( + new ComponentHolder(this.tabList.getPlayer().getProtocolVersion(), displayName)); this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME, upsertEntry); return this; } @@ -144,4 +146,4 @@ public VelocityTabListEntry setListed(boolean listed) { void setListedWithoutUpdate(boolean listed) { this.listed = listed; } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java index db3560ae01..fbca26d688 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java @@ -20,7 +20,7 @@ import com.google.common.collect.MapMaker; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.util.collect.Enum2IntMap; import com.velocitypowered.proxy.util.concurrent.Once; import java.util.Collections; @@ -121,7 +121,7 @@ public void removeBossBar(ConnectedPlayer player, BossBar bar) { @Override public void bossBarNameChanged(@NonNull BossBar bar, @NonNull Component oldName, - @NonNull Component newName) { + @NonNull Component newName) { BossBarHolder holder = this.getHandler(bar); if (holder == null) { return; @@ -149,7 +149,7 @@ public void bossBarProgressChanged(@NonNull BossBar bar, float oldPercent, float @Override public void bossBarColorChanged(@NonNull BossBar bar, @NonNull Color oldColor, - @NonNull Color newColor) { + @NonNull Color newColor) { BossBarHolder holder = this.getHandler(bar); if (holder == null) { return; @@ -162,7 +162,7 @@ public void bossBarColorChanged(@NonNull BossBar bar, @NonNull Color oldColor, @Override public void bossBarOverlayChanged(@NonNull BossBar bar, @NonNull Overlay oldOverlay, - @NonNull Overlay newOverlay) { + @NonNull Overlay newOverlay) { BossBarHolder holder = this.getHandler(bar); if (holder == null) { return; @@ -176,7 +176,7 @@ public void bossBarOverlayChanged(@NonNull BossBar bar, @NonNull Overlay oldOver @Override public void bossBarFlagsChanged(@NonNull BossBar bar, @NonNull Set added, - @NonNull Set removed) { + @NonNull Set removed) { BossBarHolder holder = this.getHandler(bar); if (holder == null) { return; @@ -212,8 +212,8 @@ com.velocitypowered.proxy.protocol.packet.BossBar createAddPacket(ConnectedPlaye .proxy.protocol.packet.BossBar(); packet.setUuid(this.id); packet.setAction(com.velocitypowered.proxy.protocol.packet.BossBar.ADD); - packet.setName(ProtocolUtils.getJsonChatSerializer(player.getProtocolVersion()) - .serialize(player.translateMessage(bar.name()))); + packet.setName( + new ComponentHolder(player.getProtocolVersion(), player.translateMessage(bar.name()))); packet.setColor(COLORS_TO_PROTOCOL.get(bar.color())); packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay())); packet.setPercent(bar.progress()); @@ -242,12 +242,12 @@ com.velocitypowered.proxy.protocol.packet.BossBar createColorUpdate(Color color) } com.velocitypowered.proxy.protocol.packet.BossBar createTitleUpdate(Component name, - ProtocolVersion version) { + ProtocolVersion version) { com.velocitypowered.proxy.protocol.packet.BossBar packet = new com.velocitypowered .proxy.protocol.packet.BossBar(); packet.setUuid(this.id); packet.setAction(com.velocitypowered.proxy.protocol.packet.BossBar.UPDATE_NAME); - packet.setName(ProtocolUtils.getJsonChatSerializer(version).serialize(name)); + packet.setName(new ComponentHolder(version, name)); return packet; } @@ -283,4 +283,4 @@ private byte serializeFlags(Set flags) { return val; } } -} +} \ No newline at end of file diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties index f2b4d8a7e7..f5b6ae9b4a 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties @@ -35,6 +35,7 @@ velocity.command.generic-error=An error occurred while running this command. velocity.command.command-does-not-exist=This command does not exist. velocity.command.players-only=Only players can run this command. velocity.command.server-does-not-exist=The specified server {0} does not exist. +velocity.command.player-not-found=The specified player {0} does not exist. velocity.command.server-current-server=You are currently connected to {0}. velocity.command.server-too-many=There are too many servers set up. Use tab completion to view all servers available. velocity.command.server-available=Available servers: @@ -59,5 +60,6 @@ velocity.command.dump-success=Created an anonymised report containing useful inf velocity.command.dump-will-expire=This link will expire in a few days. velocity.command.dump-server-error=An error occurred on the Velocity servers and the dump could not be completed. Please contact the Velocity staff about this problem and provide the details about this error from the Velocity console or server log. velocity.command.dump-offline=Likely cause: Invalid system DNS settings or no internet connection +velocity.command.send-usage=/send # Kick velocity.kick.shutdown=Proxy shutting down. \ No newline at end of file diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties index a980e0c83f..5c73de752e 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties @@ -35,6 +35,7 @@ velocity.command.command-does-not-exist=Este comando no existe. velocity.command.players-only=Solo los jugadores pueden ejecutar este comando. velocity.command.server-does-not-exist=El servidor especificado {0} no existe. velocity.command.server-current-server=Estás conectado a {0}. +velocity.command.player-not-found=El jugador especificado {0} no existe. velocity.command.server-too-many=Hay demasiados servidores registrados. Usa la finalización con tabulación para ver todos los servidores disponibles. velocity.command.server-available=Servidores disponibles\: velocity.command.server-tooltip-player-online={0} jugador conectado @@ -57,4 +58,5 @@ velocity.command.dump-send-error=Se ha producido un error al comunicarse con los velocity.command.dump-success=Se ha creado un informe anónimo que contiene información útil sobre este proxy. Si un desarrollador lo solicita, puedes compartir el siguiente enlace con él\: velocity.command.dump-will-expire=Este enlace caducará en unos días. velocity.command.dump-server-error=Se ha producido un error en los servidores de Velocity y la subida no se ha podido completar. Notifica al equipo de Velocity sobre este problema y proporciona los detalles sobre este error disponibles en el archivo de registro o la consola de tu servidor Velocity. -velocity.command.dump-offline=Causa probable\: la configuración DNS del sistema no es válida o no hay conexión a internet \ No newline at end of file +velocity.command.dump-offline=Causa probable\: la configuración DNS del sistema no es válida o no hay conexión a internet +velocity.command.send-usage=/send \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java b/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java new file mode 100644 index 0000000000..9bab68a016 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021-2023 Velocity 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 com.velocitypowered.proxy.component; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.junit.jupiter.api.Test; + +/** + * ComponentHolder tests. + */ +public class ComponentHolderTest { + + @Test + void testJsonToBinary() { + Component component = MiniMessage.miniMessage().deserialize( + "<#09add3>A Velocity <#09add3>Server"); + ComponentHolder holder = new ComponentHolder(ProtocolVersion.MINECRAFT_1_20_3, component); + holder.getJson(); + holder.getBinaryTag(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0c31266f95..7b375a6f9f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,7 @@ sequenceOf( include(project) project(project).projectDir = file(it) } + +val deprecatedConfigurateModule = ":deprecated-configurate3" +include(deprecatedConfigurateModule) +project(deprecatedConfigurateModule).projectDir = file("proxy/deprecated/configurate3")