Compare commits

...

77 Commits

Author SHA1 Message Date
Chaoscaot babb22bf88 Remove duplicate mapping for MINECRAFT_1_21_2 in StateRegistry
SteamWarCI Build successful
2025-11-09 01:51:08 +01:00
Chaoscaot 468127996c Merge remote-tracking branch 'upstream/dev/3.0.0'
SteamWarCI Build failed
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2025-11-09 01:46:27 +01:00
Adrian b6b6b20fe9 Generate a new forwarding secret file if the file is deleted (#1671)
* Generate a new forwarding secret file if the file is deleted

This allows to generate a new forwarding secret simply by deleting the file if required.
The file will only be generated if the forwarding secret is not configured through a system property

resolves #1670

* Add file creation message
2025-10-23 11:13:36 -05:00
Dylan Sperrer f75b512837 Moved pre-1.19.1 command argument validation so it prints the faulty identifier (#1675) 2025-10-21 12:45:04 -05:00
Adrian 7412aca81c Fixed sending ServerData packets if the description component from the backend server is null (#1673) 2025-10-20 19:51:47 -05:00
Adrian 02cf349075 Fixed disconnecting players in the middle of a backend server reconfiguration (#1669) 2025-10-19 09:43:40 -05:00
Andrew Steinborn 67b988e6d2 Update all localizations to use the current year 2025-10-18 18:58:21 -04:00
Andrew Steinborn d2c13c2a4c Provide encode buffer hint 2025-10-18 17:36:39 -04:00
Andrew Steinborn 498a38cf74 Re-enable adaptive allocator
Recent Netty versions have improved the adaptive allocator, and we shouldn't be seeing the OOM issues others were noticing before. Let's re-enable it.

As for the buffer resizing issue, the upstream issue netty/netty#14912 is long fixed. I think we *should* pre-allocate the buffers beforehand much more aggressively, but that has to be future work.
2025-10-18 16:40:54 -04:00
Andrew Steinborn 13a1c93ea6 Bump Netty to 4.2.7.Final 2025-10-18 16:22:32 -04:00
Shane Freeder 38a0a7ed27 use correct string length for newer MC versions (Fixes #1629) (#1668) 2025-10-18 21:22:26 +01:00
Andrew Steinborn 70c3eabdb1 Minor optimizations for MinecraftCompressorAndLengthEncoder and friends
No need to bounce around changing the writer index, we can just set the value directly.

Also pull out the handshake checks into a separate function, to improve inlining.
2025-10-18 16:15:22 -04:00
okx-code 4cd3b68697 Fix players disconnecting when updating boss bars (#1656)
* Fix 1.20.2+ clients disconnecting when updating boss bars

On 1.20.2, the Minecraft client started clearing boss bars after the login packet, which meant that the ProxyServer#showBossbar API would result in the player getting kicked if the boss bar they were previously shown was updated after switching servers.

Therefore, I have added BossBarManager which drops boss bar packets once the client enters the configure phase to ensure that they do not disconnect, and then re-adds the boss bar once the client enters the login phase.

This ensures that clients do not receive boss bar updates for boss bars that they don't exist and causing them to disconnect. I have also taken care to ensure that this logic only applies on 1.20.2 and up, as it is not necessary for older clients.

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-10-16 23:12:57 -05:00
Emil 1140fc65ba fix: Enable EMIT_CLICK_URL_HTTPS on component serializers (#1665) 2025-10-14 13:10:57 -05:00
Ross 5753548b44 Fix SimpleCommand suggestion offset (#1664)
* Fix command suggestion offset

* fix length error

* add test

* checkstyle

---------

Co-authored-by: Ross <2086824-trashp@users.noreply.gitlab.com>
2025-10-13 14:41:33 -05:00
Ross 806b386cdb Fix command suggestion offset (#1662) 2025-10-11 21:11:44 -05:00
Cedric d266059abe Update adventure to version 4.25.0 (#1660) 2025-10-10 03:40:10 -05:00
Aaron b1dd26fbc4 1.21.10 (#1658) 2025-10-07 15:40:25 +01:00
Timon Seidel c8c27af7c3 feat: Add primitive support for sound api (#1422)
* feat: Add primitive support for sound api

* change to fail silently

fix: implement the correct playSound method
fix: bumped "since" version

* chore: update 1.21.5

* chore: enforce adventure's policy of not throwing exceptions on unsupported actions

* feat: allow sounds to be played from other players (on the same server)

* chore(fix): add missing getters/setters in packets

* chore: update 1.21.6
chore: added own notes to playSound method, as adventure moved them to the Sound class

* chore: cleanup

* fix: ignore invalid sound source
fix: sound source error on wrong version

* chore: prettify key writing

* Implement missing Player#playSound(Sound)

* Reverted Player#playSound(Sound) implementation

Also, improved documentation related to #playSound mehtods

* chore(jd): mark dialog operations unsupported

* chore: update 1.21.9

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-29 09:22:19 -05:00
Aaron ba01492790 Minecraft 1.21.9 (#1651)
* 1.21.9-pr1
- not tested yet

* 1.21.9-pre2

* feat: forward code of conduct packets in CONFIG state

* 1.21.9

---------

Co-authored-by: Emilxyz <12966472+Emilxyz@users.noreply.github.com>
2025-09-27 12:15:02 -05:00
Adrian Gonzales 94368d5021 Update publishing endpoint 2025-09-25 19:28:54 -05:00
Joo200 ec793a9fdb Log console command executions (#1137) 2025-09-24 00:50:05 -05:00
DartCZ 37f622f226 feat: add ProxyPreShutdownEvent before players are disconnected (#1626)
* feat: delay player disconnect until ProxyShutdownEvent completes

* fix: added back empty line

* feat: added ProxyPreShutdownEvent

* feat: CR changes

* chore: fixed license, annotated with Beta annotation

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* chore: consolidated log message

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* Update api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* feat: make ProxyPreShutdownEvent timeout configurable via system property

* fix: cs

* Document velocity.pre-shutdown-timeout system property

---------

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>
Co-authored-by: powercas_gamer <cas@mizule.dev>
Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-22 22:27:25 +01:00
Ecconia 87f74eaeda [ci skip] Improve documentation for priority parameter in EventManager (#1619)
* Improve documentation for priority parameter in EventManager

PostOrder was deprecated in commit
 (4f227badc2) in favor of priorities.
PostOrder itself was very descriptive on which PostOrder is processed first. A number cannot be descriptive about that - it is never clear if higher or lower numbers are processed first.

The Subscribe event attribute does contain a description on how priorities are evaluated. The EventManager did not, which literally did confused developers manually registering events.

This commit fixes this by describing the priority argument in EventManager with the same description that Subscribe uses.

* Fixed checkstyle

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-22 14:34:06 -05:00
ᑕᖇEEᑭYᑕᖇEEᑭEᖇ 8406979e71 Fix Weird Behavior in the Annotation Processor (#1645)
* fix: weird behavior of the annotation processor

* optimize imports
2025-09-17 12:17:48 -05:00
VelVeV 6e80f57739 Bump to Netty 4.2.5 (#1643) 2025-09-07 13:05:41 -05:00
Emil 1532fb360b fix: forward custom click packet in config state (#1641) 2025-09-03 22:43:02 +01:00
Emil 180af8c844 fix(resourcepack): apply server-side translations to resource pack prompt (#1611) 2025-09-03 11:09:12 -05:00
Timon Seidel 311e2bc18d fix(temp): pass though custom click action in config state (#1640) 2025-09-03 03:44:52 +01:00
Timon Seidel bfd15e1a81 fix: kick logging ignoring config (#1636) 2025-08-31 10:14:32 -05:00
Shane Freeder d2d333a958 Bumpy netty to 4.2.4 2025-08-14 21:14:51 +01:00
Emil 60a22ff330 chore: bump adventure to 4.24.0 (#1628) 2025-08-14 17:44:06 +01:00
Emil 946e5c47d4 fix: send callback command to >= 1.21.6 clients (#1627) 2025-08-14 17:43:20 +01:00
Gero 5d450ab3c7 Support all component-like and literal tooltips and errors (#1600) 2025-08-13 15:12:15 -05:00
Timon Seidel a509a878e9 [ci skip] chore: migrate legacy url (#1606) 2025-08-13 14:09:52 -05:00
Andrew Steinborn 49e2988e37 Utilize ByteBuf.readString() 2025-08-08 21:47:49 -04:00
Andrew Steinborn db8d16fd6e Bump to Netty 4.2.3
Closes #1615
2025-08-08 21:44:05 -04:00
Emil d47848cb93 feat: map show_dialog & clear_dialog in CONFIG state (#1621) 2025-08-02 17:43:06 +01:00
Chaoscaot 873fca763d Merge remote-tracking branch 'upstream/dev/3.0.0'
SteamWarCI Build successful
2025-07-28 18:34:52 +02:00
Pantera (Mad_Daniel) e99407132f Add version information for 1.21.8 (#1612) 2025-07-18 03:26:24 +01:00
Chaoscaot 67d63faeca Reapply "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit 11834de220.
2025-07-10 10:42:11 +02:00
Chaoscaot 871b053561 Merge remote-tracking branch 'upstream/dev/3.0.0' 2025-07-10 10:41:50 +02:00
Shane Freeder 81deb1fff8 Update maven publishing repo name 2025-06-30 15:22:39 +01:00
Jones 59560ebad1 1.21.7 Support (#1598)
* Support 1.21.7 RC 1

* Use snapshot protocol for RC 1

* Support 1.21.7 RC 2

* Set release protocol for 1.21.7

* Update api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java

---------

Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
2025-06-30 16:09:39 +02:00
Limbo 67a6600c05 New Crowdin updates (#1283)
* New translations messages.properties (Bulgarian)

* New translations messages.properties (Spanish)
2025-06-29 23:31:24 -07:00
Christoph Loy f3e30558e4 Gradle deprecation fixes & upgrades (#1594)
* Fix Gradle deprecations

By using test suites, we explicitely configure the relevant dependencies
on the test sourceset. This is not done by merely configuring the test task.

* Switch to maintained version of Shadow

* Update to Gradle 8.14.2
2025-06-28 16:28:29 -07:00
Riley Park e46ab6ad7d build: publish using fill (#1599) 2025-06-28 16:12:00 -07:00
Gero b6fd48f282 Update to adventure 4.22.0 (#1595) 2025-06-27 16:56:05 +01:00
Chaoscaot c2edc26d8e Merge pull request 'Update Velocity' (#2) from update into master
SteamWarCI Build successful
Reviewed-on: #2
2025-06-26 23:11:02 +02:00
Chaoscaot 76417b13d4 Merge branch 'updatev2' into update
SteamWarCI Build successful
2025-06-26 22:53:32 +02:00
Chaoscaot 91a61643bd Revert "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-04-27 20:24:41 +02:00
Chaoscaot b6e05cb0b9 Refactor TCP Fast Open checks and update message identifiers.
SteamWarCI Build successful
Removed transport type conditions for TCP Fast Open to streamline configuration usage. Added imports for new message identifiers in `ClientPlaySessionHandler`. Cleaned up Netty library definitions in `libs.versions.toml`.
2025-04-27 20:09:05 +02:00
Chaoscaot 1507b91463 Merge remote-tracking branch 'upstream/dev/3.0.0' into update
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java
2025-04-27 19:53:15 +02:00
Lixfel b06af3718c Merge remote-tracking branch 'github/dev/3.0.0'
SteamWarCI Build successful
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java
2025-04-01 07:06:14 +02:00
Lixfel a20a896582 Skip javadoc generation
SteamWarCI Build successful
2025-01-22 09:37:30 +01:00
Lixfel e1a3421212 Adapt to new server
SteamWarCI Build failed
2025-01-22 09:33:59 +01:00
Lixfel 19e51a2b12 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-12-06 11:14:27 +01:00
Lixfel b89a5c5ce9 Fix CI 2024-12-02 12:45:04 +01:00
Lixfel 65d3277319 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-11-30 09:25:25 +01:00
Lixfel a22bfa10f9 Merge pull request 'Update 1.21.2 client support' (#5) from upstream into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/5
2024-11-11 08:15:32 +01:00
Lixfel d9d1319a3a Merge remote-tracking branch 'upstream/dev/3.0.0' into upstream
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2024-11-10 18:32:48 +01:00
Lixfel 15ecbf4345 Merge pull request 'Update Velocity (might fix Command problems) and fix PluginMessages...' (#4) from fix-pluginmessages into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/4
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-08-20 08:14:11 +02:00
Lixfel 5e3bbcd427 Fix command signature issues. 2024-08-20 08:03:18 +02:00
Lixfel a6c79db07b Remove filter checks to receive PluginMessages unfiltered. 2024-08-18 15:32:08 +02:00
Lixfel 6e33bc6c17 Merge remote-tracking branch 'refs/remotes/upstream/dev/3.0.0' 2024-08-18 15:28:10 +02:00
Lixfel 01208bb359 Indicate NoChatReports support in ServerPing 2024-06-24 18:36:32 +02:00
Lixfel fa88aaae52 Always unsign chat. 2024-06-21 12:48:58 +02:00
Lixfel 2da400a267 Merge pull request 'Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix)' (#3) from io_uring into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/3
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-06-19 12:06:10 +02:00
Lixfel 8103135dfb Fix type 2024-06-19 10:21:36 +02:00
Lixfel cfabff7288 Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix) 2024-06-19 10:11:32 +02:00
Lixfel 2f5a27a708 Fix CI 2024-06-19 09:47:18 +02:00
Lixfel fdfe8bcc4b Fix CI 2024-06-19 09:40:58 +02:00
Chaoscaot a19fd8db74 Add UpdateTeamsPacket 2024-06-16 21:27:13 +02:00
Chaoscaot e63d71423d Add UpdateTeamsPacket 2024-06-16 21:24:06 +02:00
Lixfel a7afe35fab Rebuild 2024-06-16 13:25:07 +02:00
Lixfel 56d6339313 Fix JVM 2024-06-16 13:18:29 +02:00
Lixfel 2475572573 Add steamwarci.yml 2024-06-16 12:52:43 +02:00
110 changed files with 1669 additions and 319 deletions
+3 -3
View File
@@ -14,10 +14,10 @@ jobs:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
java-version: 21
distribution: 'zulu'
- name: Build with Gradle
run: ./gradlew build
+1 -1
View File
@@ -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(
@@ -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
@@ -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 <E> the event type to handle
*/
@@ -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
* <code>velocity.pre-shutdown-timeout</code> 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";
}
}
@@ -91,7 +91,9 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"),
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_6(771, "1.21.6"),
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;
@@ -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<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable {
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable, Sound.Emitter {
/**
* Returns the player's current username.
@@ -383,8 +384,12 @@ public interface Player extends
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void playSound(@NotNull Sound sound) {
@@ -393,8 +398,11 @@ public interface Player extends
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void playSound(@NotNull Sound sound, double x, double y, double z) {
@@ -403,18 +411,28 @@ public interface Player extends
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-146721">MC-146721</a>, stereo sounds are always played globally in 1.14+.
*
* <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-138832">MC-138832</a>, 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}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @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
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void openBook(@NotNull Book book) {
}
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void showDialog(@NotNull DialogLike dialog) {
}
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void closeDialog() {
}
/**
* Transfers a Player to a host.
*
@@ -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,9 +30,10 @@ 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;
public ServerPing(Version version, @Nullable Players players,
net.kyori.adventure.text.Component description, @Nullable Favicon favicon) {
@@ -47,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");
@@ -64,7 +67,8 @@ public final class ServerPing {
return Optional.ofNullable(players);
}
public net.kyori.adventure.text.Component getDescriptionComponent() {
@Nullable
public Component getDescriptionComponent() {
return description;
}
@@ -151,7 +155,7 @@ public final class ServerPing {
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>();
private net.kyori.adventure.text.Component description;
private Component description;
private @Nullable Favicon favicon;
private boolean nullOutPlayers;
private boolean nullOutModinfo;
@@ -299,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;
}
@@ -359,7 +363,7 @@ public final class ServerPing {
return samplePlayers;
}
public Optional<net.kyori.adventure.text.Component> getDescriptionComponent() {
public Optional<Component> getDescriptionComponent() {
return Optional.ofNullable(description);
}
@@ -2,8 +2,15 @@ import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.withType
import java.io.ByteArrayOutputStream
// This interface is needed as a workaround to get an instance of ExecOperations
interface Injected {
@get:Inject
val execOps: ExecOperations
}
val currentShortRevision = ByteArrayOutputStream().use {
exec {
val execOps = objects.newInstance<Injected>().execOps
execOps.exec {
executable = "git"
args = listOf("rev-parse", "HEAD")
standardOutput = it
@@ -8,10 +8,10 @@ extensions.configure<PublishingExtension> {
maven {
credentials(PasswordCredentials::class.java)
name = "paper"
val base = "https://repo.papermc.io/repository/maven"
val releasesRepoUrl = "$base-releases/"
val snapshotsRepoUrl = "$base-snapshots/"
name = if (version.toString().endsWith("SNAPSHOT")) "paperSnapshots" else "paper" // "paper" is seemingly not defined
val base = "https://artifactory.papermc.io/artifactory"
val releasesRepoUrl = "$base/releases/"
val snapshotsRepoUrl = "$base/snapshots/"
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
}
}
+5 -5
View File
@@ -20,11 +20,11 @@ subprojects {
testImplementation(rootProject.libs.junit)
}
tasks {
test {
useJUnitPlatform()
reports {
junitXml.required.set(true)
testing.suites.named<JvmTestSuite>("test") {
useJUnitJupiter()
targets.all {
testTask.configure {
reports.junitXml.required = true
}
}
}
+5 -4
View File
@@ -3,16 +3,17 @@ 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"
indra-publishing = "net.kyori.indra.publishing:2.0.6"
shadow = "io.github.goooler.shadow:8.1.5"
shadow = "com.gradleup.shadow:8.3.6"
spotless = "com.diffplug.spotless:6.25.0"
[libraries]
adventure-bom = "net.kyori:adventure-bom:4.21.0"
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.21.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"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+20
View File
@@ -1,9 +1,11 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
import io.papermc.fill.model.BuildChannel
plugins {
application
id("velocity-init-manifest")
alias(libs.plugins.shadow)
alias(libs.plugins.fill)
}
application {
@@ -108,6 +110,24 @@ tasks {
}
}
val projectVersion = version as String
fill {
project("velocity")
build {
channel = BuildChannel.STABLE
versionFamily("3.0.0")
version(projectVersion)
downloads {
register("server:default") {
file = tasks.shadowJar.flatMap { it.archiveFile }
nameResolver.set { project, _, version, build -> "$project-$version-$build.jar" }
}
}
}
}
dependencies {
implementation(project(":velocity-api"))
implementation(project(":velocity-native"))
@@ -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")) {
@@ -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<ConnectedPlayer> players = ImmutableList.copyOf(connectionsByUuid.values());
for (ConnectedPlayer player : players) {
player.disconnect(reason);
@@ -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);
}
}
}
@@ -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));
}
@@ -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()
@@ -103,13 +103,17 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
.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();
@@ -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);
@@ -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;
}
}
@@ -34,6 +34,8 @@ import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.command.CommandGraphInjector;
@@ -177,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
}
@@ -290,31 +294,14 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return true;
}
// Register and unregister packets are simply forwarded to the server as-is.
if (PluginMessageUtil.isRegister(packet) || PluginMessageUtil.isUnregister(packet)) {
return false;
}
if (PluginMessageUtil.isMcBrand(packet)) {
PluginMessagePacket rewritten = PluginMessageUtil
.rewriteMinecraftBrand(packet,
server.getVersion(), playerConnection.getProtocolVersion());
playerConnection.write(rewritten);
return true;
}
if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) {
// Handled.
return true;
}
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
return false;
}
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, copy);
String channel = packet.getChannel();
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), channel.indexOf(':') == -1 ? new LegacyChannelIdentifier(channel) : MinecraftChannelIdentifier.from(channel), copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
PluginMessagePacket copied = new PluginMessagePacket(
@@ -359,7 +346,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
// Inject commands from the proxy.
final CommandGraphInjector<CommandSource> 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(
@@ -511,4 +503,4 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
playerConnection.setAutoReading(writable);
}
}
}
@@ -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(
@@ -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));
@@ -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<Long, Long> 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.
@@ -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();
@@ -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;
@@ -30,6 +29,8 @@ import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnteredConfigurationEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
@@ -87,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;
@@ -339,43 +341,25 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
if (!player.getPhase().handle(player, packet, serverConn)) {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
// We don't have any plugins listening on this channel, process the packet now.
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
.consideredComplete()) {
// The client is trying to send messages too early. This is primarily caused by mods,
// but further aggravated by Velocity. To work around these issues, we will queue any
// non-FML handshake messages to be sent once the FML handshake has completed or the
// JoinGame packet has been received by the proxy, whichever comes first.
//
// We also need to make sure to retain these packets, so they can be flushed
// appropriately.
loginPluginMessages.add(packet.retain());
} else {
// The connection is ready, send the packet now.
backendConn.write(packet.retain());
}
} else {
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
PluginMessagePacket message = new PluginMessagePacket(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
.consideredComplete()) {
// We're still processing the connection (see above), enqueue the packet for now.
loginPluginMessages.add(message.retain());
} else {
backendConn.write(message);
}
byte[] copy = ByteBufUtil.getBytes(packet.content());
String channel = packet.getChannel();
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, channel.indexOf(':') == -1 ? new LegacyChannelIdentifier(channel) : MinecraftChannelIdentifier.from(channel), copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
PluginMessagePacket message = new PluginMessagePacket(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
.consideredComplete()) {
// We're still processing the connection (see above), enqueue the packet for now.
loginPluginMessages.add(message.retain());
} else {
backendConn.write(message);
}
}, backendConn.eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling plugin message packet for {}", player, ex);
return null;
});
}
}
}, backendConn.eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling plugin message packet for {}", player, ex);
return null;
});
}
}
}
@@ -535,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();
@@ -575,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();
@@ -694,23 +687,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return;
}
List<Offer> 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<Offer> 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);
}
@@ -765,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));
}
@@ -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<Result> 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<Boolean> 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;
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<VelocityBossBarImplementation> 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;
}
}
@@ -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);
}
@@ -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;
@@ -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);
@@ -116,7 +116,7 @@ public enum TransportType {
return NIO;
}
if (IoUring.isAvailable() && !Boolean.getBoolean("velocity.disable-iouring-transport")) {
if (IoUring.isAvailable() && Boolean.getBoolean("velocity.enable-iouring-transport")) {
return IO_URING;
}
@@ -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;
}
}
@@ -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)
@@ -69,6 +72,8 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -77,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)
@@ -86,6 +93,8 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -94,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)
@@ -103,6 +114,8 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -111,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)
@@ -121,6 +136,7 @@ public enum ProtocolUtils {
// after 1.21.5
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.FALSE)
.build()
)
.build();
@@ -134,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) {
@@ -234,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) {
@@ -272,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.
*
@@ -310,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.
*
@@ -775,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.
*/
@@ -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;
@@ -88,6 +94,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
import com.velocitypowered.proxy.protocol.packet.UpdateTeamsPacket;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket;
@@ -101,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;
@@ -183,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,
@@ -237,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 {
@@ -430,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,
@@ -465,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,
@@ -484,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,
@@ -503,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,
@@ -525,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,
@@ -554,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,
@@ -577,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,
@@ -599,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,
@@ -612,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,
@@ -625,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,
@@ -638,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,
@@ -668,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,
@@ -677,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,
@@ -694,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,
@@ -715,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,
@@ -723,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,
@@ -732,17 +790,36 @@ 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(0x82, MINECRAFT_1_21_2, false),
map(0x87, MINECRAFT_1_21_9, false));
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
map(0x44, ProtocolVersion.MINECRAFT_1_12_1, true),
map(0x47, ProtocolVersion.MINECRAFT_1_13, true),
map(0x4B, ProtocolVersion.MINECRAFT_1_14, true),
map(0x4C, ProtocolVersion.MINECRAFT_1_15, true),
map(0x55, ProtocolVersion.MINECRAFT_1_17, true),
map(0x58, ProtocolVersion.MINECRAFT_1_19_1, true),
map(0x56, ProtocolVersion.MINECRAFT_1_19_3, true),
map(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true),
map(0x5C, ProtocolVersion.MINECRAFT_1_20_2, true),
map(0x5E, ProtocolVersion.MINECRAFT_1_20_3, true),
map(0x60, ProtocolVersion.MINECRAFT_1_20_5, true),
map(0x67, ProtocolVersion.MINECRAFT_1_21_2, true)
);
}
},
LOGIN {
@@ -46,7 +46,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
if (uncompressed < threshold) {
// Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, uncompressed + 1);
ProtocolUtils.writeVarInt(out, 0);
out.writeByte(0);
out.writeBytes(msg);
} else {
handleCompressed(ctx, msg, out);
@@ -57,7 +57,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throws DataFormatException {
int uncompressed = msg.readableBytes();
ProtocolUtils.write21BitVarInt(out, 0); // Dummy packet length
out.writeMedium(0); // Reserve the packet length
ProtocolUtils.writeVarInt(out, uncompressed);
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
@@ -72,11 +72,8 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet.");
}
int writerIndex = out.writerIndex();
int packetLength = out.readableBytes() - 3;
out.writerIndex(0);
ProtocolUtils.write21BitVarInt(out, packetLength); // Rewrite packet length
out.writerIndex(writerIndex);
out.setMedium(0, ProtocolUtils.encode21BitVarInt(packetLength)); // Rewrite packet length
}
@Override
@@ -96,8 +96,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
}
private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception {
int expectedMinLen = packet.expectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(buf, direction, registry.version);
int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version);
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
}
@@ -54,6 +54,19 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
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);
}
@@ -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) {
@@ -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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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;
@@ -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;
}
}
@@ -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;
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
@@ -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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -0,0 +1,233 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import java.util.List;
public class UpdateTeamsPacket implements MinecraftPacket {
private String name;
private Mode mode;
private Component displayName;
private Component prefix;
private Component suffix;
private NameTagVisibility nameTagVisibility;
private CollisionRule collisionRule;
private int color;
private byte friendlyFlags;
private List<String> players;
public UpdateTeamsPacket(String name, Mode mode, Component displayName, Component prefix, Component suffix, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, int color, byte friendlyFlags, List<String> players) {
this.name = name;
this.mode = mode;
this.displayName = displayName;
this.prefix = prefix;
this.suffix = suffix;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.friendlyFlags = friendlyFlags;
this.players = players;
}
public UpdateTeamsPacket() {
}
@Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Packet is not implemented");
}
@Override
public boolean handle(MinecraftSessionHandler minecraftSessionHandler) {
return false;
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(byteBuf, name);
byteBuf.writeByte(mode.ordinal());
switch (mode) {
case CREATE, UPDATE:
new ComponentHolder(protocolVersion, displayName).write(byteBuf);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) {
new ComponentHolder(protocolVersion, prefix).write(byteBuf);
new ComponentHolder(protocolVersion, suffix).write(byteBuf);
}
byteBuf.writeByte(friendlyFlags);
ProtocolUtils.writeString(byteBuf, nameTagVisibility.getValue());
ProtocolUtils.writeString(byteBuf, collisionRule.getValue());
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_12_2)) {
ProtocolUtils.writeVarInt(byteBuf, color);
new ComponentHolder(protocolVersion, prefix).write(byteBuf);
new ComponentHolder(protocolVersion, suffix).write(byteBuf);
} else {
byteBuf.writeByte((byte) color);
}
ProtocolUtils.writeVarInt(byteBuf, players.size());
for (String player : players) {
ProtocolUtils.writeString(byteBuf, player);
}
break;
case ADD_PLAYER, REMOVE_PLAYER:
ProtocolUtils.writeVarInt(byteBuf, players.size());
for (String player : players) {
ProtocolUtils.writeString(byteBuf, player);
}
break;
case REMOVE:
break;
}
}
public enum Mode {
CREATE,
REMOVE,
UPDATE,
ADD_PLAYER,
REMOVE_PLAYER,
}
public enum NameTagVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
private final String value;
NameTagVisibility(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum CollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
private final String value;
CollisionRule(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public String getName() {
return name;
}
public Mode getMode() {
return mode;
}
public Component getDisplayName() {
return displayName;
}
public Component getPrefix() {
return prefix;
}
public Component getSuffix() {
return suffix;
}
public NameTagVisibility getNameTagVisibility() {
return nameTagVisibility;
}
public CollisionRule getCollisionRule() {
return collisionRule;
}
public int getColor() {
return color;
}
public byte getFriendlyFlags() {
return friendlyFlags;
}
public List<String> getPlayers() {
return players;
}
public void setName(String name) {
this.name = name;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
public void setNameTagVisibility(NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public void setCollisionRule(CollisionRule collisionRule) {
this.collisionRule = collisionRule;
}
public void setColor(int color) {
this.color = color;
}
public void setFriendlyFlags(byte friendlyFlags) {
this.friendlyFlags = friendlyFlags;
}
public void setPlayers(List<String> players) {
this.players = players;
}
}
@@ -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<Action> actions;
private final List<Entry> 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(
@@ -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 {
@@ -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) {
@@ -116,6 +116,8 @@ public class KeyedPlayerChatPacket implements MinecraftPacket {
ProtocolUtils.readByteArray(buf));
}
}
unsigned = true;
}
@Override
@@ -132,6 +132,7 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
unsigned = true;
}
unsigned = true;
}
@Override
@@ -69,6 +69,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
this.salt = buf.readLong();
this.signed = buf.readBoolean();
if (this.signed) {
this.signed = false;
this.signature = readMessageSignature(buf);
} else {
this.signature = new byte[0];
@@ -41,11 +41,14 @@ 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);
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
this.argumentSignatures = new ArgumentSignatures();
}
@Override
@@ -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
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}
@@ -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;
}
@@ -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();
}
}
@@ -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;
}
@@ -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<String, Map<String, int[]>> entry : tags.entrySet()) {
size += ProtocolUtils.stringSizeHint(entry.getKey());
size += ProtocolUtils.varIntBytes(entry.getValue().size());
for (Map.Entry<String, int[]> 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;
}
}
@@ -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}
@@ -35,7 +35,7 @@ velocity.command.generic-error=Възникна грешка при изпълн
velocity.command.command-does-not-exist=Тази команда не съществува.
velocity.command.players-only=Само играчи могат да изпълняват тази команда.
velocity.command.server-does-not-exist=Сървър с името {0} не съществува.
velocity.command.player-not-found=Този играч {0} не съществува.
velocity.command.player-not-found=Играч с името {0} не съществува.
velocity.command.server-current-server=В момента сте свързан към {0}.
velocity.command.server-too-many=Има прекалено много регистрирани сървъри. Използвайте TAB, за да видите всички налични сървъри.
velocity.command.server-available=Налични сървъри\:
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -29,7 +29,7 @@ velocity.error.modern-forwarding-needs-new-client=Este servidor solo es compatib
velocity.error.modern-forwarding-failed=El servidor no ha enviado una solicitud de reenvío al proxy. Asegúrate de que tu servidor está configurado para usar el método de reenvío de Velocity.
velocity.error.moved-to-new-server=Has sido echado de {0}\: {1}
velocity.error.no-available-servers=No hay servidores disponibles a los que conectarte. Inténtalo de nuevo más tarde o contacta con un administrador.
velocity.error.illegal-chat-characters=Illegal characters in chat
velocity.error.illegal-chat-characters=Caracteres no válidos en el chat
# Commands
velocity.command.generic-error=Se ha producido un error al ejecutar este comando.
velocity.command.command-does-not-exist=Este comando no existe.
@@ -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}
@@ -60,6 +60,6 @@ velocity.command.dump-success=Se ha creado un informe anónimo que contiene info
velocity.command.dump-will-expire=Este enlace caducará en unos días.
velocity.command.dump-server-error=Se ha producido un error en los servidores de Velocity y la subida no se ha podido completar. Notifica al equipo de Velocity sobre este problema y proporciona los detalles sobre este error disponibles en el archivo de registro o la consola de tu servidor Velocity.
velocity.command.dump-offline=Causa probable\: la configuración DNS del sistema no es válida o no hay conexión a internet
velocity.command.send-usage=/send <player> <server>
velocity.command.send-usage=/send <jugador> <servidor>
# Kick
velocity.kick.shutdown=Proxy shutting down.
velocity.kick.shutdown=El proxy se ha apagado.
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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}

Some files were not shown because too many files have changed in this diff Show More