diff --git a/api/build.gradle.kts b/api/build.gradle.kts index a4cbd741..b96208c1 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -70,7 +70,7 @@ tasks { "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", "https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/", "https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/", - "https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", + "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", ) o.tags( diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java index d1c7be51..b44f2847 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java @@ -14,7 +14,6 @@ import com.velocitypowered.api.plugin.Plugin; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; -import java.util.Objects; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -68,8 +67,8 @@ public class PluginAnnotationProcessor extends AbstractProcessor { Name qualifiedName = ((TypeElement) element).getQualifiedName(); - if (Objects.equals(pluginClassFound, qualifiedName.toString())) { - if (!warnedAboutMultiplePlugins) { + if (pluginClassFound != null) { + if (!pluginClassFound.equals(qualifiedName.toString()) && !warnedAboutMultiplePlugins) { environment.getMessager() .printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " + "multiple plugins. We are using " + pluginClassFound diff --git a/api/src/main/java/com/velocitypowered/api/event/EventManager.java b/api/src/main/java/com/velocitypowered/api/event/EventManager.java index b8702ab3..ed11986c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventManager.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventManager.java @@ -60,7 +60,8 @@ public interface EventManager { * * @param plugin the plugin to associate with the handler * @param eventClass the class for the event handler to register - * @param postOrder the relative order in which events should be posted to the handler + * @param postOrder the relative order in which events should be posted to the handler. The higher + * the priority, the earlier the event handler will be called * @param handler the handler to register * @param the event type to handle */ diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java new file mode 100644 index 00000000..893942c0 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018-2025 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.proxy; + +import com.google.common.annotations.Beta; +import com.velocitypowered.api.event.annotation.AwaitingEvent; + +/** + * This event is fired by the proxy after it has stopped accepting new connections, + * but before players are disconnected. + * This is the last point at which you can interact with currently connected players, + * for example to transfer them to another proxy or perform other cleanup tasks. + * + * @implNote Velocity will wait for all event listeners to complete before disconnecting players, + * but note that the event will time out after the configured value of the + * velocity.pre-shutdown-timeout system property, default 10 seconds, + * in seconds to prevent shutdown from hanging indefinitely + * @since 3.4.0 + */ +@Beta +@AwaitingEvent +public final class ProxyPreShutdownEvent { + + @Override + public String toString() { + return "ProxyPreShutdownEvent"; + } +} 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 039fec40..f2a629b1 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -92,7 +92,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_21_4(769, "1.21.4"), MINECRAFT_1_21_5(770, "1.21.5"), MINECRAFT_1_21_6(771, "1.21.6"), - MINECRAFT_1_21_7(772, "1.21.7", "1.21.8"); + MINECRAFT_1_21_7(772, "1.21.7", "1.21.8"), + MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"); 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 04e65c84..efe21cd7 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -29,6 +29,7 @@ import java.util.Locale; import java.util.Optional; import java.util.UUID; import java.util.function.UnaryOperator; +import net.kyori.adventure.dialog.DialogLike; import net.kyori.adventure.identity.Identified; import net.kyori.adventure.inventory.Book; import net.kyori.adventure.key.Key; @@ -48,7 +49,7 @@ public interface Player extends /* Fundamental Velocity interfaces */ CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink, /* Adventure-specific interfaces */ - Identified, HoverEventSource, Keyed, KeyIdentifiable { + Identified, HoverEventSource, Keyed, KeyIdentifiable, Sound.Emitter { /** * Returns the player's current username. @@ -383,8 +384,12 @@ public interface Player extends /** * {@inheritDoc} * - * This method is not currently implemented in Velocity - * and will not perform any actions. + * + * @apiNote This method is not currently implemented in Velocity + * and will not perform any actions. + * @see #playSound(Sound, Sound.Emitter) + * @see + * Unsupported Adventure Operations */ @Override default void playSound(@NotNull Sound sound) { @@ -393,8 +398,11 @@ public interface Player extends /** * {@inheritDoc} * - * This method is not currently implemented in Velocity - * and will not perform any actions. + * @apiNote This method is not currently implemented in Velocity + * and will not perform any actions. + * @see #playSound(Sound, Sound.Emitter) + * @see + * Unsupported Adventure Operations */ @Override default void playSound(@NotNull Sound sound, double x, double y, double z) { @@ -403,18 +411,28 @@ public interface Player extends /** * {@inheritDoc} * - * This method is not currently implemented in Velocity - * and will not perform any actions. + *

Note: Due to MC-146721, stereo sounds are always played globally in 1.14+. + * + *

Note: Due to MC-138832, the volume and pitch are ignored when using this method in 1.14 to 1.16.5. + * + * @param sound the sound to play + * @param emitter the emitter of the sound; may be another player of this player's server + * @since 3.4.0 + * @sinceMinecraft 1.19.3 + * @apiNote This method is currently only implemented for players on 1.19.3+ + * and requires a present {@link #getCurrentServer} for the emitting player as well as this player. */ @Override - default void playSound(@NotNull Sound sound, Sound.Emitter emitter) { + default void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) { } /** * {@inheritDoc} * - * This method is not currently implemented in Velocity - * and will not perform any actions. + * @param stop the sound and/or a sound source, to stop + * @since 3.4.0 + * @sinceMinecraft 1.19.3 + * @apiNote This method is currently only implemented for players on 1.19.3+. */ @Override default void stopSound(@NotNull SoundStop stop) { @@ -425,11 +443,40 @@ public interface Player extends * * This method is not currently implemented in Velocity * and will not perform any actions. + * + * @see + * Unsupported Adventure Operations */ @Override default void openBook(@NotNull Book book) { } + /** + * {@inheritDoc} + * + * This method is not currently implemented in Velocity + * and will not perform any actions. + * + * @see + * Unsupported Adventure Operations + */ + @Override + default void showDialog(@NotNull DialogLike dialog) { + } + + /** + * {@inheritDoc} + * + * This method is not currently implemented in Velocity + * and will not perform any actions. + * + * @see + * Unsupported Adventure Operations + */ + @Override + default void closeDialog() { + } + /** * Transfers a Player to a host. * diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index 5abc94c7..0ad26908 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -19,7 +19,9 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; -import org.checkerframework.checker.nullness.qual.Nullable; +import net.kyori.adventure.text.Component; +import org.jspecify.annotations.Nullable; + /** * Represents a 1.7 and above server list ping response. This class is immutable. @@ -28,7 +30,7 @@ public final class ServerPing { private final Version version; private final @Nullable Players players; - private final net.kyori.adventure.text.Component description; + private final @Nullable Component description; private final @Nullable Favicon favicon; private final @Nullable ModInfo modinfo; private final boolean preventsChatReports = true; @@ -48,8 +50,8 @@ public final class ServerPing { * @param modinfo the mods this server runs */ public ServerPing(Version version, @Nullable Players players, - net.kyori.adventure.text.Component description, @Nullable Favicon favicon, - @Nullable ModInfo modinfo) { + Component description, @Nullable Favicon favicon, + @Nullable ModInfo modinfo) { this.version = Preconditions.checkNotNull(version, "version"); this.players = players; this.description = Preconditions.checkNotNull(description, "description"); @@ -65,7 +67,8 @@ public final class ServerPing { return Optional.ofNullable(players); } - public net.kyori.adventure.text.Component getDescriptionComponent() { + @Nullable + public Component getDescriptionComponent() { return description; } @@ -152,7 +155,7 @@ public final class ServerPing { private final List samplePlayers = new ArrayList<>(); private String modType = "FML"; private final List mods = new ArrayList<>(); - private net.kyori.adventure.text.Component description; + private Component description; private @Nullable Favicon favicon; private boolean nullOutPlayers; private boolean nullOutModinfo; @@ -300,7 +303,7 @@ public final class ServerPing { * @param description Component to use as the description. * @return this builder, for chaining */ - public Builder description(net.kyori.adventure.text.Component description) { + public Builder description(Component description) { this.description = Preconditions.checkNotNull(description, "description"); return this; } @@ -360,7 +363,7 @@ public final class ServerPing { return samplePlayers; } - public Optional getDescriptionComponent() { + public Optional getDescriptionComponent() { return Optional.ofNullable(description); } diff --git a/build-logic/src/main/kotlin/velocity-publish.gradle.kts b/build-logic/src/main/kotlin/velocity-publish.gradle.kts index a7cc744a..8b20d66b 100644 --- a/build-logic/src/main/kotlin/velocity-publish.gradle.kts +++ b/build-logic/src/main/kotlin/velocity-publish.gradle.kts @@ -9,9 +9,9 @@ extensions.configure { credentials(PasswordCredentials::class.java) name = if (version.toString().endsWith("SNAPSHOT")) "paperSnapshots" else "paper" // "paper" is seemingly not defined - val base = "https://repo.papermc.io/repository/maven" - val releasesRepoUrl = "$base-releases/" - val snapshotsRepoUrl = "$base-snapshots/" + val base = "https://artifactory.papermc.io/artifactory" + val releasesRepoUrl = "$base/releases/" + val snapshotsRepoUrl = "$base/snapshots/" setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fb1e863f..d0bb2c84 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ configurate3 = "3.7.3" configurate4 = "4.1.2" flare = "2.0.1" log4j = "2.24.3" -netty = "4.2.1.Final" +netty = "4.2.7.Final" [plugins] fill = "io.papermc.fill.gradle:1.0.3" @@ -12,8 +12,8 @@ shadow = "com.gradleup.shadow:8.3.6" spotless = "com.diffplug.spotless:6.25.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.23.0" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.23.0" +adventure-bom = "net.kyori:adventure-bom:4.25.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.25.0" adventure-facet = "net.kyori:adventure-platform-facet:4.3.4" asm = "org.ow2.asm:asm:9.8" auto-service = "com.google.auto.service:auto-service:1.0.1" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index a6fc850b..0ec8622d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -47,11 +47,6 @@ public class Velocity { System.setProperty("io.netty.native.workdir", System.getProperty("velocity.natives-tmpdir")); } - // Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator - if (System.getProperty("io.netty.allocator.type") == null) { - System.setProperty("io.netty.allocator.type", "pooled"); - } - // Disable the resource leak detector by default as it reduces performance. Allow the user to // override this if desired. if (!VelocityProperties.hasProperty("io.netty.leakDetection.level")) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index c3e8d472..ccfc5f14 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -24,6 +24,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyPreShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyReloadEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -119,7 +120,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; */ public class VelocityServer implements ProxyServer, ForwardingAudience { - public static final String VELOCITY_URL = "https://velocitypowered.com"; + public static final String VELOCITY_URL = "https://papermc.io/software/velocity"; private static final Logger logger = LogManager.getLogger(VelocityServer.class); public static final Gson GENERAL_GSON = new GsonBuilder() @@ -150,6 +151,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { ) .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .create(); + private static final int PRE_SHUTDOWN_TIMEOUT = + Integer.getInteger("velocity.pre-shutdown-timeout", 10); private final ConnectionManager cm; private final ProxyOptions options; @@ -216,7 +219,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { ProxyVersion version = getVersion(); PluginDescription description = new VelocityPluginDescription( "velocity", version.getName(), version.getVersion(), "The Velocity proxy", - VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null); + version.getName().equals("Velocity") ? VELOCITY_URL : null, + ImmutableList.of(version.getVendor()), Collections.emptyList(), null); VelocityPluginContainer container = new VelocityPluginContainer(description); container.setInstance(VelocityVirtualPlugin.INSTANCE); return container; @@ -578,6 +582,20 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { // done first to refuse new connections cm.shutdown(); + try { + eventManager.fire(new ProxyPreShutdownEvent()) + .toCompletableFuture() + .get(PRE_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS); + } catch (TimeoutException ignored) { + logger.warn("Your plugins took over {} seconds during pre shutdown.", + PRE_SHUTDOWN_TIMEOUT); + } catch (ExecutionException ee) { + logger.error("Exception in ProxyPreShutdownEvent handler; continuing shutdown.", ee); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + logger.warn("Interrupted while waiting for ProxyPreShutdownEvent; continuing shutdown."); + } + ImmutableList players = ImmutableList.copyOf(connectionsByUuid.values()); for (ConnectedPlayer player : players) { player.disconnect(reason); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java index 9dec3309..e2451188 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java @@ -53,15 +53,23 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, viewer.getProtocolVersion(), viewer.translateMessage(this.bar.name()) ); - viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name)); + viewer.getBossBarManager().writeUpdate(this, BossBarPacket.createAddPacket(this.id, this.bar, name)); return true; } return false; } + public void createDirect(final ConnectedPlayer viewer) { + final ComponentHolder name = new ComponentHolder( + viewer.getProtocolVersion(), + viewer.translateMessage(this.bar.name()) + ); + viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name)); + } + public boolean viewerRemove(final ConnectedPlayer viewer) { if (this.viewers.remove(viewer)) { - viewer.getConnection().write(BossBarPacket.createRemovePacket(this.id, this.bar)); + viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar)); return true; } return false; @@ -84,7 +92,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, this.bar, new ComponentHolder(viewer.getProtocolVersion(), translated) ); - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -96,7 +104,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -108,7 +116,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -120,7 +128,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -132,7 +140,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 21f82f34..095445bf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -35,7 +35,6 @@ import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.command.CommandResult; import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.VelocityBrigadierMessage; import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.PostCommandInvocationEvent; import com.velocitypowered.api.plugin.PluginManager; @@ -59,6 +58,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.format.NamedTextColor; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.nullness.qual.Nullable; @@ -242,8 +242,8 @@ public class VelocityCommandManager implements CommandManager { CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand()); if (isSyntaxError) { final Message message = e.getRawMessage(); - if (message instanceof VelocityBrigadierMessage velocityMessage) { - source.sendMessage(velocityMessage.asComponent().applyFallbackStyle(NamedTextColor.RED)); + if (message instanceof ComponentLike componentLike) { + source.sendMessage(componentLike.asComponent().applyFallbackStyle(NamedTextColor.RED)); } else { source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED)); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 1e393476..6b233b87 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -176,8 +176,7 @@ public final class VelocityCommand { .append(Component.text() .content("PaperMC") .color(NamedTextColor.GREEN) - .clickEvent( - ClickEvent.openUrl("https://papermc.io/software/velocity")) + .clickEvent(ClickEvent.openUrl(VelocityServer.VELOCITY_URL)) .build()) .append(Component.text(" - ")) .append(Component.text() diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java index 380f45e3..847d67d1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java @@ -103,13 +103,17 @@ abstract class InvocableCommandRegistrar, .requiresWithContext((context, reader) -> requirement.test(context)) .executes(callback) .suggests((context, builder) -> { + // Offset the suggestion to the last space seperated word + int lastSpace = builder.getRemaining().lastIndexOf(' ') + 1; + final var offsetBuilder = builder.createOffset(builder.getStart() + lastSpace); + final I invocation = invocationFactory.create(context); return command.suggestAsync(invocation).thenApply(suggestions -> { for (String value : suggestions) { Preconditions.checkNotNull(value, "suggestion"); - builder.suggest(value); + offsetBuilder.suggest(value); } - return builder.build(); + return offsetBuilder.build(); }); }) .build(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index dfd007ac..6f0fb61c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -515,7 +515,7 @@ public class VelocityConfiguration implements ProxyConfig { String forwardingSecretString = System.getenv().getOrDefault( "VELOCITY_FORWARDING_SECRET", ""); - if (forwardingSecretString.isEmpty()) { + if (forwardingSecretString.isBlank()) { final String forwardSecretFile = config.get("forwarding-secret-file"); final Path secretPath = forwardSecretFile == null ? defaultForwardingSecretPath @@ -528,7 +528,11 @@ public class VelocityConfiguration implements ProxyConfig { "The file " + forwardSecretFile + " is not a valid file or it is a directory."); } } else { - throw new RuntimeException("The forwarding-secret-file does not exist."); + Files.createFile(secretPath); + Files.writeString(secretPath, forwardingSecretString = generateRandomString(12), + StandardCharsets.UTF_8); + logger.info("The forwarding-secret-file does not exist. A new file has been created at {}", + forwardSecretFile); } } final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8); 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 b36d9f0a..d1101d6a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -23,7 +23,11 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; +import com.velocitypowered.proxy.protocol.packet.DialogClearPacket; +import com.velocitypowered.proxy.protocol.packet.DialogShowPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; @@ -48,6 +52,7 @@ import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; @@ -67,6 +72,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerComma import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; @@ -364,4 +371,32 @@ public interface MinecraftSessionHandler { default boolean handle(ClientboundServerLinksPacket packet) { return false; } + + default boolean handle(DialogClearPacket packet) { + return false; + } + + default boolean handle(DialogShowPacket packet) { + return false; + } + + default boolean handle(ServerboundCustomClickActionPacket packet) { + return false; + } + + default boolean handle(CodeOfConductPacket packet) { + return false; + } + + default boolean handle(CodeOfConductAcceptPacket packet) { + return false; + } + + default boolean handle(ClientboundSoundEntityPacket packet) { + return false; + } + + default boolean handle(ClientboundStopSoundPacket 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 7b3e733f..7fda94bd 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 @@ -179,10 +179,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(BossBarPacket packet) { - if (packet.getAction() == BossBarPacket.ADD) { - playerSessionHandler.getServerBossBars().add(packet.getUuid()); - } else if (packet.getAction() == BossBarPacket.REMOVE) { - playerSessionHandler.getServerBossBars().remove(packet.getUuid()); + if (serverConn.getPlayer().getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + if (packet.getAction() == BossBarPacket.ADD) { + playerSessionHandler.getServerBossBars().add(packet.getUuid()); + } else if (packet.getAction() == BossBarPacket.REMOVE) { + playerSessionHandler.getServerBossBars().remove(packet.getUuid()); + } } return false; // forward } @@ -344,7 +346,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { // Inject commands from the proxy. final CommandGraphInjector injector = server.getCommandManager().getInjector(); injector.inject(rootNode, serverConn.getPlayer()); - rootNode.removeChildByName("velocity:callback"); + + // In 1.21.6 a confirmation prompt was added when executing a command via `run_command` click + // action if the command is unknown. To prevent this prompt we have to send the command. + if (this.playerConnection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_6)) { + rootNode.removeChildByName("velocity:callback"); + } } server.getEventManager().fire( @@ -496,4 +503,4 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { 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 fb0e48d1..8ab38e58 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 @@ -52,6 +52,7 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; @@ -256,7 +257,13 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(DisconnectPacket packet) { serverConn.disconnect(); - resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer())); + // If the player receives a DisconnectPacket without a connection to a server in progress, + // it means that the backend server has kicked the player during reconfiguration + if (serverConn.getPlayer().getConnectionInFlight() != null) { + resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer())); + } else { + serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet, true); + } return true; } @@ -358,6 +365,12 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(CodeOfConductPacket packet) { + this.serverConn.getPlayer().getConnection().write(packet.retain()); + return true; + } + @Override public void disconnected() { resultFuture.completeExceptionally( diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 612e9c25..14884af4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -165,7 +165,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { smc.setAutoReading(false); - clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); + clientPlaySessionHandler.doSwitch().thenRunAsync(() -> smc.setAutoReading(true), smc.eventLoop()); } else { // Initial login - the player is already in configuration state. server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index af3f8179..71ebd7bc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -53,6 +53,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; @@ -70,6 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private boolean gracefulDisconnect = false; private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private final Map pendingPings = new HashMap<>(); + private @MonotonicNonNull Integer entityId; /** * Initializes a new server connection. @@ -324,6 +326,14 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return pendingPings; } + public Integer getEntityId() { + return entityId; + } + + public void setEntityId(Integer entityId) { + this.entityId = entityId; + } + /** * Ensures that this server connection remains "active": the connection is established and not * closed, the player is still connected to the server, and the player still remains online. 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 dc033f57..776f99d6 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 @@ -40,6 +40,8 @@ import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; @@ -205,6 +207,26 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ServerboundCustomClickActionPacket packet) { + if (player.getConnectionInFlight() != null) { + player.getConnectionInFlight().ensureConnected().write(packet.retain()); + return true; + } + + return false; + } + + @Override + public boolean handle(CodeOfConductAcceptPacket packet) { + if (this.player.getConnectionInFlight() != null) { + this.player.getConnectionInFlight().ensureConnected().write(packet); + return true; + } + + return false; + } + @Override public void handleGeneric(MinecraftPacket packet) { VelocityServerConnection serverConnection = player.getConnectedServer(); 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 0b89e7ba..de5f3f01 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 @@ -21,7 +21,6 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc import com.google.common.collect.ImmutableList; import com.mojang.brigadier.suggestion.Suggestion; -import com.velocitypowered.api.command.VelocityBrigadierMessage; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; @@ -89,6 +88,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -519,9 +519,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Config state clears everything in the client. No need to clear later. spawned = false; - serverBossBars.clear(); player.clearPlayerListHeaderAndFooterSilent(); player.getTabList().clearAllSilent(); + if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + player.getBossBarManager().dropPackets(); + } else { + serverBossBars.clear(); + } } player.switchToConfigState(); @@ -559,15 +563,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } } - // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to - // track them. - for (UUID serverBossBar : serverBossBars) { - BossBarPacket deletePacket = new BossBarPacket(); - deletePacket.setUuid(serverBossBar); - deletePacket.setAction(BossBarPacket.REMOVE); - player.getConnection().delayedWrite(deletePacket); + destination.setEntityId(joinGame.getEntityId()); // used for sound api + if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + player.getBossBarManager().sendBossBars(); + } else { + // Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2), + // thus the need to track them. + for (UUID serverBossBar : serverBossBars) { + BossBarPacket deletePacket = new BossBarPacket(); + deletePacket.setUuid(serverBossBar); + deletePacket.setAction(BossBarPacket.REMOVE); + player.getConnection().delayedWrite(deletePacket); + } + serverBossBars.clear(); } - serverBossBars.clear(); // Tell the server about the proxy's plugin message channels. ProtocolVersion serverVersion = serverMc.getProtocolVersion(); @@ -678,23 +687,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return; } - List offers = new ArrayList<>(); - for (Suggestion suggestion : suggestions.getList()) { - String offer = suggestion.getText(); - ComponentHolder tooltip = null; - if (suggestion.getTooltip() != null - && suggestion.getTooltip() instanceof VelocityBrigadierMessage) { - tooltip = new ComponentHolder(player.getProtocolVersion(), - ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); + int startPos = -1; + for (var suggestion : suggestions.getList()) { + if (startPos == -1 || startPos > suggestion.getRange().getStart()) { + startPos = suggestion.getRange().getStart(); } - offers.add(new Offer(offer, tooltip)); } - int startPos = packet.getCommand().lastIndexOf(' ') + 1; + if (startPos > 0) { + List offers = new ArrayList<>(); + for (Suggestion suggestion : suggestions.getList()) { + String offer; + if (suggestion.getRange().getStart() == startPos) { + offer = suggestion.getText(); + } else { + offer = command.substring(startPos, suggestion.getRange().getStart()) + suggestion.getText(); + } + ComponentHolder tooltip = null; + if (suggestion.getTooltip() instanceof ComponentLike componentLike) { + tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent()); + } else if (suggestion.getTooltip() != null) { + tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString())); + } + offers.add(new Offer(offer, tooltip)); + } + TabCompleteResponsePacket resp = new TabCompleteResponsePacket(); resp.setTransactionId(packet.getTransactionId()); - resp.setStart(startPos); - resp.setLength(packet.getCommand().length() - startPos); + resp.setStart(startPos + 1); + resp.setLength(packet.getCommand().length() - startPos - 1); resp.getOffers().addAll(offers); player.getConnection().write(resp); } @@ -749,10 +770,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { offer = offer.substring(command.length()); } ComponentHolder tooltip = null; - if (suggestion.getTooltip() != null - && suggestion.getTooltip() instanceof VelocityBrigadierMessage) { - tooltip = new ComponentHolder(player.getProtocolVersion(), - ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); + if (suggestion.getTooltip() instanceof ComponentLike componentLike) { + tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent()); + } else if (suggestion.getTooltip() != null) { + tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString())); } 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 1fb3884b..0807789f 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 @@ -62,6 +62,7 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.player.bossbar.BossBarManager; import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; @@ -73,6 +74,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; @@ -127,6 +130,8 @@ import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.resource.ResourcePackInfoLike; import net.kyori.adventure.resource.ResourcePackRequest; import net.kyori.adventure.resource.ResourcePackRequestLike; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.sound.SoundStop; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; @@ -197,6 +202,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private @Nullable ClientSettingsPacket clientSettingsPacket; private volatile ChatQueue chatQueue; private final ChatBuilderFactory chatBuilderFactory; + private final BossBarManager bossBarManager; ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, @Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode, @@ -223,6 +229,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, this.chatQueue = new ChatQueue(this); this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion()); this.resourcePackHandler = ResourcePackHandler.create(this, server); + this.bossBarManager = new BossBarManager(this); } /** @@ -733,15 +740,19 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, 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(), - plainTextReason); + if (this.server.getConfiguration().isLogPlayerConnections()) { + logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), + plainTextReason); + } handleConnectionException(server, disconnectReason, Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED, Component.text(server.getServerInfo().getName()), disconnectReason), safe); } else { - logger.error("{}: disconnected while connecting to {}: {}", this, - server.getServerInfo().getName(), plainTextReason); + if (this.server.getConfiguration().isLogPlayerConnections()) { + logger.error("{}: disconnected while connecting to {}: {}", this, + server.getServerInfo().getName(), plainTextReason); + } handleConnectionException(server, disconnectReason, Component.translatable("velocity.error.cant-connect", NamedTextColor.RED, Component.text(server.getServerInfo().getName()), @@ -806,9 +817,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, createConnectionRequest(res.getServer(), previousConnection).connect() .whenCompleteAsync((status, throwable) -> { if (throwable != null) { - handleConnectionException( - status != null ? status.getAttemptedConnection() : res.getServer(), throwable, - true); + handleConnectionException(res.getServer(), throwable, true); return; } @@ -1038,6 +1047,50 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, this.clientBrand = clientBrand; } + @Override + public void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) { + Preconditions.checkNotNull(sound, "sound"); + Preconditions.checkNotNull(emitter, "emitter"); + VelocityServerConnection soundTargetServerConn = getConnectedServer(); + if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3) + || connection.getState() != StateRegistry.PLAY + || soundTargetServerConn == null + || (sound.source() == Sound.Source.UI + && getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) { + return; + } + + VelocityServerConnection soundEmitterServerConn; + if (emitter == Sound.Emitter.self()) { + soundEmitterServerConn = soundTargetServerConn; + } else if (emitter instanceof ConnectedPlayer player) { + if ((soundEmitterServerConn = player.getConnectedServer()) == null) { + return; + } + + if (!soundEmitterServerConn.getServer().equals(soundTargetServerConn.getServer())) { + return; + } + } else { + return; + } + + connection.write(new ClientboundSoundEntityPacket(sound, null, soundEmitterServerConn.getEntityId())); + } + + @Override + public void stopSound(@NotNull SoundStop stop) { + Preconditions.checkNotNull(stop, "stop"); + if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3) + || connection.getState() != StateRegistry.PLAY + || (stop.source() == Sound.Source.UI + && getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) { + return; + } + + connection.write(new ClientboundStopSoundPacket(stop)); + } + @Override public void transferToHost(final InetSocketAddress address) { Preconditions.checkNotNull(address); @@ -1379,6 +1432,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, return handshakeIntent; } + public BossBarManager getBossBarManager() { + return bossBarManager; + } + private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final RegisteredServer toConnect; @@ -1438,7 +1495,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, VelocityServerConnection con = new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server); connectionInFlight = con; - return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con), + + return con.connect().whenCompleteAsync((result, exception) -> { + if (result != null && !result.isSuccessful() && !result.isSafe()) { + handleConnectionException(result.getAttemptedConnection(), + // The only way for the reason to be null is if the result is safe + DisconnectPacket.create(result.getReasonComponent().orElseThrow(), + getProtocolVersion(), connection.getState()), false); + } + this.resetIfInFlightIs(con); + }, connection.eventLoop()); }, connection.eventLoop()); }); @@ -1452,22 +1518,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public CompletableFuture connect() { - return this.internalConnect().whenCompleteAsync((status, throwable) -> { - if (status != null && !status.isSuccessful()) { - if (!status.isSafe()) { - handleConnectionException(status.getAttemptedConnection(), throwable, false); - } - } - }, connection.eventLoop()).thenApply(x -> x); + return this.internalConnect().thenApply(x -> x); } @Override public CompletableFuture connectWithIndication() { return internalConnect().whenCompleteAsync((status, throwable) -> { if (throwable != null) { - // TODO: The exception handling from this is not very good. Find a better way. - handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect, - throwable, true); + handleConnectionException(toConnect, throwable, true); return; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java new file mode 100644 index 00000000..03ff4f7f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java @@ -0,0 +1,79 @@ +/* + * 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.player.bossbar; + +import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.BossBarPacket; +import java.util.HashSet; +import java.util.Set; + +/** + * Handles dropping and resending boss bar packets on versions 1.20.2 and newer because the client now + * deletes all boss bars during the login phase, and sending update packets would cause the client to be disconnected. + */ +public final class BossBarManager { + + private final ConnectedPlayer player; + private final Set bossBars = new HashSet<>(); + + private boolean dropPackets = false; + + public BossBarManager(ConnectedPlayer player) { + this.player = player; + } + + /** + * Records the specified boss bar to be re-sent when a player changes server, and sends the update packet + * if the client is able to receive it and not be disconnected. + */ + public synchronized void writeUpdate(VelocityBossBarImplementation bar, BossBarPacket packet) { + this.bossBars.add(bar); + if (!this.dropPackets) { + this.player.getConnection().write(packet); + } + } + + /** + * Removes the specified boss bar from the player to ensure it is not re-sent. + */ + public synchronized void remove(VelocityBossBarImplementation bar, BossBarPacket packet) { + this.bossBars.remove(bar); + if (!this.dropPackets) { + this.player.getConnection().write(packet); + } + } + + /** + * Re-creates the boss bars the player can see with any updates that may have occurred in the meantime, + * and allows update packets for those boss bars to be sent. + */ + public synchronized void sendBossBars() { + for (VelocityBossBarImplementation bossBar : bossBars) { + bossBar.createDirect(player); + } + this.dropPackets = false; + } + + /** + * Prevents the player from receiving boss bar update packets while logging in to a new server. + */ + public synchronized void dropPackets() { + this.dropPackets = true; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java index e5df9f65..b3843885 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java @@ -111,7 +111,7 @@ public abstract sealed class ResourcePackHandler } request.setRequired(queued.getShouldForce()); request.setPrompt(queued.getPrompt() == null ? null : - new ComponentHolder(player.getProtocolVersion(), queued.getPrompt())); + new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt()))); player.getConnection().write(request); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index d1f2b0db..7740139c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -36,6 +36,7 @@ import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import net.kyori.adventure.text.Component; /** * Common utilities for handling server list ping results. @@ -107,6 +108,13 @@ public class ServerListPingHandler { if (response == fallback) { continue; } + + if (response.getDescriptionComponent() == null) { + return response.asBuilder() + .description(Component.empty()) + .build(); + } + return response; } return fallback; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index fe360d4d..90b6292f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -136,6 +136,10 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons if (!this.server.getCommandManager().executeAsync(this, command).join()) { sendMessage(Component.translatable("velocity.command.command-does-not-exist", NamedTextColor.RED)); + return; + } + if (server.getConfiguration().isLogCommandExecutions()) { + logger.info("CONSOLE -> executed command /{}", command); } } catch (Exception e) { logger.error("An error occurred while running this command.", e); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java index e54ee770..1722f42b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java @@ -32,13 +32,18 @@ public interface MinecraftPacket { boolean handle(MinecraftSessionHandler handler); - default int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return -1; } - default int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 0; } + + default int encodeSizeHint(ProtocolUtils.Direction direction, + ProtocolVersion version) { + return -1; + } } 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 a90ee9dc..efc0ed17 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -44,6 +44,7 @@ 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.sound.Sound; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; @@ -61,6 +62,8 @@ public enum ProtocolUtils { .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() + // general options + .value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE) // before 1.16 .value(JSONOptions.EMIT_RGB, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.VALUE_FIELD) @@ -79,6 +82,8 @@ public enum ProtocolUtils { .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() + // general options + .value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE) // after 1.16 .value(JSONOptions.EMIT_RGB, Boolean.TRUE) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE) @@ -98,6 +103,8 @@ public enum ProtocolUtils { .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() + // general options + .value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE) // after 1.16 .value(JSONOptions.EMIT_RGB, Boolean.TRUE) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE) @@ -117,6 +124,8 @@ public enum ProtocolUtils { .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( OptionSchema.globalSchema().stateBuilder() + // general options + .value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE) // after 1.16 .value(JSONOptions.EMIT_RGB, Boolean.TRUE) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.SNAKE_CASE) @@ -141,7 +150,7 @@ public enum ProtocolUtils { BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY}; private static final QuietDecoderException BAD_VARINT_CACHED = new QuietDecoderException("Bad VarInt decoded"); - private static final int[] VAR_INT_LENGTHS = new int[65]; + private static final int[] VAR_INT_LENGTHS = new int[33]; static { for (int i = 0; i <= 32; ++i) { @@ -241,16 +250,15 @@ public enum ProtocolUtils { } /** - * Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}. + * Directly encodes a 21-bit Minecraft VarInt, ready to be written with {@link ByteBuf#writeMedium(int)}. * The upper 11 bits will be discarded. * - * @param buf the buffer to read from - * @param value the integer to write + * @param value the value to encode + * @return the encoded value */ - public static void write21BitVarInt(ByteBuf buf, int value) { + public static int encode21BitVarInt(int value) { // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ - int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); - buf.writeMedium(w); + return (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); } public static String readString(ByteBuf buf) { @@ -279,12 +287,22 @@ public enum ProtocolUtils { checkFrame(buf.isReadable(length), "Trying to read a string that is too long (wanted %s, only have %s)", length, buf.readableBytes()); - String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); - buf.skipBytes(length); + String str = buf.readString(length, StandardCharsets.UTF_8); checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap); return str; } + /** + * Determines the size of the written {@code str} if encoded as a VarInt-prefixed UTF-8 string. + * + * @param str the string to write + * @return the encoded size + */ + public static int stringSizeHint(CharSequence str) { + int size = ByteBufUtil.utf8Bytes(str); + return varIntBytes(size) + size; + } + /** * Writes the specified {@code str} to the {@code buf} with a VarInt prefix. * @@ -317,6 +335,16 @@ public enum ProtocolUtils { writeString(buf, key.asString()); } + /** + * Writes the key to the buffer, dropping the "minecraft:" namespace when present. + * + * @param buf the buffer to write to + * @param key the key to write + */ + public static void writeMinimalKey(ByteBuf buf, Key key) { + writeString(buf, key.asMinimalString()); + } + /** * Reads a standard Mojang Text namespaced:key array from the buffer. * @@ -782,6 +810,40 @@ public enum ProtocolUtils { return new IdentifiedKeyImpl(revision, key, expiry, signature); } + /** + * Reads a {@link Sound.Source} from the buffer. + * + * @param buf the buffer + * @param version the protocol version + * @return the sound source + */ + public static Sound.Source readSoundSource(ByteBuf buf, ProtocolVersion version) { + int ordinal = readVarInt(buf); + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5) + && ordinal == Sound.Source.UI.ordinal()) { + throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+"); + } + + return Sound.Source.values()[ordinal]; + } + + /** + * Writes a {@link Sound.Source} to the buffer. + * + * @param buf the buffer + * @param version the protocol version + * @param source the sound source to write + */ + public static void writeSoundSource(ByteBuf buf, ProtocolVersion version, Sound.Source source) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5) + && source == Sound.Source.UI) { + throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+"); + } + + writeVarInt(buf, source.ordinal()); + } + /** * Represents the direction in which a packet flows. */ 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 27abbfed..1da01780 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -41,6 +41,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_9; 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; @@ -58,7 +59,11 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; +import com.velocitypowered.proxy.protocol.packet.DialogClearPacket; +import com.velocitypowered.proxy.protocol.packet.DialogShowPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; @@ -81,6 +86,7 @@ import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; @@ -102,6 +108,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerComm import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket; +import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; @@ -184,6 +192,12 @@ public enum StateRegistry { KnownPacksPacket.class, KnownPacksPacket::new, map(0x07, MINECRAFT_1_20_5, false)); + serverbound.register(ServerboundCustomClickActionPacket.class, ServerboundCustomClickActionPacket::new, + map(0x08, MINECRAFT_1_21_6, false)); + serverbound.register( + CodeOfConductAcceptPacket.class, + () -> CodeOfConductAcceptPacket.INSTANCE, + map(0x09, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, @@ -238,6 +252,12 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_21, false)); clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, map(0x10, MINECRAFT_1_21, false)); + clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE, + map(0x11, MINECRAFT_1_21_6, false)); + clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this), + map(0x12, MINECRAFT_1_21_6, false)); + clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new, + map(0x13, MINECRAFT_1_21_9, false)); } }, PLAY { @@ -431,6 +451,26 @@ public enum StateRegistry { ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, map(0x16, MINECRAFT_1_20_5, false), map(0x15, MINECRAFT_1_21_5, false)); + clientbound.register( + ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new, + map(0x5D, MINECRAFT_1_19_3, true), + map(0x61, MINECRAFT_1_19_4, true), + map(0x63, MINECRAFT_1_20_2, true), + map(0x65, MINECRAFT_1_20_3, true), + map(0x67, MINECRAFT_1_20_5, true), + map(0x6E, MINECRAFT_1_21_2, true), + map(0x6D, MINECRAFT_1_21_5, true), + map(0x72, MINECRAFT_1_21_9, true)); + clientbound.register( + ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new, + map(0x5F, MINECRAFT_1_19_3, true), + map(0x63, MINECRAFT_1_19_4, true), + map(0x66, MINECRAFT_1_20_2, true), + map(0x68, MINECRAFT_1_20_3, true), + map(0x6A, MINECRAFT_1_20_5, true), + map(0x71, MINECRAFT_1_21_2, true), + map(0x70, MINECRAFT_1_21_5, true), + map(0x75, MINECRAFT_1_21_9, true)); clientbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -466,7 +506,8 @@ public enum StateRegistry { map(0x1A, MINECRAFT_1_19_4, false), map(0x1B, MINECRAFT_1_20_2, false), map(0x1D, MINECRAFT_1_20_5, false), - map(0x1C, MINECRAFT_1_21_5, false)); + map(0x1C, MINECRAFT_1_21_5, false), + map(0x20, MINECRAFT_1_21_9, false)); clientbound.register( KeepAlivePacket.class, KeepAlivePacket::new, @@ -485,7 +526,8 @@ public enum StateRegistry { map(0x24, MINECRAFT_1_20_2, false), map(0x26, MINECRAFT_1_20_5, false), map(0x27, MINECRAFT_1_21_2, false), - map(0x26, MINECRAFT_1_21_5, false)); + map(0x26, MINECRAFT_1_21_5, false), + map(0x2B, MINECRAFT_1_21_9, false)); clientbound.register( JoinGamePacket.class, JoinGamePacket::new, @@ -504,7 +546,8 @@ public enum StateRegistry { map(0x29, MINECRAFT_1_20_2, false), map(0x2B, MINECRAFT_1_20_5, false), map(0x2C, MINECRAFT_1_21_2, false), - map(0x2B, MINECRAFT_1_21_5, false)); + map(0x2B, MINECRAFT_1_21_5, false), + map(0x30, MINECRAFT_1_21_9, false)); clientbound.register( RespawnPacket.class, RespawnPacket::new, @@ -526,14 +569,16 @@ public enum StateRegistry { map(0x45, MINECRAFT_1_20_3, true), map(0x47, MINECRAFT_1_20_5, true), map(0x4C, MINECRAFT_1_21_2, true), - map(0x4B, MINECRAFT_1_21_5, true)); + map(0x4B, MINECRAFT_1_21_5, true), + map(0x50, MINECRAFT_1_21_9, true)); clientbound.register( RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, map(0x43, MINECRAFT_1_20_3, false), map(0x45, MINECRAFT_1_20_5, false), map(0x4A, MINECRAFT_1_21_2, false), - map(0x49, MINECRAFT_1_21_5, false)); + map(0x49, MINECRAFT_1_21_5, false), + map(0x4E, MINECRAFT_1_21_9, false)); clientbound.register( ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, @@ -555,7 +600,8 @@ public enum StateRegistry { map(0x44, MINECRAFT_1_20_3, false), map(0x46, MINECRAFT_1_20_5, false), map(0x4B, MINECRAFT_1_21_2, false), - map(0x4A, MINECRAFT_1_21_5, false)); + map(0x4A, MINECRAFT_1_21_5, false), + map(0x4F, MINECRAFT_1_21_9, false)); clientbound.register( HeaderAndFooterPacket.class, HeaderAndFooterPacket::new, @@ -578,7 +624,8 @@ public enum StateRegistry { map(0x6A, MINECRAFT_1_20_3, true), map(0x6D, MINECRAFT_1_20_5, true), map(0x74, MINECRAFT_1_21_2, true), - map(0x73, MINECRAFT_1_21_5, true)); + map(0x73, MINECRAFT_1_21_5, true), + map(0x78, MINECRAFT_1_21_9, true)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -600,7 +647,8 @@ public enum StateRegistry { map(0x61, MINECRAFT_1_20_3, true), map(0x63, MINECRAFT_1_20_5, true), map(0x6A, MINECRAFT_1_21_2, true), - map(0x69, MINECRAFT_1_21_5, true)); + map(0x69, MINECRAFT_1_21_5, true), + map(0x6E, MINECRAFT_1_21_9, true)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -613,7 +661,8 @@ public enum StateRegistry { map(0x63, MINECRAFT_1_20_3, true), map(0x65, MINECRAFT_1_20_5, true), map(0x6C, MINECRAFT_1_21_2, true), - map(0x6B, MINECRAFT_1_21_5, true)); + map(0x6B, MINECRAFT_1_21_5, true), + map(0x70, MINECRAFT_1_21_9, true)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -626,7 +675,8 @@ public enum StateRegistry { map(0x4A, MINECRAFT_1_20_3, true), map(0x4C, MINECRAFT_1_20_5, true), map(0x51, MINECRAFT_1_21_2, true), - map(0x50, MINECRAFT_1_21_5, true)); + map(0x50, MINECRAFT_1_21_5, true), + map(0x55, MINECRAFT_1_21_9, true)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -639,7 +689,8 @@ public enum StateRegistry { map(0x64, MINECRAFT_1_20_3, true), map(0x66, MINECRAFT_1_20_5, true), map(0x6D, MINECRAFT_1_21_2, true), - map(0x6C, MINECRAFT_1_21_5, true)); + map(0x6C, MINECRAFT_1_21_5, true), + map(0x71, MINECRAFT_1_21_9, true)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -669,7 +720,8 @@ public enum StateRegistry { map(0x3B, MINECRAFT_1_20_2, false), map(0x3D, MINECRAFT_1_20_5, false), map(0x3F, MINECRAFT_1_21_2, false), - map(0x3E, MINECRAFT_1_21_5, false)); + map(0x3E, MINECRAFT_1_21_5, false), + map(0x43, MINECRAFT_1_21_9, false)); clientbound.register( UpsertPlayerInfoPacket.class, UpsertPlayerInfoPacket::new, @@ -678,12 +730,14 @@ public enum StateRegistry { map(0x3C, MINECRAFT_1_20_2, false), map(0x3E, MINECRAFT_1_20_5, false), map(0x40, MINECRAFT_1_21_2, false), - map(0x3F, MINECRAFT_1_21_5, false)); + map(0x3F, MINECRAFT_1_21_5, false), + map(0x44, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, map(0x6B, MINECRAFT_1_20_5, false), map(0x72, MINECRAFT_1_21_2, false), - map(0x71, MINECRAFT_1_21_5, false)); + map(0x71, MINECRAFT_1_21_5, false), + map(0x76, MINECRAFT_1_21_9, false)); clientbound.register( SystemChatPacket.class, SystemChatPacket::new, @@ -695,7 +749,8 @@ public enum StateRegistry { map(0x69, MINECRAFT_1_20_3, true), map(0x6C, MINECRAFT_1_20_5, true), map(0x73, MINECRAFT_1_21_2, true), - map(0x72, MINECRAFT_1_21_5, true)); + map(0x72, MINECRAFT_1_21_5, true), + map(0x77, MINECRAFT_1_21_9, true)); clientbound.register( PlayerChatCompletionPacket.class, PlayerChatCompletionPacket::new, @@ -716,7 +771,8 @@ public enum StateRegistry { map(0x49, MINECRAFT_1_20_3, false), map(0x4B, MINECRAFT_1_20_5, false), map(0x50, MINECRAFT_1_21_2, false), - map(0x4F, MINECRAFT_1_21_5, false)); + map(0x4F, MINECRAFT_1_21_5, false), + map(0x54, MINECRAFT_1_21_9, false)); clientbound.register( StartUpdatePacket.class, () -> StartUpdatePacket.INSTANCE, @@ -724,7 +780,8 @@ public enum StateRegistry { map(0x67, MINECRAFT_1_20_3, false), map(0x69, MINECRAFT_1_20_5, false), map(0x70, MINECRAFT_1_21_2, false), - map(0x6F, MINECRAFT_1_21_5, false)); + map(0x6F, MINECRAFT_1_21_5, false), + map(0x74, MINECRAFT_1_21_9, false)); clientbound.register( BundleDelimiterPacket.class, () -> BundleDelimiterPacket.INSTANCE, @@ -733,16 +790,20 @@ public enum StateRegistry { TransferPacket.class, TransferPacket::new, map(0x73, MINECRAFT_1_20_5, false), - map(0x7A, MINECRAFT_1_21_2, false)); + map(0x7A, MINECRAFT_1_21_2, false), + map(0x7F, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, map(0x7A, MINECRAFT_1_21, false), - map(0x81, MINECRAFT_1_21_2, false)); + map(0x81, MINECRAFT_1_21_2, false), + map(0x86, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, map(0x7B, MINECRAFT_1_21, false), + map(0x82, MINECRAFT_1_21_2, false), + map(0x87, MINECRAFT_1_21_9, false)); map(0x82, MINECRAFT_1_21_2, false)); clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new, map(0x41, ProtocolVersion.MINECRAFT_1_9, true), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressorAndLengthEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressorAndLengthEncoder.java index 90952a72..990d1732 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressorAndLengthEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressorAndLengthEncoder.java @@ -46,7 +46,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder expectedMaxLen) { throw handleOverflow(packet, expectedMaxLen, buf.readableBytes()); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java index 7133f1d2..4af366ce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java @@ -54,6 +54,19 @@ public class MinecraftEncoder extends MessageToByteEncoder { msg.encode(out, direction, registry.version); } + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, MinecraftPacket msg, + boolean preferDirect) throws Exception { + int hint = msg.encodeSizeHint(direction, registry.version); + if (hint < 0) { + return super.allocateBuffer(ctx, msg, preferDirect); + } + + int packetId = this.registry.getPacketId(msg); + int totalHint = ProtocolUtils.varIntBytes(packetId) + hint; + return preferDirect ? ctx.alloc().ioBuffer(totalHint) : ctx.alloc().heapBuffer(totalHint); + } + public void setProtocolVersion(final ProtocolVersion protocolVersion) { this.registry = state.getProtocolRegistry(direction, protocolVersion); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 5450390b..7d4d8d6d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java @@ -83,9 +83,8 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { // try to read the length of the packet in.markReaderIndex(); - int preIndex = in.readerIndex(); int length = readRawVarInt21(in); - if (preIndex == in.readerIndex()) { + if (packetStart == in.readerIndex()) { return; } if (length < 0) { @@ -94,38 +93,9 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { if (length > 0) { if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) { - StateRegistry.PacketRegistry.ProtocolRegistry registry = - state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION); - - final int index = in.readerIndex(); - final int packetId = readRawVarInt21(in); - // Index hasn't changed, we've read nothing - if (index == in.readerIndex()) { - in.resetReaderIndex(); + if (validateServerboundHandshakePacket(in, length)) { return; } - final int payloadLength = length - ProtocolUtils.varIntBytes(packetId); - - MinecraftPacket packet = registry.createPacket(packetId); - - // We handle every packet in this phase, if you said something we don't know, something is really wrong - if (packet == null) { - throw UNKNOWN_PACKET; - } - - // We 'technically' have the incoming bytes of a payload here, and so, these can actually parse - // the packet if needed, so, we'll take advantage of the existing methods - int expectedMinLen = packet.expectedMinLength(in, direction, registry.version); - int expectedMaxLen = packet.expectedMaxLength(in, direction, registry.version); - if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) { - throw handleOverflow(packet, expectedMaxLen, in.readableBytes()); - } - if (payloadLength < expectedMinLen) { - throw handleUnderflow(packet, expectedMaxLen, in.readableBytes()); - } - - - in.readerIndex(index); } } @@ -139,6 +109,41 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { } } + private boolean validateServerboundHandshakePacket(ByteBuf in, int length) throws Exception { + StateRegistry.PacketRegistry.ProtocolRegistry registry = + state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION); + + final int index = in.readerIndex(); + final int packetId = readRawVarInt21(in); + // Index hasn't changed, we've read nothing + if (index == in.readerIndex()) { + in.resetReaderIndex(); + return true; + } + final int payloadLength = length - ProtocolUtils.varIntBytes(packetId); + + MinecraftPacket packet = registry.createPacket(packetId); + + // We handle every packet in this phase, if you said something we don't know, something is really wrong + if (packet == null) { + throw UNKNOWN_PACKET; + } + + // We 'technically' have the incoming bytes of a payload here, and so, these can actually parse + // the packet if needed, so, we'll take advantage of the existing methods + int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version); + int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version); + if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) { + throw handleOverflow(packet, expectedMaxLen, in.readableBytes()); + } + if (payloadLength < expectedMinLen) { + throw handleUnderflow(packet, expectedMaxLen, in.readableBytes()); + } + + in.readerIndex(index); + return false; + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (MinecraftDecoder.DEBUG) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java index 08fe23b2..d3fc3a82 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java @@ -362,4 +362,12 @@ public class AvailableCommandsPacket implements MinecraftPacket { return builder.buildFuture(); } } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + // This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of + // 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized + // amount. + return 128 * 1024; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java new file mode 100644 index 00000000..459f1430 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2025 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 io.netty.buffer.ByteBuf; +import net.kyori.adventure.sound.Sound; +import org.jetbrains.annotations.Nullable; + +import java.util.Random; + +public class ClientboundSoundEntityPacket implements MinecraftPacket { + + private static final Random SEEDS_RANDOM = new Random(); + + private Sound sound; + private @Nullable Float fixedRange; + private int emitterEntityId; + + public ClientboundSoundEntityPacket() {} + + public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) { + this.sound = sound; + this.fixedRange = fixedRange; + this.emitterEntityId = emitterEntityId; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("Decode is not implemented"); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID + + ProtocolUtils.writeMinimalKey(buf, sound.name()); + + buf.writeBoolean(fixedRange != null); + if (fixedRange != null) + buf.writeFloat(fixedRange); + + ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source()); + + ProtocolUtils.writeVarInt(buf, emitterEntityId); + + buf.writeFloat(sound.volume()); + + buf.writeFloat(sound.pitch()); + + buf.writeLong(sound.seed().orElse(SEEDS_RANDOM.nextLong())); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public Sound getSound() { + return sound; + } + + public void setSound(Sound sound) { + this.sound = sound; + } + + public @Nullable Float getFixedRange() { + return fixedRange; + } + + public void setFixedRange(@Nullable Float fixedRange) { + this.fixedRange = fixedRange; + } + + public int getEmitterEntityId() { + return emitterEntityId; + } + + public void setEmitterEntityId(int emitterEntityId) { + this.emitterEntityId = emitterEntityId; + } + +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java new file mode 100644 index 00000000..3e085d38 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2025 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 io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.sound.SoundStop; + +import javax.annotation.Nullable; + +public class ClientboundStopSoundPacket implements MinecraftPacket { + + private @Nullable Sound.Source source; + private @Nullable Key soundName; + + public ClientboundStopSoundPacket() {} + + public ClientboundStopSoundPacket(SoundStop soundStop) { + this(soundStop.source(), soundStop.sound()); + } + + public ClientboundStopSoundPacket(@Nullable Sound.Source source, @Nullable Key soundName) { + this.source = source; + this.soundName = soundName; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + int flagsBitmask = buf.readByte(); + + if ((flagsBitmask & 1) != 0) { + source = ProtocolUtils.readSoundSource(buf, protocolVersion); + } else { + source = null; + } + + if ((flagsBitmask & 2) != 0) { + soundName = ProtocolUtils.readKey(buf); + } else { + soundName = null; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + int flagsBitmask = 0; + if (source != null && soundName == null) { + flagsBitmask |= 1; + } else if (soundName != null && source == null) { + flagsBitmask |= 2; + } else if (source != null /*&& sound != null*/) { + flagsBitmask |= 3; + } + + buf.writeByte(flagsBitmask); + + if (source != null) { + ProtocolUtils.writeSoundSource(buf, protocolVersion, source); + } + + if (soundName != null) { + ProtocolUtils.writeMinimalKey(buf, soundName); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + @Nullable + public Sound.Source getSource() { + return source; + } + + public void setSource(@Nullable Sound.Source source) { + this.source = source; + } + + @Nullable + public Key getSoundName() { + return soundName; + } + + public void setSoundName(@Nullable Key soundName) { + this.soundName = soundName; + } + +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java new file mode 100644 index 00000000..4188abfd --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2025 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.Direction; +import io.netty.buffer.ByteBuf; + +public class DialogClearPacket implements MinecraftPacket { + + public static final DialogClearPacket INSTANCE = new DialogClearPacket(); + + private DialogClearPacket() { + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java new file mode 100644 index 00000000..67d4b8f8 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018-2025 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 com.velocitypowered.proxy.protocol.StateRegistry; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; + +public class DialogShowPacket implements MinecraftPacket { + + private final StateRegistry state; + private int id; + private BinaryTag nbt; + + public DialogShowPacket(final StateRegistry state) { + this.state = state; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.id = this.state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf); + if (this.id == 0) { + this.nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader()); + } + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (this.state == StateRegistry.CONFIG) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt); + } else { + ProtocolUtils.writeVarInt(buf, this.id); + if (this.id == 0) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt); + } + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java index d6591e7b..fedb67a5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java @@ -107,7 +107,7 @@ public class EncryptionResponsePacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { // It turns out these come out to the same length, whether we're talking >=1.8 or not. // The length prefix always winds up being 2 bytes. int base = 256 + 2 + 2; @@ -123,8 +123,8 @@ public class EncryptionResponsePacket implements MinecraftPacket { } @Override - public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - int base = expectedMaxLength(buf, direction, version); + public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + int base = decodeExpectedMaxLength(buf, direction, version); if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { // These are "optional" base -= 128 + 8; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java index 9eb7a93e..88cb3688 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java @@ -24,6 +24,7 @@ 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; public class HandshakePacket implements MinecraftPacket { @@ -108,14 +109,21 @@ public class HandshakePacket implements MinecraftPacket { } @Override - public int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 7; } @Override - public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable + // sizes of this packet. + return 4 * 1024; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java index dad9e08d..16cf519b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java @@ -36,7 +36,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 0; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java index 682785eb..2fa82e92 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java @@ -21,6 +21,7 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -86,4 +87,9 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java index dee33c13..e7d9443d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java @@ -21,6 +21,7 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -88,4 +89,9 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java index d78ed833..ecf2887f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java @@ -23,6 +23,7 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -143,4 +144,9 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr public PluginMessagePacket touch(Object hint) { return (PluginMessagePacket) super.touch(hint); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java index 610462a9..325a3c9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java @@ -22,6 +22,7 @@ import com.velocitypowered.api.util.Favicon; 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 com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.Nullable; @@ -121,4 +122,9 @@ public class ServerDataPacket implements MinecraftPacket { public void setSecureChatEnforced(boolean secureChatEnforced) { this.secureChatEnforced = secureChatEnforced; } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return 8 * 1024; + } } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java index 74eabb5c..65693cd8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java @@ -150,7 +150,7 @@ public class ServerLoginPacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically // legal on the protocol level. int base = 1 + (16 * 3); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java index 1e70568f..322cd9b1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.util.UuidUtils; 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 com.velocitypowered.proxy.util.VelocityProperties; import io.netty.buffer.ByteBuf; import java.util.List; @@ -132,4 +133,11 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable + // sizes of this packet. + return 4 * 1024; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java new file mode 100644 index 00000000..6b846c23 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2025 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; +import io.netty.buffer.ByteBuf; + +public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket { + + public ServerboundCustomClickActionPacket() { + super(null); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + replace(buf.readRetainedSlice(buf.readableBytes())); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + buf.writeBytes(content()); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java index 15999e61..30236704 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java @@ -44,12 +44,12 @@ public class StatusPingPacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { return 8; } @Override - public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { return 8; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java index 46dc0401..870d9909 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java @@ -53,7 +53,7 @@ public class StatusRequestPacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { return 0; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java index 8591fa03..20fada4b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java @@ -21,6 +21,7 @@ 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 org.checkerframework.checker.nullness.qual.Nullable; @@ -66,4 +67,9 @@ public class StatusResponsePacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return ProtocolUtils.stringSizeHint(this.status); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java index 35bf4a96..9ef40ef0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java @@ -36,6 +36,8 @@ import org.jetbrains.annotations.Nullable; public class UpsertPlayerInfoPacket implements MinecraftPacket { + private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants(); + private final EnumSet actions; private final List entries; @@ -85,14 +87,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - Action[] actions = Action.class.getEnumConstants(); - byte[] bytes = new byte[-Math.floorDiv(-actions.length, 8)]; + byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)]; buf.readBytes(bytes); BitSet actionSet = BitSet.valueOf(bytes); - for (int idx = 0; idx < actions.length; idx++) { + for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { if (actionSet.get(idx)) { - addAction(actions[idx]); + addAction(ALL_ACTIONS[idx]); } } @@ -109,14 +110,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - Action[] actions = Action.class.getEnumConstants(); - BitSet set = new BitSet(actions.length); - for (int idx = 0; idx < actions.length; idx++) { - set.set(idx, this.actions.contains(actions[idx])); + BitSet set = new BitSet(ALL_ACTIONS.length); + for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { + set.set(idx, this.actions.contains(ALL_ACTIONS[idx])); } byte[] bytes = set.toByteArray(); - buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-actions.length, 8))); + buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8))); ProtocolUtils.writeVarInt(buf, this.entries.size()); for (Entry entry : this.entries) { @@ -133,12 +133,6 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket { return handler.handle(this); } - public BitSet readFixedBitSet(ByteBuf buf, int param0) { - byte[] var0 = new byte[-Math.floorDiv(-param0, 8)]; - buf.readBytes(var0); - return BitSet.valueOf(var0); - } - public enum Action { ADD_PLAYER((ignored, buf, info) -> { // read info.profile = new GameProfile( 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 197fddf3..203ab375 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 @@ -45,6 +45,8 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; @@ -87,9 +89,6 @@ public class ArgumentPropertyRegistry { ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion); ArgumentPropertySerializer serializer = byIdentifier.get(identifier); - if (serializer == null) { - throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown."); - } Object result = serializer.deserialize(buf, protocolVersion); if (result instanceof ArgumentType) { @@ -156,7 +155,7 @@ public class ArgumentPropertyRegistry { * @param protocolVersion the protocol version to use * @return the identifier read from the buffer */ - public static ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) { + public static @NotNull ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) { if (protocolVersion.noLessThan(MINECRAFT_1_19)) { int id = ProtocolUtils.readVarInt(buf); for (ArgumentIdentifier i : byIdentifier.keySet()) { @@ -173,8 +172,8 @@ public class ArgumentPropertyRegistry { return i; } } + throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown."); } - return null; } static { 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 index 22b49621..01f36d0e 100644 --- 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 @@ -87,7 +87,7 @@ public class ComponentHolder { } catch (Exception ex) { logger.error( "Error converting binary component to JSON component! " - + "Binary: " + binaryTag + " JSON: " + json, ex); + + "Binary: " + binaryTag + " JSON: " + json, ex); throw ex; } } @@ -112,7 +112,7 @@ public class ComponentHolder { public static BinaryTag serialize(JsonElement json) { if (json instanceof JsonPrimitive jsonPrimitive) { - if (jsonPrimitive.isNumber()) { + if (jsonPrimitive.isNumber()) { Number number = json.getAsNumber(); if (number instanceof Byte) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java index 40a2fef4..d8ab07b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java @@ -41,7 +41,8 @@ public class SessionPlayerCommandPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.command = ProtocolUtils.readString(buf, 256); + int cap = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5) ? 256 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE; + this.command = ProtocolUtils.readString(buf, cap); this.timeStamp = Instant.ofEpochMilli(buf.readLong()); this.salt = buf.readLong(); this.argumentSignatures = new ArgumentSignatures(buf); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java index cb5ac3c4..915f0cfb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java @@ -28,7 +28,7 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.command = ProtocolUtils.readString(buf, 256); + this.command = ProtocolUtils.readString(buf, ProtocolUtils.DEFAULT_MAX_STRING_SIZE); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java new file mode 100644 index 00000000..e9811f9f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018-2025 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.config; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; + +public class CodeOfConductAcceptPacket implements MinecraftPacket { + + public static final CodeOfConductAcceptPacket INSTANCE = new CodeOfConductAcceptPacket(); + + private CodeOfConductAcceptPacket() { + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java new file mode 100644 index 00000000..41433307 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018-2025 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.config; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; +import io.netty.buffer.ByteBuf; + +public class CodeOfConductPacket extends DeferredByteBufHolder implements MinecraftPacket { + + public CodeOfConductPacket() { + super(null); + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.replace(buf.readRetainedSlice(buf.readableBytes())); + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + buf.writeBytes(this.content()); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java index b4d93ec4..20d40fd4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java @@ -40,7 +40,7 @@ public class FinishedUpdatePacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 0; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java index ef0ee47a..2d9ed230 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java @@ -21,6 +21,7 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; @@ -47,4 +48,9 @@ public class RegistrySyncPacket extends DeferredByteBufHolder implements Minecra public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + return content().readableBytes(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java index 8d4585d8..d41265ce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java @@ -40,7 +40,7 @@ public class StartUpdatePacket implements MinecraftPacket { } @Override - public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { return 0; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java index 6d462fb2..c0cf414e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java @@ -22,6 +22,7 @@ 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.Map; @@ -79,4 +80,22 @@ public class TagsUpdatePacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int encodeSizeHint(Direction direction, ProtocolVersion version) { + int size = ProtocolUtils.varIntBytes(tags.size()); + for (Map.Entry> entry : tags.entrySet()) { + size += ProtocolUtils.stringSizeHint(entry.getKey()); + size += ProtocolUtils.varIntBytes(entry.getValue().size()); + for (Map.Entry innerEntry : entry.getValue().entrySet()) { + size += ProtocolUtils.stringSizeHint(innerEntry.getKey()); + size += ProtocolUtils.varIntBytes(innerEntry.getValue().length); + for (int innerEntryValue : innerEntry.getValue()) { + size += ProtocolUtils.varIntBytes(innerEntryValue); + } + } + } + + return size; + } } diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ar_SA.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ar_SA.properties index 103ae08a..8f79f570 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ar_SA.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ar_SA.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=هناك {0} لاعبين متصلون با velocity.command.glist-view-all=لعرض اللاعبين على جميع السيرفرات استخدم /glist all velocity.command.reload-success=تم إعادة تحميل إعدادات Velocity بنجاح. velocity.command.reload-failure=فشلت إعادة تحميل إعدادات Velocity. تفقد الـconsole للمزيد من التفاصيل. -velocity.command.version-copyright=حقوق الطبع والنشر 2018-2023 {0}. {1} مرخصة بموجب شروط الإصدار الثالث لرخصة GNU العامة (GPLv3). +velocity.command.version-copyright=حقوق الطبع والنشر 2018-{2} {0}. {1} مرخصة بموجب شروط الإصدار الثالث لرخصة GNU العامة (GPLv3). velocity.command.no-plugins=لا توجد إضافات مثبتة على Velocity. velocity.command.plugins-list=الإضافات\: {0} velocity.command.plugin-tooltip-website=موقعها\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_bg_BG.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_bg_BG.properties index b5a12e46..c554fa3d 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_bg_BG.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_bg_BG.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} играчи са свързани къ velocity.command.glist-view-all=За да видите всички играчи, разпределени по сървъри, използвайте /glist all. velocity.command.reload-success=Настройките на Velocity бяха презаредени успешно. velocity.command.reload-failure=Не успяхме да презаредим настройките на Velocity. Моля, проверете конзолата за повече информация. -velocity.command.version-copyright=Авторско право 2018-2023 {0}. {1} е лицензиран под условията на GNU General Public License v3. +velocity.command.version-copyright=Авторско право 2018-{2} {0}. {1} е лицензиран под условията на GNU General Public License v3. velocity.command.no-plugins=За момента няма инсталирани добавки. velocity.command.plugins-list=Добавки\: {0} velocity.command.plugin-tooltip-website=Уебсайт\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_cs_CZ.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_cs_CZ.properties index 99d4f38a..980e4591 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_cs_CZ.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_cs_CZ.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=Počet hráčů připojených k tomuto prox velocity.command.glist-view-all=Ke zobrazení všech hráčů na všech serverech použij /glist all. velocity.command.reload-success=Konfigurace Velocity úspěšně načtena. velocity.command.reload-failure=Nebylo možné načíst konfiguraci Velocity. Podrobnosti jsou na konzoli. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} je licencovaný pod podmínkami GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} je licencovaný pod podmínkami GNU General Public License v3. velocity.command.no-plugins=V tuto chvíli nejsou nainstalovány žádné zásuvné moduly. velocity.command.plugins-list=Zásuvné moduly\: {0} velocity.command.plugin-tooltip-website=Webová stránka\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_da_DK.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_da_DK.properties index c593076e..1a82cbdf 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_da_DK.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_da_DK.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} spillere er i øjeblikket forbundet til velocity.command.glist-view-all=For at se alle spillere på servere, brug /glist all. velocity.command.reload-success=Velocity konfiguration blev genindlæst. velocity.command.reload-failure=Kan ikke genindlæse din Velocity konfiguration. Tjek konsollen for flere detaljer. -velocity.command.version-copyright=Ophavsret 2018-2023 {0}. {1} er licenseret under betingelserne i GNU General Public License v3. +velocity.command.version-copyright=Ophavsret 2018-{2} {0}. {1} er licenseret under betingelserne i GNU General Public License v3. velocity.command.no-plugins=Der er ingen plugins installeret i øjeblikket. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Hjemmeside\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_de_DE.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_de_DE.properties index f2d6872a..4de00f9e 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_de_DE.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_de_DE.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} Spieler sind derzeit mit dem Proxy verb velocity.command.glist-view-all=Um alle Spieler auf Servern aufzulisten, verwende /glist all. velocity.command.reload-success=Velocity-Konfiguration erfolgreich neu geladen. velocity.command.reload-failure=Die Velocity-Konfiguration konnte nicht neu geladen werden. Prüfe die Konsole für weitere Details. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} ist lizenziert unter den Bedingungen der GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} ist lizenziert unter den Bedingungen der GNU General Public License v3. velocity.command.no-plugins=Es sind derzeit keine Plugins installiert. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Webseite\: {0} 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 a99efb44..0d484bc9 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 @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} jugadores están conectados al proxy. velocity.command.glist-view-all=Para ver todos los jugadores por servidores, usa /glist all. velocity.command.reload-success=La configuración de Velocity ha sido recargada correctamente. velocity.command.reload-failure=No ha sido posible recargar la configuración de Velocity. Para obtener más información, revisa la consola. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} está licenciado bajo los términos de la Licencia Pública General de GNU v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} está licenciado bajo los términos de la Licencia Pública General de GNU v3. velocity.command.no-plugins=Actualmente no hay plugins instalados. velocity.command.plugins-list=Complementos\: {0} velocity.command.plugin-tooltip-website=Página web\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_et_EE.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_et_EE.properties index 246419b9..0e4ea63d 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_et_EE.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_et_EE.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} players are currently connected to the velocity.command.glist-view-all=Et näha kõiki mängijaid kõikides serverites, kasuta käsklust /glist all. velocity.command.reload-success=Velocity seadistus edukalt taaslaetud. velocity.command.reload-failure=Unable to reload your Velocity configuration. Check the console for more details. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=There are no plugins currently installed. velocity.command.plugins-list=Pluginad\: {0} velocity.command.plugin-tooltip-website=Veebileht\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fi_FI.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fi_FI.properties index 84b6d593..39c8f374 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fi_FI.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fi_FI.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} pelaajaa on tällä hetkellä yhdistän velocity.command.glist-view-all=Nähdäksesi pelaajat kaikilla palvelimilla, käytä komentoa /glist all. velocity.command.reload-success=Velocityn konfiguraatio uudelleenladattiin onnistuneesti. velocity.command.reload-failure=Velocityn konfiguraation uudelleenlataus epäonnistui. Katso tarkemmat lisätiedot konsolista. -velocity.command.version-copyright=Tekijänoikeus 2018-2023 {0}. {1} on lisensoitu GNU General Public License v3\:n ehtojen mukaisesti. +velocity.command.version-copyright=Tekijänoikeus 2018-{2} {0}. {1} on lisensoitu GNU General Public License v3\:n ehtojen mukaisesti. velocity.command.no-plugins=Yhtäkään pluginia ei ole asennettu. velocity.command.plugins-list=Pluginit\: {0} velocity.command.plugin-tooltip-website=Verkkosivu\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fr_FR.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fr_FR.properties index da990209..64fa63ee 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fr_FR.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_fr_FR.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} joueurs sont actuellement connectés au velocity.command.glist-view-all=Pour afficher tous les joueurs connectés aux serveurs, utilisez /glist all. velocity.command.reload-success=Configuration de Velocity rechargée avec succès. velocity.command.reload-failure=Impossible de recharger votre configuration de Velocity. Consultez la console pour plus de détails. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} est sous la licence GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} est sous la licence GNU General Public License v3. velocity.command.no-plugins=Il n'y a aucun plugin actuellement installé. velocity.command.plugins-list=Plugins \: {0} velocity.command.plugin-tooltip-website=Site Internet \: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_he_IL.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_he_IL.properties index 3b0b8c69..85cd9f99 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_he_IL.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_he_IL.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} שחקנים מחוברים כעת ל- velocity.command.glist-view-all=כדי להציג את כל השחקנים בשרתים, השתמש ב- glist all/. velocity.command.reload-success=תצורת Velocity נטענה מחדש בהצלחה. velocity.command.reload-failure=אין אפשרות לטעון מחדש את תצורת ה- Velocity שלך. עיין בקונסולה לקבלת פרטים נוספים. -velocity.command.version-copyright=זכויות יוצרים 2018-2023 {0}. {1} מורשה על פי תנאי v3 הרישיון הציבורי הכללי של GNU. +velocity.command.version-copyright=זכויות יוצרים 2018-{2} {0}. {1} מורשה על פי תנאי v3 הרישיון הציבורי הכללי של GNU. velocity.command.no-plugins=לא מותקנים כעת תוספים. velocity.command.plugins-list=תוספים\: {0} velocity.command.plugin-tooltip-website=אתר אינטרנט\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_hu_HU.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_hu_HU.properties index e5cb5b1c..b43f69b5 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_hu_HU.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_hu_HU.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} játékos van jelenleg csatlakozva a pr velocity.command.glist-view-all=Hogy megnézd az összes játékost az összes szerveren, használd a /glist all parancsot. velocity.command.reload-success=A Velocity beállításai sikeresen frissítve lettek. velocity.command.reload-failure=Hiba történt a Velocity beállításainak frissítése közben. Több információt a konzolban találsz. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} licenszelve van a GNU General Public License v3 alatt. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} licenszelve van a GNU General Public License v3 alatt. velocity.command.no-plugins=Jelenleg egyetlen plugin sincs telepítve. velocity.command.plugins-list=Pluginok\: {0} velocity.command.plugin-tooltip-website=Weboldal\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_it_IT.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_it_IT.properties index 0c72f5e1..d2a79886 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_it_IT.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_it_IT.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} giocatori sono attualmente connessi al velocity.command.glist-view-all=Per visualizzare tutti i giocatori sui server, usa /glist all. velocity.command.reload-success=La configurazione di Velocity è stata ricaricata con successo. velocity.command.reload-failure=Impossibile ricaricare la configurazione di Velocity. Controlla la console per maggiori dettagli. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} è concesso in licenza secondo i termini della GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} è concesso in licenza secondo i termini della GNU General Public License v3. velocity.command.no-plugins=Non ci sono plugin installati. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Sito web\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ja_JP.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ja_JP.properties index a16b0605..235de499 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ja_JP.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ja_JP.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} 人が現在プロキシに接続して velocity.command.glist-view-all=サーバー上のすべてのプレイヤーを表示するには、/glist allを使用してください。 velocity.command.reload-success=Velocityの設定が再読み込みされました。 velocity.command.reload-failure=Velocityの設定を再読み込みできません。詳細はコンソールで確認してください。 -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} は、GNU General Public License v3に基づいてライセンスされています。 +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} は、GNU General Public License v3に基づいてライセンスされています。 velocity.command.no-plugins=現在インストールされているプラグインはありません。 velocity.command.plugins-list=プラグイン\: {0} velocity.command.plugin-tooltip-website=ウェブサイト\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ko_KR.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ko_KR.properties index b4bbf4c8..e7e528cc 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ko_KR.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ko_KR.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=플레이어 {0}명이 현재 프록시에 velocity.command.glist-view-all=서버에 있는 모든 플레이어를 보려면, /glist all을 사용하세요. velocity.command.reload-success=Velocity 설정을 성공적으로 다시 불러왔습니다. velocity.command.reload-failure=Velocity 설정을 다시 불러올 수 없습니다. 자세한 내용은 콘솔을 확인하세요. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1}은(는) GNU General Public License v3 라이센스의 약관을 따릅니다. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1}은(는) GNU General Public License v3 라이센스의 약관을 따릅니다. velocity.command.no-plugins=설치된 플러그인이 없습니다. velocity.command.plugins-list=플러그인\: {0} velocity.command.plugin-tooltip-website=웹사이트\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nb_NO.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nb_NO.properties index ef7794e5..05a17545 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nb_NO.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nb_NO.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} spillere er for øyeblikket tilkoblet d velocity.command.glist-view-all=For å se alle tilkoblede spillere, utfør /glist all. velocity.command.reload-success=Velocity konfigurasjonen ble lastet inn på nytt. velocity.command.reload-failure=Kan ikke laste inn Velocity konfigurasjonen din. Sjekk konsollen for mer detaljer. -velocity.command.version-copyright=Opphavsrett 2018-2023 {0}. {1} er lisensiert under betingelsene av GNU General Public License v3. +velocity.command.version-copyright=Opphavsrett 2018-{2} {0}. {1} er lisensiert under betingelsene av GNU General Public License v3. velocity.command.no-plugins=Ingen plugin er installert. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Hjemmeside\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nl_NL.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nl_NL.properties index 44d12b08..cbb5f6b8 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nl_NL.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nl_NL.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} spelers zijn momenteel verbonden met de velocity.command.glist-view-all=Om alle spelers op de servers te bekijken, gebruik /glist all. velocity.command.reload-success=Velocity configuratie succesvol herladen. velocity.command.reload-failure=De Velocity-configuratie kan niet opnieuw worden geladen. Kijk in de console voor meer details. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is gelicentieerd onder de voorwaarden van de GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is gelicentieerd onder de voorwaarden van de GNU General Public License v3. velocity.command.no-plugins=Er zijn momenteel geen plugins geïnstalleerd. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nn_NO.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nn_NO.properties index eb6bfa38..b23ce960 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nn_NO.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_nn_NO.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} spelare er for augneblinken tilkopla de velocity.command.glist-view-all=For å sjå alle tilkopla spelarar, gjer /glist all. velocity.command.reload-success=Velocity konfigurasjonen blei lasta inn på nytt. velocity.command.reload-failure=Kan ikkje lasta inn Velocity konfigurasjonen din. Sjekk konsollen for meir detaljar. -velocity.command.version-copyright=Opphavsrett 2018-2023 {0}. {1} er lisensiert under vilkåra av GNU General Public License v3. +velocity.command.version-copyright=Opphavsrett 2018-{2} {0}. {1} er lisensiert under vilkåra av GNU General Public License v3. velocity.command.no-plugins=Det er ingen plugins installert. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Heimeside\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pl_PL.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pl_PL.properties index c3e2cfcf..02a90f87 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pl_PL.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pl_PL.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=Z tym proxy jest obecnie połączonych {0} velocity.command.glist-view-all=Aby zobaczyć wszystkich graczy na wszystkich serwerach, użyj /glist all. velocity.command.reload-success=Konfiguracja Velocity została pomyślnie załadowana ponownie. velocity.command.reload-failure=Nie udało się ponownie załadować twojej konfiguracji Velocity. Sprawdź konsolę po więcej szczegółów. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} jest objęty licencją na warunkach GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} jest objęty licencją na warunkach GNU General Public License v3. velocity.command.no-plugins=Obecnie nie ma zainstalowanych żadnych pluginów. velocity.command.plugins-list=Pluginy\: {0} velocity.command.plugin-tooltip-website=Strona internetowa\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pt_BR.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pt_BR.properties index 5a915a8d..e0708942 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pt_BR.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_pt_BR.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} jogadores estão atualmente conectados velocity.command.glist-view-all=Para ver todos os jogadores em todos os servidores, use /glist all. velocity.command.reload-success=Configuração do Velocity recarregada com êxito. velocity.command.reload-failure=Não foi possível recarregar a configuração do Velocity. Verifique o console para mais detalhes. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} está licenciado sobre os termos da Licença Pública Geral GNU v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} está licenciado sobre os termos da Licença Pública Geral GNU v3. velocity.command.no-plugins=Não há plugins instalados atualmente. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Site\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ro_RO.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ro_RO.properties index 8f28b91b..ea0d4609 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ro_RO.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ro_RO.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} players are currently connected to the velocity.command.glist-view-all=To view all players on servers, use /glist all. velocity.command.reload-success=Velocity configuration successfully reloaded. velocity.command.reload-failure=Unable to reload your Velocity configuration. Check the console for more details. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=There are no plugins currently installed. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ru_RU.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ru_RU.properties index a2a7afb9..5e5be8e6 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ru_RU.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_ru_RU.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} игрок(а, ов) подключен velocity.command.glist-view-all=Чтобы просмотреть всех игроков на серверах, используйте /glist all. velocity.command.reload-success=Конфигурация Velocity успешно перезагружена. velocity.command.reload-failure=Не удалось перезагрузить конфигурацию Velocity. Проверьте консоль для получения более подробной информации. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} лицензирована на условиях GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} лицензирована на условиях GNU General Public License v3. velocity.command.no-plugins=Ни одного плагина не установлено. velocity.command.plugins-list=Плагины\: {0} velocity.command.plugin-tooltip-website=Веб-сайт\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sk_SK.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sk_SK.properties index f7a89585..04e75974 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sk_SK.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sk_SK.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} hráčov je pripojených na tento proxy velocity.command.glist-view-all=Pre zobrazenie všetkých hráčov na všetkých serveroch použi /glist all. velocity.command.reload-success=Konfigurácia Velocity úspešne načítaná. velocity.command.reload-failure=Nepodarilo sa načítať konfiguráciu Velocity. Pozri sa do konzoly pre viac informácií. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} je licencovaný pod podmienkami GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} je licencovaný pod podmienkami GNU General Public License v3. velocity.command.no-plugins=Aktuálne nie sú nainštalované žiadne pluginy. velocity.command.plugins-list=Pluginy\: {0} velocity.command.plugin-tooltip-website=Webstránka\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sq_AL.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sq_AL.properties index 64e6b2b1..711b8e84 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sq_AL.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sq_AL.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} lojtarët janë aktualisht të lidhur m velocity.command.glist-view-all=Për të parë të gjithë lojtarët në servera, përdorni /glist all. velocity.command.reload-success=Konfigurimi i shpejtësisë u ringarkua me sukses. velocity.command.reload-failure=Nuk mund të ringarkohet konfigurimi i shpejtësisë. Kontrolloni konsolën për më shumë detaje. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=There are no plugins currently installed. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_CS.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_CS.properties index 6313a69b..006a3037 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_CS.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_CS.properties @@ -46,7 +46,7 @@ velocity.command.glist-player-plural={0} igrača je trenutno povezano na proxy. velocity.command.glist-view-all=Za pregled svih igrača na serverima koristite /glist all. velocity.command.reload-success=Velocity konfiguracija uspešno ponovno učitana. velocity.command.reload-failure=Nije moguće ponovo učitati Vašu Velocity konfiguraciju. Proverite konzolu za više detalja. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} je licenciran pod uslovima licence GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} je licenciran pod uslovima licence GNU General Public License v3. velocity.command.no-plugins=Trenutno nema instaliranih plugin-a. velocity.command.plugins-list=Plugin-i\: {0} velocity.command.plugin-tooltip-website=Web stranica\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_Latn.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_Latn.properties index 313aa913..6106bb7b 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_Latn.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sr_Latn.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} igrača je trenutno povezano na proxy. velocity.command.glist-view-all=Za pregled svih igrača na serverima koristite /glist all. velocity.command.reload-success=Velocity konfiguracija uspešno ponovno učitana. velocity.command.reload-failure=Nije moguće ponovo učitati Vašu Velocity konfiguraciju. Proverite konzolu za više detalja. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=Trenutno nema instaliranih plugin-a. velocity.command.plugins-list=Plugin-i\: {0} velocity.command.plugin-tooltip-website=Web stranica\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sv_SE.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sv_SE.properties index fca663fa..973506eb 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sv_SE.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_sv_SE.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} spelare är just nu ansluten till proxy velocity.command.glist-view-all=För att se alla spelare på servrar, använd /glist all. velocity.command.reload-success=Velocity konfigurationen har laddats om. velocity.command.reload-failure=Det gick inte att ladda om din Velocity konfiguration. Kontrollera konsolen för mer information. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} är licensierad enligt villkoren i GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} är licensierad enligt villkoren i GNU General Public License v3. velocity.command.no-plugins=Det finns inga tillägg installerade. velocity.command.plugins-list=Tillägg\: {0} velocity.command.plugin-tooltip-website=Webbsida\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tl_PH.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tl_PH.properties index a9e5c15b..ec4cc9d2 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tl_PH.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tl_PH.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} mga manlalaro nasa proxy. velocity.command.glist-view-all=Para sa makita ang mga lahat ng manlalaro sa mga serbidor, magamit ka ng /glist all. velocity.command.reload-success=Matagumpay ang reload ng konfigurasyon ng Velocity. velocity.command.reload-failure=Hindi kaya ang magreload ng konfigurasyon mo sa Velocity. Magsiyasat ang konsole para mas maraming detalye. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=Walang plugin nang naka-install. velocity.command.plugins-list=Mga plugin\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tr_TR.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tr_TR.properties index 5ae8c98d..3c255d93 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tr_TR.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_tr_TR.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=Şu anda sunucuya toplam {0} oyuncu bağlı velocity.command.glist-view-all=Sunucudaki bütün oyuncuları görüntülemek için /glist all komutunu kullan. velocity.command.reload-success=Velocity ayarları başarıyla güncellendi. velocity.command.reload-failure=Velocity ayarlarınız güncellenemiyor. Daha fazla bilgi için konsolu kontrol edin. -velocity.command.version-copyright=Talif hakkı 2018-2023 {0}. {1}, GNU General Public License v3 adı altında lisanslanmıştır. +velocity.command.version-copyright=Talif hakkı 2018-{2} {0}. {1}, GNU General Public License v3 adı altında lisanslanmıştır. velocity.command.no-plugins=Yüklenmiş herhangi bir eklenti yok. velocity.command.plugins-list=Eklentiler\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_uk_UA.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_uk_UA.properties index 18be5e9c..2400bd29 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_uk_UA.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_uk_UA.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} гравець(ця, ців) приєд velocity.command.glist-view-all=Щоб переглянути всіх гравців на серверах, використовуйте /glist all. velocity.command.reload-success=Конфігурація Velocity успішно перезавантажена. velocity.command.reload-failure=Не вдалося перезантажити конфігурацію Velocity. Перевірте консоль для подробиць. -velocity.command.version-copyright=Авторське право 2018-2023 {0}. {1} ліцензовано на умовах GNU General Public License v3. +velocity.command.version-copyright=Авторське право 2018-{2} {0}. {1} ліцензовано на умовах GNU General Public License v3. velocity.command.no-plugins=Плагіни відсутні. velocity.command.plugins-list=Плагіни\: {0} velocity.command.plugin-tooltip-website=Веб-сайт\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_vi_VN.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_vi_VN.properties index 7e46f191..5450e21e 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_vi_VN.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_vi_VN.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural={0} người chơi hiện đang kết nối velocity.command.glist-view-all=Để xem tất cả người chơi trên toàn bộ máy chủ, dùng /glist all. velocity.command.reload-success=Điều chỉnh Velocity đã tải lại thành công. velocity.command.reload-failure=Không thể tải lại điều chỉnh Velocity. Kiểm tra console để xem thêm chi tiết. -velocity.command.version-copyright=Copyright 2018-2023 {0}. {1} is licensed under the terms of the GNU General Public License v3. +velocity.command.version-copyright=Copyright 2018-{2} {0}. {1} is licensed under the terms of the GNU General Public License v3. velocity.command.no-plugins=Hiện tại không có plugin được cài đặt. velocity.command.plugins-list=Plugins\: {0} velocity.command.plugin-tooltip-website=Website\: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_CN.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_CN.properties index f26dea88..4b3b07d3 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_CN.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_CN.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=共有 {0} 名玩家已连接至此代理 velocity.command.glist-view-all=使用 /glist all 命令来查看所有服务器的全部玩家列表。 velocity.command.reload-success=Velocity 配置已成功重载。 velocity.command.reload-failure=无法重载 Velocity 配置,请查看控制台了解详情。 -velocity.command.version-copyright=Copyright 2018-2023 {0}。{1} 以 GNU 通用公共许可证第三版授权。 +velocity.command.version-copyright=Copyright 2018-{2} {0}。{1} 以 GNU 通用公共许可证第三版授权。 velocity.command.no-plugins=当前没有安装任何插件。 velocity.command.plugins-list=插件:{0} velocity.command.plugin-tooltip-website=网站:{0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_HK.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_HK.properties index dc58d3f8..37468347 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_HK.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_HK.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=共有 {0} 個玩家已連接至此代理 velocity.command.glist-view-all=使用 /glist all 命令來查看所有伺服器的玩家列表。 velocity.command.reload-success=Velocity 配置重新加載完成。 velocity.command.reload-failure=無法重新加載 Velocity 配置,請查看控制台了解詳情。 -velocity.command.version-copyright=Copyright 2018-2023 {0} ( {1} 的授權條款爲: GNU 通用公共授權條款第三版) +velocity.command.version-copyright=Copyright 2018-{2} {0} ( {1} 的授權條款爲: GNU 通用公共授權條款第三版) velocity.command.no-plugins=目前未有安裝任何 Velocity 插件。 velocity.command.plugins-list=插件: {0} velocity.command.plugin-tooltip-website=網站: {0} diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_TW.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_TW.properties index 49ed3916..b7fe1b5b 100644 --- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_TW.properties +++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_zh_TW.properties @@ -48,7 +48,7 @@ velocity.command.glist-player-plural=共有 {0} 名玩家已連線至此代理 velocity.command.glist-view-all=使用 /glist all 命令來查看所有伺服器的全部玩家列表。 velocity.command.reload-success=Velocity 配置已成功重載。 velocity.command.reload-failure=無法重載 Velocity 配置,請查看控制台了解詳情。 -velocity.command.version-copyright=Copyright 2018-2023 {0}。{1} 以 GNU 通用公共許可證第三版授權。 +velocity.command.version-copyright=Copyright 2018-{2} {0}。{1} 以 GNU 通用公共許可證第三版授權。 velocity.command.no-plugins=當前沒有安裝任何插件。 velocity.command.plugins-list=插件:{0} velocity.command.plugin-tooltip-website=網站:{0} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java index 9fe86335..fa282f71 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java @@ -18,14 +18,17 @@ package com.velocitypowered.proxy.command; import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.Suggestions; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.RawCommand; +import com.velocitypowered.api.command.SimpleCommand; import java.util.List; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; @@ -286,4 +289,26 @@ public class SuggestionsProviderTests extends CommandTestSuite { return ImmutableList.of(); } } + + @Test + void testSuggestionOffset() { + final var meta = manager.metaBuilder("offset").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return List.of("bump"); + } + }); + + assertSuggestions("offset bu", "bump"); + for (int i = 10; i < 20; i++) { + final Suggestions suggestions = manager.offerBrigadierSuggestions(source, "offset " + "bump ".repeat(i)).join(); + assertEquals(7 + 5 * i, suggestions.getList().get(0).getRange().getStart()); + } + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/ProtocolUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/ProtocolUtilsTest.java index 1ed59cd8..ccd9cb7a 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/ProtocolUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/ProtocolUtilsTest.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.protocol; +import static com.velocitypowered.proxy.protocol.ProtocolUtils.encode21BitVarInt; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -83,7 +84,7 @@ public class ProtocolUtilsTest { private void writeReadTest3Bytes(ByteBuf buf, int test) { buf.clear(); - ProtocolUtils.write21BitVarInt(buf, test); + buf.writeMedium(encode21BitVarInt(test)); assertEquals(test, ProtocolUtils.readVarInt(buf)); }