Merge pull request 'Update to 26.3' (#4) from update into master
SteamWarCI Build successful
SteamWarCI Build successful
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -6,18 +6,28 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: namespace-profile-linux-default
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v6
|
uses: namespacelabs/nscloud-checkout-action@v9
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
- name: Set up cache
|
||||||
|
uses: namespacelabs/nscloud-cache-action@v1
|
||||||
|
with:
|
||||||
|
cache: gradle
|
||||||
|
- name: Setup gradle build cache
|
||||||
|
run: |
|
||||||
|
nsc cache gradle setup --init-gradle /tmp/init.gradle
|
||||||
- name: Set up Gradle
|
- name: Set up Gradle
|
||||||
uses: gradle/actions/setup-gradle@v5
|
uses: gradle/actions/setup-gradle@v6
|
||||||
|
with:
|
||||||
|
cache-disabled: true # namespace handles this
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v6
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 21
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
|
cache: "" # namespace handles this
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew --init-script=/tmp/init.gradle build
|
||||||
|
|||||||
@@ -40,3 +40,5 @@ page.
|
|||||||
Translations are handled using [Crowdin](https://papermc-io.crowdin.com/velocity).
|
Translations are handled using [Crowdin](https://papermc-io.crowdin.com/velocity).
|
||||||
If you want to translate a language not available on Crowdin,
|
If you want to translate a language not available on Crowdin,
|
||||||
you might want to ask in the [Discord](https://discord.gg/papermc) about it.
|
you might want to ask in the [Discord](https://discord.gg/papermc) about it.
|
||||||
|
|
||||||
|
[](https://namespace.so/github-actions/?utm_source=oss&utm_campaign=papermc)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public final class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLogin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the UUID of the connecting player.
|
* Returns the UUID of the connecting player.
|
||||||
|
*
|
||||||
* <p>This value is {@code null} on 1.19.2 and lower,
|
* <p>This value is {@code null} on 1.19.2 and lower,
|
||||||
* up to 1.20.1 it is optional and from 1.20.2 it will always be available.</p>
|
* up to 1.20.1 it is optional and from 1.20.2 it will always be available.</p>
|
||||||
*
|
*
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is executed when a player entered the configuration state and can be configured by Velocity.
|
* This event is executed when a player entered the configuration state and can be configured by Velocity.
|
||||||
|
*
|
||||||
* <p>Velocity will wait for this event before continuing/ending the configuration state.</p>
|
* <p>Velocity will wait for this event before continuing/ending the configuration state.</p>
|
||||||
*
|
*
|
||||||
* @param player The player who can be configured.
|
* @param player The player who can be configured.
|
||||||
|
|||||||
+1
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
/**
|
/**
|
||||||
* This event is executed when a player is about to enter the configuration state.
|
* This event is executed when a player is about to enter the configuration state.
|
||||||
* It is <b>not</b> called for the initial configuration of a player after login.
|
* It is <b>not</b> called for the initial configuration of a player after login.
|
||||||
|
*
|
||||||
* <p>Velocity will wait for this event before asking the client to enter configuration state.
|
* <p>Velocity will wait for this event before asking the client to enter configuration state.
|
||||||
* However due to backend server being unable to keep the connection alive during state changes,
|
* However due to backend server being unable to keep the connection alive during state changes,
|
||||||
* Velocity will only wait for a maximum of 5 seconds.</p>
|
* Velocity will only wait for a maximum of 5 seconds.</p>
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is executed when a player has entered the configuration state.
|
* This event is executed when a player has entered the configuration state.
|
||||||
|
*
|
||||||
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
|
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
|
||||||
* the {@linkplain Player#getProtocolState()} method is guaranteed
|
* the {@linkplain Player#getProtocolState()} method is guaranteed
|
||||||
* to return {@link ProtocolState#CONFIGURATION}.</p>
|
* to return {@link ProtocolState#CONFIGURATION}.</p>
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is executed when a player is about to finish the configuration state.
|
* This event is executed when a player is about to finish the configuration state.
|
||||||
|
*
|
||||||
* <p>Velocity will wait for this event before asking the client to finish the configuration state.
|
* <p>Velocity will wait for this event before asking the client to finish the configuration state.
|
||||||
* However due to backend server being unable to keep the connection alive during state changes,
|
* However due to backend server being unable to keep the connection alive during state changes,
|
||||||
* Velocity will only wait for a maximum of 5 seconds. If you need to hold a player in configuration
|
* Velocity will only wait for a maximum of 5 seconds. If you need to hold a player in configuration
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is executed when a player has finished the configuration state.
|
* This event is executed when a player has finished the configuration state.
|
||||||
|
*
|
||||||
* <p>From this moment on, the {@link Player#getProtocolState()} method
|
* <p>From this moment on, the {@link Player#getProtocolState()} method
|
||||||
* will return {@link ProtocolState#PLAY}.</p>
|
* will return {@link ProtocolState#PLAY}.</p>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package com.velocitypowered.api.network;
|
|||||||
public enum ProtocolState {
|
public enum ProtocolState {
|
||||||
/**
|
/**
|
||||||
* Initial connection State.
|
* Initial connection State.
|
||||||
|
*
|
||||||
* <p>This status can be caused by a {@link HandshakeIntent#STATUS},
|
* <p>This status can be caused by a {@link HandshakeIntent#STATUS},
|
||||||
* {@link HandshakeIntent#LOGIN} or {@link HandshakeIntent#TRANSFER} intent.</p>
|
* {@link HandshakeIntent#LOGIN} or {@link HandshakeIntent#TRANSFER} intent.</p>
|
||||||
* If the intent is LOGIN or TRANSFER, the next state will be {@link #LOGIN},
|
* If the intent is LOGIN or TRANSFER, the next state will be {@link #LOGIN},
|
||||||
@@ -24,6 +25,7 @@ public enum ProtocolState {
|
|||||||
HANDSHAKE,
|
HANDSHAKE,
|
||||||
/**
|
/**
|
||||||
* Ping State of a connection.
|
* Ping State of a connection.
|
||||||
|
*
|
||||||
* <p>Connections with the {@link HandshakeIntent#STATUS} intent will pass through this state
|
* <p>Connections with the {@link HandshakeIntent#STATUS} intent will pass through this state
|
||||||
* and be disconnected after it requests the ping from the server
|
* and be disconnected after it requests the ping from the server
|
||||||
* and the server responds with the respective ping.</p>
|
* and the server responds with the respective ping.</p>
|
||||||
@@ -31,11 +33,13 @@ public enum ProtocolState {
|
|||||||
STATUS,
|
STATUS,
|
||||||
/**
|
/**
|
||||||
* Authentication State of a connection.
|
* Authentication State of a connection.
|
||||||
|
*
|
||||||
* <p>At this moment the player is authenticating with the authentication servers.</p>
|
* <p>At this moment the player is authenticating with the authentication servers.</p>
|
||||||
*/
|
*/
|
||||||
LOGIN,
|
LOGIN,
|
||||||
/**
|
/**
|
||||||
* Configuration State of a connection.
|
* Configuration State of a connection.
|
||||||
|
*
|
||||||
* <p>At this point the player allows the server to send information
|
* <p>At this point the player allows the server to send information
|
||||||
* such as resource packs and plugin messages, at the same time the player
|
* such as resource packs and plugin messages, at the same time the player
|
||||||
* will send his client brand and the respective plugin messages
|
* will send his client brand and the respective plugin messages
|
||||||
@@ -46,6 +50,7 @@ public enum ProtocolState {
|
|||||||
CONFIGURATION,
|
CONFIGURATION,
|
||||||
/**
|
/**
|
||||||
* Game State of a connection.
|
* Game State of a connection.
|
||||||
|
*
|
||||||
* <p>In this state is where the whole game runs, the server is able to change
|
* <p>In this state is where the whole game runs, the server is able to change
|
||||||
* the player's state to {@link #CONFIGURATION} as needed in versions 1.20.2 and higher.</p>
|
* the player's state to {@link #CONFIGURATION} as needed in versions 1.20.2 and higher.</p>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
|
|||||||
MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"),
|
MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"),
|
||||||
MINECRAFT_1_21_11(774, "1.21.11"),
|
MINECRAFT_1_21_11(774, "1.21.11"),
|
||||||
MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"),
|
MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"),
|
||||||
MINECRAFT_26_2(776, "26.2");
|
MINECRAFT_26_2(776, "26.2"),
|
||||||
|
MINECRAFT_26_3(777, "26.3");
|
||||||
|
|
||||||
private static final int SNAPSHOT_BIT = 30;
|
private static final int SNAPSHOT_BIT = 30;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
* Represents three different states of a setting.
|
* Represents three different states of a setting.
|
||||||
*
|
*
|
||||||
* <p>Possible values:</p>
|
* <p>Possible values:</p>
|
||||||
* <p></p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #TRUE} - a positive setting</li>
|
* <li>{@link #TRUE} - a positive setting</li>
|
||||||
* <li>{@link #FALSE} - a negative (negated) setting</li>
|
* <li>{@link #FALSE} - a negative (negated) setting</li>
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ public interface Player extends
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
* <p><strong>Note that this method does not send a plugin message to the server the player
|
* <p><strong>Note that this method does not send a plugin message to the server the player
|
||||||
* is connected to.</strong> You should only use this method if you are trying to communicate
|
* is connected to.</strong> You should only use this method if you are trying to communicate
|
||||||
* with a mod that is installed on the player's client.</p>
|
* with a mod that is installed on the player's client.</p>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public interface KeySigned {
|
|||||||
* Validates the signature, expiry temporal and key against the
|
* Validates the signature, expiry temporal and key against the
|
||||||
* signer public key. Note: This will **not** check for
|
* signer public key. Note: This will **not** check for
|
||||||
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
|
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
|
||||||
|
*
|
||||||
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
|
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
|
||||||
* Addendum: Does not work for 1.19.1 until the user has authenticated.
|
* Addendum: Does not work for 1.19.1 until the user has authenticated.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -237,8 +237,11 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
|
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
|
||||||
|
*
|
||||||
* <p>This only works for players currently <b>not</b> connected to this proxy.</p>
|
* <p>This only works for players currently <b>not</b> connected to this proxy.</p>
|
||||||
|
*
|
||||||
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
||||||
|
*
|
||||||
* <p>Will ignore mismatching key revisions data.</p>
|
* <p>Will ignore mismatching key revisions data.</p>
|
||||||
*
|
*
|
||||||
* @param chatSession session to set
|
* @param chatSession session to set
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
group=com.velocitypowered
|
group=com.velocitypowered
|
||||||
version=4.1.0-SNAPSHOT
|
version=4.2.1-SNAPSHOT
|
||||||
|
|||||||
+10
-10
@@ -3,12 +3,12 @@ configurate3 = "3.7.3"
|
|||||||
configurate4 = "4.2.0"
|
configurate4 = "4.2.0"
|
||||||
flare = "2.0.1"
|
flare = "2.0.1"
|
||||||
log4j = "2.26.1"
|
log4j = "2.26.1"
|
||||||
netty = "4.2.16.Final"
|
netty = "4.2.18.Final"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
fill = "io.papermc.fill.gradle:1.0.12"
|
fill = "io.papermc.fill.gradle:1.0.12"
|
||||||
shadow = "com.gradleup.shadow:9.6.1"
|
shadow = "com.gradleup.shadow:9.6.1"
|
||||||
spotless = "com.diffplug.spotless:8.9.0"
|
spotless = "com.diffplug.spotless:8.10.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
adventure-bom = "net.kyori:adventure-bom:5.2.0"
|
adventure-bom = "net.kyori:adventure-bom:5.2.0"
|
||||||
@@ -19,8 +19,8 @@ auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.1
|
|||||||
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
|
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
|
||||||
bstats = "org.bstats:bstats-base:3.2.1"
|
bstats = "org.bstats:bstats-base:3.2.1"
|
||||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.4"
|
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.4"
|
||||||
checker-qual = "org.checkerframework:checker-qual:4.2.1"
|
checker-qual = "org.checkerframework:checker-qual:4.2.3"
|
||||||
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
|
checkstyle = "com.puppycrawl.tools:checkstyle:14.1.0"
|
||||||
completablefutures = "com.spotify:completable-futures:0.3.6"
|
completablefutures = "com.spotify:completable-futures:0.3.6"
|
||||||
configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" }
|
configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" }
|
||||||
configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" }
|
configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" }
|
||||||
@@ -32,12 +32,12 @@ disruptor = "com.lmax:disruptor:4.0.0"
|
|||||||
fastutil = "it.unimi.dsi:fastutil:8.5.19"
|
fastutil = "it.unimi.dsi:fastutil:8.5.19"
|
||||||
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
|
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
|
||||||
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
|
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
|
||||||
jline = "org.jline:jline-terminal-ffm:4.3.1"
|
jline = "org.jline:jline-terminal-jni:4.3.1"
|
||||||
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
|
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
|
||||||
junit = "org.junit.jupiter:junit-jupiter:6.1.2"
|
junit = "org.junit.jupiter:junit-jupiter:6.1.3"
|
||||||
jspecify = "org.jspecify:jspecify:1.0.1"
|
jspecify = "org.jspecify:jspecify:1.0.1"
|
||||||
kyori-ansi = "net.kyori:ansi:1.1.1"
|
kyori-ansi = "net.kyori:ansi:1.1.1"
|
||||||
guava = "com.google.guava:guava:33.6.0-jre"
|
guava = "com.google.guava:guava:33.7.1-jre"
|
||||||
gson = "com.google.code.gson:gson:2.14.0"
|
gson = "com.google.code.gson:gson:2.14.0"
|
||||||
guice = "com.google.inject:guice:7.0.0"
|
guice = "com.google.inject:guice:7.0.0"
|
||||||
lmbda = "org.lanternpowered:lmbda:3.0.0"
|
lmbda = "org.lanternpowered:lmbda:3.0.0"
|
||||||
@@ -55,9 +55,9 @@ netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll
|
|||||||
netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" }
|
netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" }
|
||||||
netty-transport-native-iouring = { module = "io.netty:netty-transport-native-io_uring", version.ref = "netty" }
|
netty-transport-native-iouring = { module = "io.netty:netty-transport-native-io_uring", version.ref = "netty" }
|
||||||
nightconfig = "com.electronwill.night-config:toml:3.9.0"
|
nightconfig = "com.electronwill.night-config:toml:3.9.0"
|
||||||
slf4j = "org.slf4j:slf4j-api:2.0.18"
|
slf4j = "org.slf4j:slf4j-api:2.0.19"
|
||||||
snakeyaml = "org.yaml:snakeyaml:2.6"
|
snakeyaml = "org.yaml:snakeyaml:2.7"
|
||||||
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.10.3"
|
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.10.4"
|
||||||
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
|
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
|||||||
@@ -488,23 +488,46 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-register servers. If a server is being replaced, make sure to note what players need to
|
// Re-register servers. If a server is being replaced or removed, make sure to note what
|
||||||
// move back to a fallback server.
|
// players need to move back to a fallback server.
|
||||||
Collection<ConnectedPlayer> evacuate = new ArrayList<>();
|
Collection<ConnectedPlayer> evacuate = new ArrayList<>();
|
||||||
for (Map.Entry<String, String> entry : newConfiguration.getServers().entrySet()) {
|
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
||||||
ServerInfo newInfo = new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()));
|
if (newConfiguration.getServers().keySet().stream()
|
||||||
Optional<RegisteredServer> rs = servers.getServer(entry.getKey());
|
.anyMatch(name -> name.equalsIgnoreCase(entry.getKey()))) {
|
||||||
if (rs.isEmpty()) {
|
continue;
|
||||||
servers.register(newInfo);
|
}
|
||||||
} else if (!rs.get().getServerInfo().equals(newInfo)) {
|
|
||||||
for (Player player : rs.get().getPlayersConnected()) {
|
RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null);
|
||||||
|
if (rs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Player player : rs.getPlayersConnected()) {
|
||||||
if (!(player instanceof ConnectedPlayer)) {
|
if (!(player instanceof ConnectedPlayer)) {
|
||||||
throw new IllegalStateException("ConnectedPlayer not found for player " + player
|
throw new IllegalStateException("Expected ConnectedPlayer for player " + player
|
||||||
+ " in server " + rs.get().getServerInfo().getName());
|
+ " on server " + rs.getServerInfo().getName() + ", got "
|
||||||
|
+ player.getClass().getName());
|
||||||
}
|
}
|
||||||
evacuate.add((ConnectedPlayer) player);
|
evacuate.add((ConnectedPlayer) player);
|
||||||
}
|
}
|
||||||
servers.unregister(rs.get().getServerInfo());
|
servers.unregister(rs.getServerInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : newConfiguration.getServers().entrySet()) {
|
||||||
|
ServerInfo newInfo = new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()));
|
||||||
|
RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null);
|
||||||
|
if (rs == null) {
|
||||||
|
servers.register(newInfo);
|
||||||
|
} else if (!rs.getServerInfo().equals(newInfo)) {
|
||||||
|
for (Player player : rs.getPlayersConnected()) {
|
||||||
|
if (!(player instanceof ConnectedPlayer)) {
|
||||||
|
throw new IllegalStateException("Expected ConnectedPlayer for player " + player
|
||||||
|
+ " on server " + rs.getServerInfo().getName() + ", got "
|
||||||
|
+ player.getClass().getName());
|
||||||
|
}
|
||||||
|
evacuate.add((ConnectedPlayer) player);
|
||||||
|
}
|
||||||
|
servers.unregister(rs.getServerInfo());
|
||||||
servers.register(newInfo);
|
servers.register(newInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,7 +676,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down."
|
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down".
|
||||||
*
|
*
|
||||||
* @param explicitExit whether the user explicitly shut down the proxy
|
* @param explicitExit whether the user explicitly shut down the proxy
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,13 +16,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.config;
|
package com.velocitypowered.proxy.config;
|
||||||
|
import com.electronwill.nightconfig.core.Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported passthrough modes for ping passthrough.
|
* Object to contain all the things that can be toggled for ping passthrough.
|
||||||
|
*
|
||||||
|
* @param version Whether the version should be passed through.
|
||||||
|
* @param players Whether the player count should be passed through.
|
||||||
|
* @param description Whether the description should be passed through.
|
||||||
|
* @param favicon Whether the favicon should be passed through.
|
||||||
|
* @param modinfo Whether the modinfo should be passed through.
|
||||||
*/
|
*/
|
||||||
public enum PingPassthroughMode {
|
public record PingPassthroughMode(boolean version, boolean players,
|
||||||
DISABLED,
|
boolean description, boolean favicon, boolean modinfo) {
|
||||||
MODS,
|
public static final PingPassthroughMode DEFAULT = new PingPassthroughMode();
|
||||||
DESCRIPTION,
|
|
||||||
ALL
|
/**
|
||||||
|
* Creates a default PingPassthroughMode.
|
||||||
|
*/
|
||||||
|
private PingPassthroughMode() {
|
||||||
|
this(false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a PingPassthroughMode from a config section, or the default if the section is null.
|
||||||
|
* Based on the code for PacketLimiterConfig.
|
||||||
|
*
|
||||||
|
* @param config The configuration object to parse.
|
||||||
|
* @return The PingPassthroughMode, or the default if {@code config} is null.
|
||||||
|
*/
|
||||||
|
public static PingPassthroughMode fromConfig(Config config) {
|
||||||
|
if (config == null) {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
return new PingPassthroughMode(
|
||||||
|
config.getOrElse("version", DEFAULT.version()),
|
||||||
|
config.getOrElse("players", DEFAULT.players()),
|
||||||
|
config.getOrElse("description", DEFAULT.description()),
|
||||||
|
config.getOrElse("favicon", DEFAULT.favicon()),
|
||||||
|
config.getOrElse("modinfo", DEFAULT.modinfo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean enabled() {
|
||||||
|
return this.version || this.players || this.description || this.favicon
|
||||||
|
|| this.modinfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration;
|
|||||||
import com.velocitypowered.proxy.config.migration.MiniMessageTranslationsMigration;
|
import com.velocitypowered.proxy.config.migration.MiniMessageTranslationsMigration;
|
||||||
import com.velocitypowered.proxy.config.migration.MotdMigration;
|
import com.velocitypowered.proxy.config.migration.MotdMigration;
|
||||||
import com.velocitypowered.proxy.config.migration.PacketLimiterMigration;
|
import com.velocitypowered.proxy.config.migration.PacketLimiterMigration;
|
||||||
|
import com.velocitypowered.proxy.config.migration.PingPassthroughMigration;
|
||||||
import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration;
|
import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration;
|
||||||
import com.velocitypowered.proxy.util.AddressUtil;
|
import com.velocitypowered.proxy.util.AddressUtil;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
@@ -79,7 +80,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
@Expose
|
@Expose
|
||||||
private boolean onlineModeKickExistingPlayers = false;
|
private boolean onlineModeKickExistingPlayers = false;
|
||||||
@Expose
|
@Expose
|
||||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DEFAULT;
|
||||||
@Expose
|
@Expose
|
||||||
private boolean samplePlayersInPing = false;
|
private boolean samplePlayersInPing = false;
|
||||||
private final Servers servers;
|
private final Servers servers;
|
||||||
@@ -513,7 +514,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
new MotdMigration(),
|
new MotdMigration(),
|
||||||
new MiniMessageTranslationsMigration(),
|
new MiniMessageTranslationsMigration(),
|
||||||
new TransferIntegrationMigration(),
|
new TransferIntegrationMigration(),
|
||||||
new PacketLimiterMigration()
|
new PacketLimiterMigration(),
|
||||||
|
new PingPassthroughMigration(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (final ConfigurationMigration migration : migrations) {
|
for (final ConfigurationMigration migration : migrations) {
|
||||||
@@ -555,9 +557,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
final CommentedConfig metricsConfig = config.get("metrics");
|
final CommentedConfig metricsConfig = config.get("metrics");
|
||||||
final PlayerInfoForwarding forwardingMode = config.getEnumOrElse(
|
final PlayerInfoForwarding forwardingMode = config.getEnumOrElse(
|
||||||
"player-info-forwarding-mode", PlayerInfoForwarding.NONE);
|
"player-info-forwarding-mode", PlayerInfoForwarding.NONE);
|
||||||
final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
|
final PingPassthroughMode pingPassthrough = PingPassthroughMode.fromConfig(config.get("ping-passthrough"));
|
||||||
PingPassthroughMode.DISABLED);
|
|
||||||
|
|
||||||
final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false);
|
final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false);
|
||||||
|
|
||||||
final String bind = config.getOrElse("bind", "0.0.0.0:25565");
|
final String bind = config.getOrElse("bind", "0.0.0.0:25565");
|
||||||
@@ -590,7 +590,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
forwardingMode,
|
forwardingMode,
|
||||||
forwardingSecret,
|
forwardingSecret,
|
||||||
kickExisting,
|
kickExisting,
|
||||||
pingPassthroughMode,
|
pingPassthrough,
|
||||||
samplePlayersInPing,
|
samplePlayersInPing,
|
||||||
enablePlayerAddressLogging,
|
enablePlayerAddressLogging,
|
||||||
new Servers(serversConfig),
|
new Servers(serversConfig),
|
||||||
|
|||||||
+2
-1
@@ -30,7 +30,8 @@ public sealed interface ConfigurationMigration
|
|||||||
MotdMigration,
|
MotdMigration,
|
||||||
MiniMessageTranslationsMigration,
|
MiniMessageTranslationsMigration,
|
||||||
TransferIntegrationMigration,
|
TransferIntegrationMigration,
|
||||||
PacketLimiterMigration {
|
PacketLimiterMigration,
|
||||||
|
PingPassthroughMigration {
|
||||||
boolean shouldMigrate(CommentedFileConfig config);
|
boolean shouldMigrate(CommentedFileConfig config);
|
||||||
|
|
||||||
void migrate(CommentedFileConfig config, Logger logger) throws IOException;
|
void migrate(CommentedFileConfig config, Logger logger) throws IOException;
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024-2026 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.config.migration;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate the old ping passthrough entry to separate config entries.
|
||||||
|
*/
|
||||||
|
public final class PingPassthroughMigration implements ConfigurationMigration {
|
||||||
|
@Override
|
||||||
|
public boolean shouldMigrate(final CommentedFileConfig config) {
|
||||||
|
return configVersion(config) < 2.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(final CommentedFileConfig config, final Logger logger) {
|
||||||
|
// Get legacy ping passthrough value
|
||||||
|
final String legacyMode = config.getOrElse("ping-passthrough", "DISABLED")
|
||||||
|
.toUpperCase(Locale.ROOT);
|
||||||
|
boolean version = false;
|
||||||
|
boolean players = false;
|
||||||
|
boolean description = false;
|
||||||
|
boolean favicon = false;
|
||||||
|
boolean modinfo = false;
|
||||||
|
|
||||||
|
switch (legacyMode) {
|
||||||
|
case "ALL":
|
||||||
|
version = true;
|
||||||
|
players = true;
|
||||||
|
description = true;
|
||||||
|
favicon = true;
|
||||||
|
modinfo = true;
|
||||||
|
break;
|
||||||
|
case "DESCRIPTION":
|
||||||
|
description = true;
|
||||||
|
modinfo = true;
|
||||||
|
break;
|
||||||
|
case "MODS":
|
||||||
|
modinfo = true;
|
||||||
|
break;
|
||||||
|
default: // DISABLED
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.removeComment("ping-passthrough");
|
||||||
|
config.remove("ping-passthrough");
|
||||||
|
|
||||||
|
config.setComment("announce-forge",
|
||||||
|
" Announce whether or not your server supports Forge. If you run a modded server, we\n"
|
||||||
|
+ " suggest turning this on.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ " If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true\n"
|
||||||
|
+ " instead for a nicer display in the server list.");
|
||||||
|
|
||||||
|
config.set("ping-passthrough.version", version);
|
||||||
|
config.setComment(
|
||||||
|
"ping-passthrough.version",
|
||||||
|
" Should Velocity pass the version number from the backend server when responding to server list ping requests?"
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set("ping-passthrough.players", players);
|
||||||
|
config.setComment(
|
||||||
|
"ping-passthrough.players",
|
||||||
|
" Should Velocity pass the player count from the backend server when responding to server list ping requests?"
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set("ping-passthrough.description", description);
|
||||||
|
config.setComment(
|
||||||
|
"ping-passthrough.description",
|
||||||
|
" Should Velocity pass the description from the backend server when responding to server list ping requests?"
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set("ping-passthrough.favicon", favicon);
|
||||||
|
config.setComment(
|
||||||
|
"ping-passthrough.favicon",
|
||||||
|
" Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests?"
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set("ping-passthrough.modinfo", modinfo);
|
||||||
|
config.setComment(
|
||||||
|
"ping-passthrough.modinfo",
|
||||||
|
" Should Velocity pass the mod list from the backend server when responding to server list ping requests?"
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set("config-version", "2.9");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.ClientboundPostEffectsPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
@@ -404,4 +405,8 @@ public interface MinecraftSessionHandler {
|
|||||||
default boolean handle(ClientboundStopSoundPacket packet) {
|
default boolean handle(ClientboundStopSoundPacket packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean handle(ClientboundPostEffectsPacket packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -57,6 +57,7 @@ import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.JoinGamePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
@@ -166,6 +167,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return false; // forwards on
|
return false; // forwards on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(JoinGamePacket packet) {
|
||||||
|
// We receive this if the connection is reconfigured
|
||||||
|
packet.setOnlineMode(serverConn.getPlayer().isOnlineMode());
|
||||||
|
return false; // forward
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(ClientSettingsPacket packet) {
|
public boolean handle(ClientSettingsPacket packet) {
|
||||||
serverConn.ensureConnected().write(packet);
|
serverConn.ensureConnected().write(packet);
|
||||||
|
|||||||
+3
-2
@@ -135,6 +135,7 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
|||||||
import net.kyori.adventure.text.minimessage.translation.Argument;
|
import net.kyori.adventure.text.minimessage.translation.Argument;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
import net.kyori.adventure.title.Title.Times;
|
import net.kyori.adventure.title.Title.Times;
|
||||||
import net.kyori.adventure.title.TitlePart;
|
import net.kyori.adventure.title.TitlePart;
|
||||||
import net.kyori.adventure.translation.GlobalTranslator;
|
import net.kyori.adventure.translation.GlobalTranslator;
|
||||||
@@ -486,11 +487,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showTitle(net.kyori.adventure.title.@NonNull Title title) {
|
public void showTitle(@NonNull Title title) {
|
||||||
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
||||||
GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket(
|
GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket(
|
||||||
GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion());
|
GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion());
|
||||||
net.kyori.adventure.title.Title.Times times = title.times();
|
Title.Times times = title.times();
|
||||||
if (times != null) {
|
if (times != null) {
|
||||||
timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn()));
|
timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn()));
|
||||||
timesPkt.setStay((int) DurationUtils.toTicks(times.stay()));
|
timesPkt.setStay((int) DurationUtils.toTicks(times.stay()));
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modern (Minecraft 1.20.3+) ResourcePackHandler
|
* Modern (Minecraft 1.20.3+) ResourcePackHandler.
|
||||||
*/
|
*/
|
||||||
public final class ModernResourcePackHandler extends ResourcePackHandler {
|
public final class ModernResourcePackHandler extends ResourcePackHandler {
|
||||||
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
|
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
|
||||||
|
|||||||
+1
@@ -118,6 +118,7 @@ public abstract sealed class ResourcePackHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a client response to a sent resource-pack.
|
* Processes a client response to a sent resource-pack.
|
||||||
|
*
|
||||||
* <p>Cases in which no action will be taken:</p>
|
* <p>Cases in which no action will be taken:</p>
|
||||||
* <ul>
|
* <ul>
|
||||||
*
|
*
|
||||||
|
|||||||
+45
-36
@@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
|||||||
import com.velocitypowered.api.proxy.server.PingOptions;
|
import com.velocitypowered.api.proxy.server.PingOptions;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||||
|
import com.velocitypowered.api.util.Favicon;
|
||||||
import com.velocitypowered.api.util.ModInfo;
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PingPassthroughMode;
|
import com.velocitypowered.proxy.config.PingPassthroughMode;
|
||||||
@@ -100,60 +101,68 @@ public class ServerListPingHandler {
|
|||||||
|
|
||||||
CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
|
CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
|
||||||
(ex) -> fallback);
|
(ex) -> fallback);
|
||||||
return switch (mode) {
|
|
||||||
case ALL -> pingResponses.thenApply(responses -> {
|
// Return early if ping passthrough is not enabled
|
||||||
|
if (!mode.enabled()) {
|
||||||
|
return CompletableFuture.completedFuture(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pingResponses.thenApply(responses -> {
|
||||||
// Find the first non-fallback
|
// Find the first non-fallback
|
||||||
for (ServerPing response : responses) {
|
for (ServerPing response : responses) {
|
||||||
if (response == fallback) {
|
if (response == fallback) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.getDescriptionComponent() == null) {
|
ServerPing.Version version;
|
||||||
return response.asBuilder()
|
if (mode.version()) {
|
||||||
.description(Component.empty())
|
version = response.getVersion();
|
||||||
.build();
|
} else {
|
||||||
|
version = fallback.getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
ServerPing.Players players;
|
||||||
|
if (mode.players()) {
|
||||||
|
players = response.getPlayers().orElse(null);
|
||||||
|
} else {
|
||||||
|
players = fallback.getPlayers().orElse(null);
|
||||||
}
|
}
|
||||||
return fallback;
|
|
||||||
});
|
Component description;
|
||||||
case MODS -> pingResponses.thenApply(responses -> {
|
if (mode.description()) {
|
||||||
// Find the first non-fallback that contains a mod list
|
if (response.getDescriptionComponent() != null) {
|
||||||
for (ServerPing response : responses) {
|
description = response.getDescriptionComponent();
|
||||||
if (response == fallback) {
|
} else {
|
||||||
continue;
|
description = Component.empty();
|
||||||
}
|
}
|
||||||
Optional<ModInfo> modInfo = response.getModinfo();
|
} else {
|
||||||
if (modInfo.isPresent()) {
|
description = fallback.getDescriptionComponent();
|
||||||
return fallback.asBuilder().mods(modInfo.get()).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Favicon favicon;
|
||||||
|
if (mode.favicon()) {
|
||||||
|
favicon = response.getFavicon().orElse(null);
|
||||||
|
} else {
|
||||||
|
favicon = fallback.getFavicon().orElse(null);
|
||||||
}
|
}
|
||||||
return fallback;
|
|
||||||
});
|
ModInfo modinfo;
|
||||||
case DESCRIPTION -> pingResponses.thenApply(responses -> {
|
if (mode.modinfo()) {
|
||||||
// Find the first non-fallback. If it includes a modlist, add it too.
|
modinfo = response.getModinfo().orElse(null);
|
||||||
for (ServerPing response : responses) {
|
} else {
|
||||||
if (response == fallback) {
|
modinfo = fallback.getModinfo().orElse(null);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (response.getDescriptionComponent() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerPing(
|
return new ServerPing(
|
||||||
fallback.getVersion(),
|
version,
|
||||||
fallback.getPlayers().orElse(null),
|
players,
|
||||||
response.getDescriptionComponent(),
|
description,
|
||||||
fallback.getFavicon().orElse(null),
|
favicon,
|
||||||
response.getModinfo().orElse(null)
|
modinfo
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return fallback;
|
return fallback;
|
||||||
});
|
});
|
||||||
// Not possible, but covered for completeness.
|
|
||||||
default -> CompletableFuture.completedFuture(fallback);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +177,7 @@ public class ServerListPingHandler {
|
|||||||
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
|
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
|
||||||
PingPassthroughMode passthroughMode = configuration.getPingPassthrough();
|
PingPassthroughMode passthroughMode = configuration.getPingPassthrough();
|
||||||
|
|
||||||
if (passthroughMode == PingPassthroughMode.DISABLED) {
|
if (!passthroughMode.enabled()) {
|
||||||
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
|
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
|
||||||
} else {
|
} else {
|
||||||
String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString)
|
String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString)
|
||||||
|
|||||||
@@ -545,6 +545,36 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
|
||||||
|
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
|
||||||
|
for (int i = offset; i < registrations.length; i++) {
|
||||||
|
final HandlerRegistration registration = registrations[i];
|
||||||
|
try {
|
||||||
|
final EventTask eventTask = registration.handler.executeAsync(event);
|
||||||
|
if (eventTask == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
|
||||||
|
registrations, future, event, i, currentlyAsync);
|
||||||
|
if (currentlyAsync || !eventTask.requiresAsync()) {
|
||||||
|
if (continuationTask.execute()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registration.plugin.getExecutorService().execute(continuationTask);
|
||||||
|
}
|
||||||
|
// fire will continue in another thread once the async task is
|
||||||
|
// executed and the continuation is resumed
|
||||||
|
return;
|
||||||
|
} catch (final Throwable t) {
|
||||||
|
logHandlerException(registration, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (future != null) {
|
||||||
|
future.complete(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final int TASK_STATE_DEFAULT = 0;
|
private static final int TASK_STATE_DEFAULT = 0;
|
||||||
private static final int TASK_STATE_EXECUTING = 1;
|
private static final int TASK_STATE_EXECUTING = 1;
|
||||||
private static final int TASK_STATE_CONTINUE_IMMEDIATELY = 2;
|
private static final int TASK_STATE_CONTINUE_IMMEDIATELY = 2;
|
||||||
@@ -669,36 +699,6 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
|
|
||||||
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
|
|
||||||
for (int i = offset; i < registrations.length; i++) {
|
|
||||||
final HandlerRegistration registration = registrations[i];
|
|
||||||
try {
|
|
||||||
final EventTask eventTask = registration.handler.executeAsync(event);
|
|
||||||
if (eventTask == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
|
|
||||||
registrations, future, event, i, currentlyAsync);
|
|
||||||
if (currentlyAsync || !eventTask.requiresAsync()) {
|
|
||||||
if (continuationTask.execute()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
registration.plugin.getExecutorService().execute(continuationTask);
|
|
||||||
}
|
|
||||||
// fire will continue in another thread once the async task is
|
|
||||||
// executed and the continuation is resumed
|
|
||||||
return;
|
|
||||||
} catch (final Throwable t) {
|
|
||||||
logHandlerException(registration, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (future != null) {
|
|
||||||
future.complete(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logHandlerException(
|
private static void logHandlerException(
|
||||||
final HandlerRegistration registration, final Throwable t) {
|
final HandlerRegistration registration, final Throwable t) {
|
||||||
final PluginDescription pluginDescription = registration.plugin.getDescription();
|
final PluginDescription pluginDescription = registration.plugin.getDescription();
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ public enum ProtocolUtils {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
||||||
|
private static final int DEFAULT_MAX_KEY_ARRAY_SIZE = 1024;
|
||||||
private static final int MAXIMUM_VARINT_SIZE = 5;
|
private static final int MAXIMUM_VARINT_SIZE = 5;
|
||||||
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
|
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
|
||||||
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
|
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
|
||||||
@@ -349,15 +350,20 @@ public enum ProtocolUtils {
|
|||||||
writeString(buf, key.asMinimalString());
|
writeString(buf, key.asMinimalString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Key[] readKeyArray(ByteBuf buf) {
|
||||||
|
return readKeyArray(buf, DEFAULT_MAX_KEY_ARRAY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a standard Mojang Text namespaced:key array from the buffer.
|
* Reads a standard Mojang Text namespaced:key array from the buffer.
|
||||||
*
|
*
|
||||||
* @param buf the buffer to read from
|
* @param buf the buffer to read from
|
||||||
* @return the decoded key array
|
* @return the decoded key array
|
||||||
*/
|
*/
|
||||||
public static Key[] readKeyArray(ByteBuf buf) {
|
public static Key[] readKeyArray(ByteBuf buf, int cap) {
|
||||||
int length = readVarInt(buf);
|
int length = readVarInt(buf);
|
||||||
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
|
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
|
||||||
|
checkFrame(length <= cap, "Bad key array size (got %s, maximum is %s)", length, cap);
|
||||||
checkFrame(buf.isReadable(length),
|
checkFrame(buf.isReadable(length),
|
||||||
"Trying to read an array that is too long (wanted %s, only have %s)", length,
|
"Trying to read an array that is too long (wanted %s, only have %s)", length,
|
||||||
buf.readableBytes());
|
buf.readableBytes());
|
||||||
@@ -644,7 +650,7 @@ public enum ProtocolUtils {
|
|||||||
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
|
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an byte array for legacy version 1.7 from the specified {@code buf}
|
* Reads an byte array for legacy version 1.7 from the specified {@code buf}.
|
||||||
*
|
*
|
||||||
* @param buf the buffer to read from
|
* @param buf the buffer to read from
|
||||||
* @return the read byte array
|
* @return the read byte array
|
||||||
@@ -685,7 +691,7 @@ public enum ProtocolUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes an byte array for legacy version 1.7 to the specified {@code buf}
|
* Writes an byte array for legacy version 1.7 to the specified {@code buf}.
|
||||||
*
|
*
|
||||||
* @param b array
|
* @param b array
|
||||||
* @param buf buf
|
* @param buf buf
|
||||||
@@ -709,7 +715,7 @@ public enum ProtocolUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}
|
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}.
|
||||||
*
|
*
|
||||||
* @param b array
|
* @param b array
|
||||||
* @param buf buf
|
* @param buf buf
|
||||||
|
|||||||
@@ -25,49 +25,7 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERB
|
|||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolState;
|
import com.velocitypowered.api.network.ProtocolState;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
import com.velocitypowered.proxy.protocol.packet.*;
|
||||||
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;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.JoinGamePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
|
|
||||||
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.ServerboundPlayerLoadedPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
|
|
||||||
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.ChatAcknowledgementPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
|
import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
|
||||||
@@ -205,31 +163,44 @@ public enum StateRegistry {
|
|||||||
map(0x06, MINECRAFT_1_20_2, false),
|
map(0x06, MINECRAFT_1_20_2, false),
|
||||||
map(0x07, MINECRAFT_1_20_3, false),
|
map(0x07, MINECRAFT_1_20_3, false),
|
||||||
map(0x09, MINECRAFT_1_20_5, false));
|
map(0x09, MINECRAFT_1_20_5, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new,
|
||||||
|
map(0x0A, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||||
map(0x0A, MINECRAFT_1_20_5, false));
|
map(0x0A, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0B, MINECRAFT_26_3, false));
|
||||||
clientbound.register(TransferPacket.class, TransferPacket::new,
|
clientbound.register(TransferPacket.class, TransferPacket::new,
|
||||||
map(0x0B, MINECRAFT_1_20_5, false));
|
map(0x0B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0C, MINECRAFT_26_3, false));
|
||||||
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
|
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
|
||||||
map(0x07, MINECRAFT_1_20_2, false),
|
map(0x07, MINECRAFT_1_20_2, false),
|
||||||
map(0x08, MINECRAFT_1_20_3, false),
|
map(0x08, MINECRAFT_1_20_3, false),
|
||||||
map(0x0C, MINECRAFT_1_20_5, false));
|
map(0x0C, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0D, MINECRAFT_26_3, false));
|
||||||
clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new,
|
clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new,
|
||||||
map(0x08, MINECRAFT_1_20_2, false),
|
map(0x08, MINECRAFT_1_20_2, false),
|
||||||
map(0x09, MINECRAFT_1_20_3, false),
|
map(0x09, MINECRAFT_1_20_3, false),
|
||||||
map(0x0D, MINECRAFT_1_20_5, false));
|
map(0x0D, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0E, MINECRAFT_26_3, false));
|
||||||
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
|
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
|
||||||
map(0x0E, MINECRAFT_1_20_5, false));
|
map(0x0E, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0F, MINECRAFT_26_3, false));
|
||||||
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
||||||
map(0x0F, MINECRAFT_1_21, false));
|
map(0x0F, MINECRAFT_1_21, false),
|
||||||
|
map(0x10, MINECRAFT_26_3, false));
|
||||||
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
||||||
map(0x10, MINECRAFT_1_21, false));
|
map(0x10, MINECRAFT_1_21, false),
|
||||||
|
map(0x11, MINECRAFT_26_3, false));
|
||||||
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
|
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
|
||||||
map(0x11, MINECRAFT_1_21_6, false));
|
map(0x11, MINECRAFT_1_21_6, false),
|
||||||
|
map(0x12, MINECRAFT_26_3, false));
|
||||||
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
|
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
|
||||||
map(0x12, MINECRAFT_1_21_6, false));
|
map(0x12, MINECRAFT_1_21_6, false),
|
||||||
|
map(0x13, MINECRAFT_26_3, false));
|
||||||
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
|
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
|
||||||
map(0x13, MINECRAFT_1_21_9, false));
|
map(0x13, MINECRAFT_1_21_9, false),
|
||||||
|
map(0x14, MINECRAFT_26_3, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PLAY {
|
PLAY {
|
||||||
@@ -381,7 +352,8 @@ public enum StateRegistry {
|
|||||||
map(0x2D, MINECRAFT_1_21_2, false),
|
map(0x2D, MINECRAFT_1_21_2, false),
|
||||||
map(0x2F, MINECRAFT_1_21_4, false),
|
map(0x2F, MINECRAFT_1_21_4, false),
|
||||||
map(0x30, MINECRAFT_1_21_6, false),
|
map(0x30, MINECRAFT_1_21_6, false),
|
||||||
map(0x31, MINECRAFT_26_1, false));
|
map(0x31, MINECRAFT_26_1, false),
|
||||||
|
map(0x32, MINECRAFT_26_3, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||||
map(0x0B, MINECRAFT_1_20_2, false),
|
map(0x0B, MINECRAFT_1_20_2, false),
|
||||||
@@ -440,6 +412,9 @@ public enum StateRegistry {
|
|||||||
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||||
map(0x16, MINECRAFT_1_20_5, false),
|
map(0x16, MINECRAFT_1_20_5, false),
|
||||||
map(0x15, MINECRAFT_1_21_5, false));
|
map(0x15, MINECRAFT_1_21_5, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new,
|
||||||
|
map(0x53, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
|
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
|
||||||
map(0x5D, MINECRAFT_1_19_3, true),
|
map(0x5D, MINECRAFT_1_19_3, true),
|
||||||
@@ -450,7 +425,8 @@ public enum StateRegistry {
|
|||||||
map(0x6E, MINECRAFT_1_21_2, true),
|
map(0x6E, MINECRAFT_1_21_2, true),
|
||||||
map(0x6D, MINECRAFT_1_21_5, true),
|
map(0x6D, MINECRAFT_1_21_5, true),
|
||||||
map(0x72, MINECRAFT_1_21_9, true),
|
map(0x72, MINECRAFT_1_21_9, true),
|
||||||
map(0x74, MINECRAFT_26_1, true));
|
map(0x74, MINECRAFT_26_1, true),
|
||||||
|
map(0x76, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
|
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
|
||||||
map(0x5F, MINECRAFT_1_19_3, true),
|
map(0x5F, MINECRAFT_1_19_3, true),
|
||||||
@@ -461,7 +437,8 @@ public enum StateRegistry {
|
|||||||
map(0x71, MINECRAFT_1_21_2, true),
|
map(0x71, MINECRAFT_1_21_2, true),
|
||||||
map(0x70, MINECRAFT_1_21_5, true),
|
map(0x70, MINECRAFT_1_21_5, true),
|
||||||
map(0x75, MINECRAFT_1_21_9, true),
|
map(0x75, MINECRAFT_1_21_9, true),
|
||||||
map(0x77, MINECRAFT_26_1, true));
|
map(0x77, MINECRAFT_26_1, true),
|
||||||
|
map(0x79, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
PluginMessagePacket.class,
|
PluginMessagePacket.class,
|
||||||
PluginMessagePacket::new,
|
PluginMessagePacket::new,
|
||||||
@@ -519,7 +496,8 @@ public enum StateRegistry {
|
|||||||
map(0x27, MINECRAFT_1_21_2, 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),
|
map(0x2B, MINECRAFT_1_21_9, false),
|
||||||
map(0x2C, MINECRAFT_26_1, false));
|
map(0x2C, MINECRAFT_26_1, false),
|
||||||
|
map(0x2D, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
JoinGamePacket.class,
|
JoinGamePacket.class,
|
||||||
JoinGamePacket::new,
|
JoinGamePacket::new,
|
||||||
@@ -540,7 +518,8 @@ public enum StateRegistry {
|
|||||||
map(0x2C, MINECRAFT_1_21_2, 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),
|
map(0x30, MINECRAFT_1_21_9, false),
|
||||||
map(0x31, MINECRAFT_26_1, false));
|
map(0x31, MINECRAFT_26_1, false),
|
||||||
|
map(0x32, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
RespawnPacket.class,
|
RespawnPacket.class,
|
||||||
RespawnPacket::new,
|
RespawnPacket::new,
|
||||||
@@ -564,7 +543,8 @@ public enum StateRegistry {
|
|||||||
map(0x4C, MINECRAFT_1_21_2, 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),
|
map(0x50, MINECRAFT_1_21_9, true),
|
||||||
map(0x52, MINECRAFT_26_1, true));
|
map(0x52, MINECRAFT_26_1, true),
|
||||||
|
map(0x54, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
RemoveResourcePackPacket.class,
|
RemoveResourcePackPacket.class,
|
||||||
RemoveResourcePackPacket::new,
|
RemoveResourcePackPacket::new,
|
||||||
@@ -573,7 +553,8 @@ public enum StateRegistry {
|
|||||||
map(0x4A, MINECRAFT_1_21_2, 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),
|
map(0x4E, MINECRAFT_1_21_9, false),
|
||||||
map(0x50, MINECRAFT_26_1, false));
|
map(0x50, MINECRAFT_26_1, false),
|
||||||
|
map(0x51, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ResourcePackRequestPacket.class,
|
ResourcePackRequestPacket.class,
|
||||||
ResourcePackRequestPacket::new,
|
ResourcePackRequestPacket::new,
|
||||||
@@ -597,7 +578,8 @@ public enum StateRegistry {
|
|||||||
map(0x4B, MINECRAFT_1_21_2, 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),
|
map(0x4F, MINECRAFT_1_21_9, false),
|
||||||
map(0x51, MINECRAFT_26_1, false));
|
map(0x51, MINECRAFT_26_1, false),
|
||||||
|
map(0x52, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
HeaderAndFooterPacket.class,
|
HeaderAndFooterPacket.class,
|
||||||
HeaderAndFooterPacket::new,
|
HeaderAndFooterPacket::new,
|
||||||
@@ -622,7 +604,8 @@ public enum StateRegistry {
|
|||||||
map(0x74, MINECRAFT_1_21_2, 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),
|
map(0x78, MINECRAFT_1_21_9, true),
|
||||||
map(0x7A, MINECRAFT_26_1, true));
|
map(0x7A, MINECRAFT_26_1, true),
|
||||||
|
map(0x7D, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
LegacyTitlePacket.class,
|
LegacyTitlePacket.class,
|
||||||
LegacyTitlePacket::new,
|
LegacyTitlePacket::new,
|
||||||
@@ -646,7 +629,8 @@ public enum StateRegistry {
|
|||||||
map(0x6A, MINECRAFT_1_21_2, 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),
|
map(0x6E, MINECRAFT_1_21_9, true),
|
||||||
map(0x70, MINECRAFT_26_1, true));
|
map(0x70, MINECRAFT_26_1, true),
|
||||||
|
map(0x72, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleTextPacket.class,
|
TitleTextPacket.class,
|
||||||
TitleTextPacket::new,
|
TitleTextPacket::new,
|
||||||
@@ -661,7 +645,8 @@ public enum StateRegistry {
|
|||||||
map(0x6C, MINECRAFT_1_21_2, 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),
|
map(0x70, MINECRAFT_1_21_9, true),
|
||||||
map(0x72, MINECRAFT_26_1, true));
|
map(0x72, MINECRAFT_26_1, true),
|
||||||
|
map(0x74, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleActionbarPacket.class,
|
TitleActionbarPacket.class,
|
||||||
TitleActionbarPacket::new,
|
TitleActionbarPacket::new,
|
||||||
@@ -676,7 +661,8 @@ public enum StateRegistry {
|
|||||||
map(0x51, MINECRAFT_1_21_2, 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),
|
map(0x55, MINECRAFT_1_21_9, true),
|
||||||
map(0x57, MINECRAFT_26_1, true));
|
map(0x57, MINECRAFT_26_1, true),
|
||||||
|
map(0x59, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleTimesPacket.class,
|
TitleTimesPacket.class,
|
||||||
TitleTimesPacket::new,
|
TitleTimesPacket::new,
|
||||||
@@ -691,7 +677,8 @@ public enum StateRegistry {
|
|||||||
map(0x6D, MINECRAFT_1_21_2, 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),
|
map(0x71, MINECRAFT_1_21_9, true),
|
||||||
map(0x73, MINECRAFT_26_1, true));
|
map(0x73, MINECRAFT_26_1, true),
|
||||||
|
map(0x75, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleClearPacket.class,
|
TitleClearPacket.class,
|
||||||
TitleClearPacket::new,
|
TitleClearPacket::new,
|
||||||
@@ -723,7 +710,8 @@ public enum StateRegistry {
|
|||||||
map(0x3F, MINECRAFT_1_21_2, 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),
|
map(0x43, MINECRAFT_1_21_9, false),
|
||||||
map(0x45, MINECRAFT_26_1, false));
|
map(0x45, MINECRAFT_26_1, false),
|
||||||
|
map(0x46, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
UpsertPlayerInfoPacket.class,
|
UpsertPlayerInfoPacket.class,
|
||||||
UpsertPlayerInfoPacket::new,
|
UpsertPlayerInfoPacket::new,
|
||||||
@@ -734,14 +722,16 @@ public enum StateRegistry {
|
|||||||
map(0x40, MINECRAFT_1_21_2, 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),
|
map(0x44, MINECRAFT_1_21_9, false),
|
||||||
map(0x46, MINECRAFT_26_1, false));
|
map(0x46, MINECRAFT_26_1, false),
|
||||||
|
map(0x47, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||||
map(0x6B, MINECRAFT_1_20_5, false),
|
map(0x6B, MINECRAFT_1_20_5, false),
|
||||||
map(0x72, MINECRAFT_1_21_2, 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),
|
map(0x76, MINECRAFT_1_21_9, false),
|
||||||
map(0x78, MINECRAFT_26_1, false));
|
map(0x78, MINECRAFT_26_1, false),
|
||||||
|
map(0x7A, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
SystemChatPacket.class,
|
SystemChatPacket.class,
|
||||||
SystemChatPacket::new,
|
SystemChatPacket::new,
|
||||||
@@ -755,7 +745,8 @@ public enum StateRegistry {
|
|||||||
map(0x73, MINECRAFT_1_21_2, 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),
|
map(0x77, MINECRAFT_1_21_9, true),
|
||||||
map(0x79, MINECRAFT_26_1, true));
|
map(0x79, MINECRAFT_26_1, true),
|
||||||
|
map(0x7C, MINECRAFT_26_3, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
PlayerChatCompletionPacket.class,
|
PlayerChatCompletionPacket.class,
|
||||||
PlayerChatCompletionPacket::new,
|
PlayerChatCompletionPacket::new,
|
||||||
@@ -778,7 +769,8 @@ public enum StateRegistry {
|
|||||||
map(0x50, MINECRAFT_1_21_2, 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),
|
map(0x54, MINECRAFT_1_21_9, false),
|
||||||
map(0x56, MINECRAFT_26_1, false));
|
map(0x56, MINECRAFT_26_1, false),
|
||||||
|
map(0x58, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
StartUpdatePacket.class,
|
StartUpdatePacket.class,
|
||||||
() -> StartUpdatePacket.INSTANCE,
|
() -> StartUpdatePacket.INSTANCE,
|
||||||
@@ -788,7 +780,8 @@ public enum StateRegistry {
|
|||||||
map(0x70, MINECRAFT_1_21_2, 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),
|
map(0x74, MINECRAFT_1_21_9, false),
|
||||||
map(0x76, MINECRAFT_26_1, false));
|
map(0x76, MINECRAFT_26_1, false),
|
||||||
|
map(0x78, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
BundleDelimiterPacket.class,
|
BundleDelimiterPacket.class,
|
||||||
() -> BundleDelimiterPacket.INSTANCE,
|
() -> BundleDelimiterPacket.INSTANCE,
|
||||||
@@ -799,21 +792,24 @@ public enum StateRegistry {
|
|||||||
map(0x73, MINECRAFT_1_20_5, false),
|
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),
|
map(0x7F, MINECRAFT_1_21_9, false),
|
||||||
map(0x81, MINECRAFT_26_1, false));
|
map(0x81, MINECRAFT_26_1, false),
|
||||||
|
map(0x84, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundCustomReportDetailsPacket.class,
|
ClientboundCustomReportDetailsPacket.class,
|
||||||
ClientboundCustomReportDetailsPacket::new,
|
ClientboundCustomReportDetailsPacket::new,
|
||||||
map(0x7A, MINECRAFT_1_21, false),
|
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),
|
map(0x86, MINECRAFT_1_21_9, false),
|
||||||
map(0x88, MINECRAFT_26_1, false));
|
map(0x88, MINECRAFT_26_1, false),
|
||||||
|
map(0x8B, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundServerLinksPacket.class,
|
ClientboundServerLinksPacket.class,
|
||||||
ClientboundServerLinksPacket::new,
|
ClientboundServerLinksPacket::new,
|
||||||
map(0x7B, MINECRAFT_1_21, false),
|
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),
|
map(0x87, MINECRAFT_1_21_9, false),
|
||||||
map(0x89, MINECRAFT_26_1, false));
|
map(0x89, MINECRAFT_26_1, false),
|
||||||
|
map(0x8C, MINECRAFT_26_3, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
UpdateTeamsPacket.class,
|
UpdateTeamsPacket.class,
|
||||||
UpdateTeamsPacket::new,
|
UpdateTeamsPacket::new,
|
||||||
@@ -834,7 +830,8 @@ public enum StateRegistry {
|
|||||||
map(0x6B, MINECRAFT_1_21_9, true),
|
map(0x6B, MINECRAFT_1_21_9, true),
|
||||||
map(0x63, MINECRAFT_1_21_11, true),
|
map(0x63, MINECRAFT_1_21_11, true),
|
||||||
map(0x6D, MINECRAFT_26_1, true),
|
map(0x6D, MINECRAFT_26_1, true),
|
||||||
map(0x6D, MINECRAFT_26_2, true));
|
map(0x6D, MINECRAFT_26_2, true),
|
||||||
|
map(0x6F, MINECRAFT_26_3, true));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LOGIN {
|
LOGIN {
|
||||||
|
|||||||
+28
-1
@@ -213,7 +213,34 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
return 1 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
|
int minLength = 1 // locale
|
||||||
|
+ 1 // viewDistance
|
||||||
|
+ 1 // chatVisibility
|
||||||
|
+ 1; // chatColors
|
||||||
|
|
||||||
|
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
|
||||||
|
minLength += 1; // difficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
minLength += 1; // skinParts
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) {
|
||||||
|
minLength += 1; // mainHand
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||||
|
minLength += 1; // textFilteringEnabled
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
||||||
|
minLength += 1; // clientListingAllowed
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
minLength += 1; // particleStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018-2026 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 io.netty.buffer.ByteBuf;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
public final class ClientboundPostEffectsPacket implements MinecraftPacket {
|
||||||
|
|
||||||
|
private Key[] postEffects;
|
||||||
|
|
||||||
|
public ClientboundPostEffectsPacket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientboundPostEffectsPacket(Key[] postEffects) {
|
||||||
|
this.postEffects = postEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
this.postEffects = ProtocolUtils.readKeyArray(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||||
|
ProtocolUtils.writeKeyArray(buf, this.postEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
|
return handler.handle(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
|
|
||||||
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
|
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
|
||||||
private int entityId;
|
private int entityId;
|
||||||
private short gamemode;
|
private int gamemode;
|
||||||
private int dimension;
|
private int dimension;
|
||||||
private long partialHashedSeed; // 1.15+
|
private long partialHashedSeed; // 1.15+
|
||||||
private short difficulty;
|
private short difficulty;
|
||||||
@@ -47,7 +47,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
private CompoundBinaryTag registry; // 1.16+
|
private CompoundBinaryTag registry; // 1.16+
|
||||||
private DimensionInfo dimensionInfo; // 1.16+
|
private DimensionInfo dimensionInfo; // 1.16+
|
||||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||||
private short previousGamemode; // 1.16+
|
private int previousGamemode; // 1.16+
|
||||||
private int simulationDistance; // 1.18+
|
private int simulationDistance; // 1.18+
|
||||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||||
private int portalCooldown; // 1.20+
|
private int portalCooldown; // 1.20+
|
||||||
@@ -63,11 +63,11 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
this.entityId = entityId;
|
this.entityId = entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getGamemode() {
|
public int getGamemode() {
|
||||||
return gamemode;
|
return gamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGamemode(short gamemode) {
|
public void setGamemode(int gamemode) {
|
||||||
this.gamemode = gamemode;
|
this.gamemode = gamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,11 +131,11 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
this.dimensionInfo = dimensionInfo;
|
this.dimensionInfo = dimensionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getPreviousGamemode() {
|
public int getPreviousGamemode() {
|
||||||
return previousGamemode;
|
return previousGamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreviousGamemode(short previousGamemode) {
|
public void setPreviousGamemode(int previousGamemode) {
|
||||||
this.previousGamemode = previousGamemode;
|
this.previousGamemode = previousGamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,8 +345,13 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
String levelName = ProtocolUtils.readString(buf);
|
String levelName = ProtocolUtils.readString(buf);
|
||||||
this.partialHashedSeed = buf.readLong();
|
this.partialHashedSeed = buf.readLong();
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
|
this.gamemode = ProtocolUtils.readVarInt(buf);
|
||||||
|
this.previousGamemode = ProtocolUtils.readVarInt(buf); // game mode + 1 or 0
|
||||||
|
} else {
|
||||||
this.gamemode = buf.readByte();
|
this.gamemode = buf.readByte();
|
||||||
this.previousGamemode = buf.readByte();
|
this.previousGamemode = buf.readByte(); // game mode or -1
|
||||||
|
}
|
||||||
|
|
||||||
boolean isDebug = buf.readBoolean();
|
boolean isDebug = buf.readBoolean();
|
||||||
boolean isFlat = buf.readBoolean();
|
boolean isFlat = buf.readBoolean();
|
||||||
@@ -498,8 +503,13 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
|
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
|
||||||
buf.writeLong(partialHashedSeed);
|
buf.writeLong(partialHashedSeed);
|
||||||
|
|
||||||
buf.writeByte(gamemode);
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
buf.writeByte(previousGamemode);
|
ProtocolUtils.writeVarInt(buf, this.gamemode);
|
||||||
|
ProtocolUtils.writeVarInt(buf, this.previousGamemode);
|
||||||
|
} else {
|
||||||
|
buf.writeByte(this.gamemode);
|
||||||
|
buf.writeByte(this.previousGamemode);
|
||||||
|
}
|
||||||
|
|
||||||
buf.writeBoolean(dimensionInfo.isDebugType());
|
buf.writeBoolean(dimensionInfo.isDebugType());
|
||||||
buf.writeBoolean(dimensionInfo.isFlat());
|
buf.writeBoolean(dimensionInfo.isFlat());
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
private int dimension;
|
private int dimension;
|
||||||
private long partialHashedSeed;
|
private long partialHashedSeed;
|
||||||
private short difficulty;
|
private short difficulty;
|
||||||
private short gamemode;
|
private int gamemode;
|
||||||
private String levelType = "";
|
private String levelType = "";
|
||||||
private byte dataToKeep; // 1.16+
|
private byte dataToKeep; // 1.16+
|
||||||
private DimensionInfo dimensionInfo; // 1.16-1.16.1
|
private DimensionInfo dimensionInfo; // 1.16-1.16.1
|
||||||
private short previousGamemode; // 1.16+
|
private int previousGamemode; // 1.16+
|
||||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||||
private int portalCooldown; // 1.20+
|
private int portalCooldown; // 1.20+
|
||||||
@@ -46,9 +46,9 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
public RespawnPacket() {
|
public RespawnPacket() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, int gamemode,
|
||||||
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
||||||
short previousGamemode, CompoundBinaryTag currentDimensionData,
|
int previousGamemode, CompoundBinaryTag currentDimensionData,
|
||||||
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
|
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
|
||||||
int seaLevel) {
|
int seaLevel) {
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
@@ -97,11 +97,11 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getGamemode() {
|
public int getGamemode() {
|
||||||
return gamemode;
|
return gamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGamemode(short gamemode) {
|
public void setGamemode(int gamemode) {
|
||||||
this.gamemode = gamemode;
|
this.gamemode = gamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,11 +121,11 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
this.dataToKeep = dataToKeep;
|
this.dataToKeep = dataToKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getPreviousGamemode() {
|
public int getPreviousGamemode() {
|
||||||
return previousGamemode;
|
return previousGamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreviousGamemode(short previousGamemode) {
|
public void setPreviousGamemode(int previousGamemode) {
|
||||||
this.previousGamemode = previousGamemode;
|
this.previousGamemode = previousGamemode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,9 +197,17 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
|
||||||
this.partialHashedSeed = buf.readLong();
|
this.partialHashedSeed = buf.readLong();
|
||||||
}
|
}
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
|
this.gamemode = ProtocolUtils.readVarInt(buf);
|
||||||
|
} else {
|
||||||
this.gamemode = buf.readByte();
|
this.gamemode = buf.readByte();
|
||||||
|
}
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
|
this.previousGamemode = ProtocolUtils.readVarInt(buf);
|
||||||
|
} else {
|
||||||
this.previousGamemode = buf.readByte();
|
this.previousGamemode = buf.readByte();
|
||||||
|
}
|
||||||
boolean isDebug = buf.readBoolean();
|
boolean isDebug = buf.readBoolean();
|
||||||
boolean isFlat = buf.readBoolean();
|
boolean isFlat = buf.readBoolean();
|
||||||
this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
|
this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
|
||||||
@@ -249,9 +257,17 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
|
||||||
buf.writeLong(partialHashedSeed);
|
buf.writeLong(partialHashedSeed);
|
||||||
}
|
}
|
||||||
buf.writeByte(gamemode);
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, this.gamemode);
|
||||||
|
} else {
|
||||||
|
buf.writeByte(this.gamemode);
|
||||||
|
}
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
|
||||||
buf.writeByte(previousGamemode);
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, this.previousGamemode);
|
||||||
|
} else {
|
||||||
|
buf.writeByte(this.previousGamemode);
|
||||||
|
}
|
||||||
buf.writeBoolean(dimensionInfo.isDebugType());
|
buf.writeBoolean(dimensionInfo.isDebugType());
|
||||||
buf.writeBoolean(dimensionInfo.isFlat());
|
buf.writeBoolean(dimensionInfo.isFlat());
|
||||||
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
|
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
|
||||||
|
|||||||
+9
-3
@@ -25,6 +25,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
|
|||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
|
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_6;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_2;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_2;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_3;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
||||||
@@ -273,16 +274,21 @@ public class ArgumentPropertyRegistry {
|
|||||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
|
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
|
||||||
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||||
|
|
||||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
|
empty(id("minecraft:uuid", mapSet(MINECRAFT_26_3, 61), mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54), mapSet(MINECRAFT_1_20_5, 53),
|
||||||
mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||||
|
|
||||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
|
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
|
||||||
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
|
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
|
||||||
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
|
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
|
||||||
|
|
||||||
empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6
|
empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6
|
||||||
empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
|
empty(id("minecraft:dialog", mapSet(MINECRAFT_26_3, 58), mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
|
||||||
empty(id("minecraft:team_color", mapSet(MINECRAFT_26_2, 16))); // renamed from color in 26.2
|
empty(id("minecraft:team_color", mapSet(MINECRAFT_26_2, 16))); // renamed from color in 26.2
|
||||||
|
empty(id("minecraft:context_float_provider", mapSet(MINECRAFT_26_3, 55))); // added in 26.3
|
||||||
|
empty(id("minecraft:context_int_provider", mapSet(MINECRAFT_26_3, 56))); // added in 26.3
|
||||||
|
empty(id("minecraft:slot_source", mapSet(MINECRAFT_26_3, 57))); // added in 26.3
|
||||||
|
empty(id("minecraft:feature", mapSet(MINECRAFT_26_3, 59))); // added in 26.3
|
||||||
|
empty(id("minecraft:swing_animation", mapSet(MINECRAFT_26_3, 60))); // added in 26.3
|
||||||
|
|
||||||
// Crossstitch support
|
// Crossstitch support
|
||||||
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
||||||
|
|||||||
+8
-21
@@ -22,10 +22,12 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public abstract class GenericTitlePacket implements MinecraftPacket {
|
public abstract class GenericTitlePacket implements MinecraftPacket {
|
||||||
|
|
||||||
public enum ActionType {
|
public enum ActionType {
|
||||||
|
|
||||||
SET_TITLE(0),
|
SET_TITLE(0),
|
||||||
SET_SUBTITLE(1),
|
SET_SUBTITLE(1),
|
||||||
SET_ACTION_BAR(2),
|
SET_ACTION_BAR(2),
|
||||||
@@ -45,16 +47,7 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract @NotNull ActionType getAction();
|
||||||
private ActionType action;
|
|
||||||
|
|
||||||
protected void setAction(ActionType action) {
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ActionType getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentHolder getComponent() {
|
public ComponentHolder getComponent() {
|
||||||
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
|
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
|
||||||
@@ -88,11 +81,9 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
|||||||
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
|
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
public final void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
ProtocolVersion version) {
|
throw new UnsupportedOperationException("Decode is not implemented");
|
||||||
throw new UnsupportedOperationException(); // encode only
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,21 +94,17 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
|
|||||||
* @return GenericTitlePacket instance that follows the invoker type/version
|
* @return GenericTitlePacket instance that follows the invoker type/version
|
||||||
*/
|
*/
|
||||||
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
|
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
|
||||||
GenericTitlePacket packet = null;
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||||
packet = switch (type) {
|
return switch (type) {
|
||||||
case SET_ACTION_BAR -> new TitleActionbarPacket();
|
case SET_ACTION_BAR -> new TitleActionbarPacket();
|
||||||
case SET_SUBTITLE -> new TitleSubtitlePacket();
|
case SET_SUBTITLE -> new TitleSubtitlePacket();
|
||||||
case SET_TIMES -> new TitleTimesPacket();
|
case SET_TIMES -> new TitleTimesPacket();
|
||||||
case SET_TITLE -> new TitleTextPacket();
|
case SET_TITLE -> new TitleTextPacket();
|
||||||
case HIDE, RESET -> new TitleClearPacket();
|
case HIDE, RESET -> new TitleClearPacket(type);
|
||||||
default -> throw new IllegalArgumentException("Invalid ActionType");
|
default -> throw new IllegalArgumentException("Invalid ActionType");
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
packet = new LegacyTitlePacket();
|
return new LegacyTitlePacket(type);
|
||||||
}
|
}
|
||||||
packet.setAction(type);
|
|
||||||
return packet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-10
@@ -17,32 +17,44 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.title;
|
package com.velocitypowered.proxy.protocol.packet.title;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class LegacyTitlePacket extends GenericTitlePacket {
|
public class LegacyTitlePacket extends GenericTitlePacket {
|
||||||
|
|
||||||
|
private final ActionType action;
|
||||||
|
|
||||||
private @Nullable ComponentHolder component;
|
private @Nullable ComponentHolder component;
|
||||||
private int fadeIn;
|
private int fadeIn;
|
||||||
private int stay;
|
private int stay;
|
||||||
private int fadeOut;
|
private int fadeOut;
|
||||||
|
|
||||||
|
public LegacyTitlePacket() {
|
||||||
|
throw new UnsupportedOperationException("Decode is not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyTitlePacket(ActionType action) {
|
||||||
|
this.action = Preconditions.checkNotNull(action, "action");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
if (version.lessThan(ProtocolVersion.MINECRAFT_1_11)
|
if (version.lessThan(ProtocolVersion.MINECRAFT_1_11)
|
||||||
&& getAction() == ActionType.SET_ACTION_BAR) {
|
&& this.action == ActionType.SET_ACTION_BAR) {
|
||||||
throw new IllegalStateException("Action bars are only supported on 1.11 and newer");
|
throw new IllegalStateException("Action bars are only supported on 1.11 and newer");
|
||||||
}
|
}
|
||||||
ProtocolUtils.writeVarInt(buf, getAction().getAction(version));
|
ProtocolUtils.writeVarInt(buf, this.action.getAction(version));
|
||||||
|
|
||||||
switch (getAction()) {
|
switch (this.action) {
|
||||||
case SET_TITLE, SET_SUBTITLE, SET_ACTION_BAR -> {
|
case SET_TITLE, SET_SUBTITLE, SET_ACTION_BAR -> {
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
throw new IllegalStateException("No component found for " + getAction());
|
throw new IllegalStateException("No component found for " + this.action);
|
||||||
}
|
}
|
||||||
component.write(buf);
|
component.write(buf);
|
||||||
}
|
}
|
||||||
@@ -52,14 +64,13 @@ public class LegacyTitlePacket extends GenericTitlePacket {
|
|||||||
buf.writeInt(fadeOut);
|
buf.writeInt(fadeOut);
|
||||||
}
|
}
|
||||||
case HIDE, RESET -> {}
|
case HIDE, RESET -> {}
|
||||||
default -> throw new UnsupportedOperationException("Unknown action " + getAction());
|
default -> throw new UnsupportedOperationException("Unknown action " + this.action);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAction(ActionType action) {
|
public @NotNull ActionType getAction() {
|
||||||
super.setAction(action);
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,7 +116,7 @@ public class LegacyTitlePacket extends GenericTitlePacket {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "GenericTitlePacket{"
|
return "GenericTitlePacket{"
|
||||||
+ "action=" + getAction()
|
+ "action=" + action
|
||||||
+ ", component='" + component + '\''
|
+ ", component='" + component + '\''
|
||||||
+ ", fadeIn=" + fadeIn
|
+ ", fadeIn=" + fadeIn
|
||||||
+ ", stay=" + stay
|
+ ", stay=" + stay
|
||||||
|
|||||||
+4
-2
@@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TitleActionbarPacket extends GenericTitlePacket {
|
public class TitleActionbarPacket extends GenericTitlePacket {
|
||||||
|
|
||||||
private ComponentHolder component;
|
private ComponentHolder component;
|
||||||
|
|
||||||
public TitleActionbarPacket() {
|
@Override
|
||||||
setAction(ActionType.SET_ACTION_BAR);
|
public @NotNull ActionType getAction() {
|
||||||
|
return ActionType.SET_ACTION_BAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+15
-8
@@ -21,30 +21,37 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TitleClearPacket extends GenericTitlePacket {
|
public class TitleClearPacket extends GenericTitlePacket {
|
||||||
|
|
||||||
|
private final ActionType action;
|
||||||
|
|
||||||
public TitleClearPacket() {
|
public TitleClearPacket() {
|
||||||
setAction(ActionType.HIDE);
|
this(ActionType.HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleClearPacket(ActionType action) {
|
||||||
|
if (action != ActionType.HIDE && action != ActionType.RESET) {
|
||||||
|
throw new IllegalArgumentException("TitleClearPacket only accepts the HIDE and RESET actions.");
|
||||||
|
}
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAction(ActionType action) {
|
public @NotNull ActionType getAction() {
|
||||||
if (action != ActionType.HIDE && action != ActionType.RESET) {
|
return action;
|
||||||
throw new IllegalArgumentException("TitleClearPacket only accepts CLEAR and RESET actions");
|
|
||||||
}
|
|
||||||
super.setAction(action);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
buf.writeBoolean(getAction() == ActionType.RESET);
|
buf.writeBoolean(this.action == ActionType.RESET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TitleClearPacket{"
|
return "TitleClearPacket{"
|
||||||
+ ", resetTimes=" + (getAction() == ActionType.RESET)
|
+ ", resetTimes=" + (this.action == ActionType.RESET)
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TitleSubtitlePacket extends GenericTitlePacket {
|
public class TitleSubtitlePacket extends GenericTitlePacket {
|
||||||
|
|
||||||
private ComponentHolder component;
|
private ComponentHolder component;
|
||||||
|
|
||||||
public TitleSubtitlePacket() {
|
@Override
|
||||||
setAction(ActionType.SET_SUBTITLE);
|
public @NotNull ActionType getAction() {
|
||||||
|
return ActionType.SET_SUBTITLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+4
-2
@@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TitleTextPacket extends GenericTitlePacket {
|
public class TitleTextPacket extends GenericTitlePacket {
|
||||||
|
|
||||||
private ComponentHolder component;
|
private ComponentHolder component;
|
||||||
|
|
||||||
public TitleTextPacket() {
|
@Override
|
||||||
setAction(ActionType.SET_TITLE);
|
public @NotNull ActionType getAction() {
|
||||||
|
return ActionType.SET_TITLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+4
-2
@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TitleTimesPacket extends GenericTitlePacket {
|
public class TitleTimesPacket extends GenericTitlePacket {
|
||||||
|
|
||||||
@@ -28,8 +29,9 @@ public class TitleTimesPacket extends GenericTitlePacket {
|
|||||||
private int stay;
|
private int stay;
|
||||||
private int fadeOut;
|
private int fadeOut;
|
||||||
|
|
||||||
public TitleTimesPacket() {
|
@Override
|
||||||
setAction(ActionType.SET_TIMES);
|
public @NotNull ActionType getAction() {
|
||||||
|
return ActionType.SET_TIMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -30,40 +30,51 @@ package com.velocitypowered.proxy.util;
|
|||||||
* <p>This class is not thread-safe. If multiple threads access an instance concurrently,
|
* <p>This class is not thread-safe. If multiple threads access an instance concurrently,
|
||||||
* external synchronization is required.</p>
|
* external synchronization is required.</p>
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("checkstyle:WhitespaceAfter") // Not our class
|
|
||||||
public final class IntervalledCounter {
|
public final class IntervalledCounter {
|
||||||
|
|
||||||
private static final int INITIAL_SIZE = 8;
|
private static final int INITIAL_SIZE = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data points within this many nanoseconds of the newest one are merged into it, bounding the
|
||||||
|
* number of stored data points to roughly {@code interval / COALESCE_INTERVAL}.
|
||||||
|
*/
|
||||||
|
private static final long COALESCE_INTERVAL = 1_000_000L; // 1ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ring buffer holding the timestamp (in nanoseconds) for each data point.
|
* Ring buffer holding the timestamp (in nanoseconds) for each data point.
|
||||||
*/
|
*/
|
||||||
protected long[] times;
|
private long[] times;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ring buffer holding the count associated with each timestamp.
|
* Ring buffer holding the count associated with each timestamp.
|
||||||
*/
|
*/
|
||||||
protected long[] counts;
|
private long[] counts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sliding window size in nanoseconds. Only entries with time >= (currentTime - interval)
|
* The sliding window size in nanoseconds. Only entries with time >= (currentTime - interval)
|
||||||
* are considered part of the window.
|
* are considered part of the window.
|
||||||
*/
|
*/
|
||||||
protected final long interval;
|
private final long interval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached lower bound of the window (in nanoseconds) after the last update.
|
* Cached lower bound of the window (in nanoseconds) after the last update.
|
||||||
*/
|
*/
|
||||||
protected long minTime;
|
private long minTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Running sum of all counts currently within the window.
|
* Running sum of all counts currently within the window.
|
||||||
*/
|
*/
|
||||||
protected long sum;
|
private long sum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Head index (inclusive) of the ring buffer.
|
* Head index (inclusive) of the ring buffer.
|
||||||
*/
|
*/
|
||||||
protected int head; // inclusive
|
private int head; // inclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tail index (exclusive) of the ring buffer.
|
* Tail index (exclusive) of the ring buffer.
|
||||||
*/
|
*/
|
||||||
protected int tail; // exclusive
|
private int tail; // exclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new counter with the specified interval.
|
* Creates a new counter with the specified interval.
|
||||||
@@ -131,6 +142,8 @@ public final class IntervalledCounter {
|
|||||||
/**
|
/**
|
||||||
* Adds {@code count} units at the specified timestamp, assuming the timestamp is within the
|
* Adds {@code count} units at the specified timestamp, assuming the timestamp is within the
|
||||||
* current window. If the timestamp is older than {@code minTime}, the value is ignored.
|
* current window. If the timestamp is older than {@code minTime}, the value is ignored.
|
||||||
|
* If the timestamp is within {@link #COALESCE_INTERVAL} of the newest stored data point, the
|
||||||
|
* count is merged into that data point instead of creating a new one.
|
||||||
* This method does not automatically advance the window; callers should invoke
|
* This method does not automatically advance the window; callers should invoke
|
||||||
* {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand.
|
* {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand.
|
||||||
*
|
*
|
||||||
@@ -142,6 +155,15 @@ public final class IntervalledCounter {
|
|||||||
if (currTime - this.minTime < 0) {
|
if (currTime - this.minTime < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.head != this.tail) {
|
||||||
|
final int last = this.tail == 0 ? this.times.length - 1 : this.tail - 1;
|
||||||
|
// guard against overflow by using subtraction
|
||||||
|
if (currTime - this.times[last] < COALESCE_INTERVAL) {
|
||||||
|
this.counts[last] += count;
|
||||||
|
this.sum += count;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
int nextTail = (this.tail + 1) % this.times.length;
|
int nextTail = (this.tail + 1) % this.times.length;
|
||||||
if (nextTail == this.head) {
|
if (nextTail == this.head) {
|
||||||
this.resize();
|
this.resize();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Config version. Do not change this
|
# Config version. Do not change this
|
||||||
config-version = "2.8"
|
config-version = "2.9"
|
||||||
|
|
||||||
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565.
|
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565.
|
||||||
bind = "0.0.0.0:25565"
|
bind = "0.0.0.0:25565"
|
||||||
@@ -43,7 +43,7 @@ forwarding-secret-file = "forwarding.secret"
|
|||||||
# Announce whether or not your server supports Forge. If you run a modded server, we
|
# Announce whether or not your server supports Forge. If you run a modded server, we
|
||||||
# suggest turning this on.
|
# suggest turning this on.
|
||||||
#
|
#
|
||||||
# If your network runs one modpack consistently, consider using ping-passthrough = "mods"
|
# If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true
|
||||||
# instead for a nicer display in the server list.
|
# instead for a nicer display in the server list.
|
||||||
announce-forge = false
|
announce-forge = false
|
||||||
|
|
||||||
@@ -51,21 +51,6 @@ announce-forge = false
|
|||||||
# any existing player who is online if a duplicate connection attempt is made.
|
# any existing player who is online if a duplicate connection attempt is made.
|
||||||
kick-existing-players = false
|
kick-existing-players = false
|
||||||
|
|
||||||
# Should Velocity pass server list ping requests to a backend server?
|
|
||||||
# Available options:
|
|
||||||
# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png
|
|
||||||
# will determine the initial server list ping response.
|
|
||||||
# - "mods": Passes only the mod list from your backend server into the response.
|
|
||||||
# The first server in your try list (or forced host) with a mod list will be
|
|
||||||
# used. If no backend servers can be contacted, Velocity won't display any
|
|
||||||
# mod information.
|
|
||||||
# - "description": Uses the description and mod list from the backend server. The first
|
|
||||||
# server in the try (or forced host) list that responds is used for the
|
|
||||||
# description and mod list.
|
|
||||||
# - "all": Uses the backend server's response as the proxy response. The Velocity
|
|
||||||
# configuration is used if no servers could be contacted.
|
|
||||||
ping-passthrough = "DISABLED"
|
|
||||||
|
|
||||||
# If enabled (default is false), then a sample of the online players on the proxy will be visible
|
# If enabled (default is false), then a sample of the online players on the proxy will be visible
|
||||||
# when hovering over the player count in the server list.
|
# when hovering over the player count in the server list.
|
||||||
# This doesn't have any effect when ping passthrough is set to either "description" or "all".
|
# This doesn't have any effect when ping passthrough is set to either "description" or "all".
|
||||||
@@ -74,6 +59,18 @@ sample-players-in-ping = false
|
|||||||
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
||||||
enable-player-address-logging = true
|
enable-player-address-logging = true
|
||||||
|
|
||||||
|
[ping-passthrough]
|
||||||
|
# Should Velocity pass the version number from the backend server when responding to server list ping requests?
|
||||||
|
version = false
|
||||||
|
# Should Velocity pass the player count from the backend server when responding to server list ping requests?
|
||||||
|
players = false
|
||||||
|
# Should Velocity pass the description from the backend server when responding to server list ping requests?
|
||||||
|
description = false
|
||||||
|
# Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests?
|
||||||
|
favicon = false
|
||||||
|
# Should Velocity pass the mod list from the backend server when responding to server list ping requests?
|
||||||
|
modinfo = false
|
||||||
|
|
||||||
[packet-limiter]
|
[packet-limiter]
|
||||||
# Size of the moving time window in seconds used to calculate average rates.
|
# Size of the moving time window in seconds used to calculate average rates.
|
||||||
# A larger window tolerates short bursts while still enforcing the configured limits over time.
|
# A larger window tolerates short bursts while still enforcing the configured limits over time.
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 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.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class IntervalledCounterTest {
|
||||||
|
|
||||||
|
private static final long INTERVAL = TimeUnit.SECONDS.toNanos(7);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sumAndRateTrackAddedCounts() {
|
||||||
|
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
|
||||||
|
long now = 0;
|
||||||
|
|
||||||
|
counter.updateAndAdd(700, now);
|
||||||
|
assertEquals(700, counter.getSum());
|
||||||
|
assertEquals(100.0, counter.getRate(), 1e-9);
|
||||||
|
|
||||||
|
now += TimeUnit.SECONDS.toNanos(1);
|
||||||
|
counter.updateAndAdd(1400, now);
|
||||||
|
assertEquals(2100, counter.getSum());
|
||||||
|
assertEquals(300.0, counter.getRate(), 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void dataPointsOutsideTheWindowAreEvicted() {
|
||||||
|
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
|
||||||
|
long now = 0;
|
||||||
|
|
||||||
|
counter.updateAndAdd(10, now);
|
||||||
|
now += TimeUnit.SECONDS.toNanos(3);
|
||||||
|
counter.updateAndAdd(20, now);
|
||||||
|
assertEquals(30, counter.getSum());
|
||||||
|
assertEquals(2, counter.totalDataPoints());
|
||||||
|
|
||||||
|
// 7.5s after the first point: only the second point remains
|
||||||
|
now = TimeUnit.MILLISECONDS.toNanos(7500);
|
||||||
|
counter.updateCurrentTime(now);
|
||||||
|
assertEquals(20, counter.getSum());
|
||||||
|
assertEquals(1, counter.totalDataPoints());
|
||||||
|
|
||||||
|
// 10.5s: everything has expired
|
||||||
|
now = TimeUnit.MILLISECONDS.toNanos(10500);
|
||||||
|
counter.updateCurrentTime(now);
|
||||||
|
assertEquals(0, counter.getSum());
|
||||||
|
assertEquals(0, counter.totalDataPoints());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void storedDataPointsAreBoundedByTimeNotByCallCount() {
|
||||||
|
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
|
||||||
|
|
||||||
|
// Simulate a flood of tiny (even zero-sized) packets arriving far faster than one per
|
||||||
|
// millisecond for the whole window. Before coalescing was introduced, each call stored a
|
||||||
|
// separate data point, so an attacker could grow the ring buffer without bound while staying
|
||||||
|
// under a bytes-per-second limit.
|
||||||
|
final long stepNanos = 100;
|
||||||
|
final long calls = INTERVAL / stepNanos;
|
||||||
|
long now = 0;
|
||||||
|
for (long i = 0; i < calls; i++) {
|
||||||
|
counter.updateAndAdd(i % 2, now);
|
||||||
|
now += stepNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
long expectedSum = calls / 2;
|
||||||
|
assertEquals(expectedSum, counter.getSum());
|
||||||
|
// 7s window at 1ms coalescing granularity is ~7000 points; leave some slack.
|
||||||
|
assertTrue(counter.totalDataPoints() <= 7100,
|
||||||
|
"expected at most ~7000 data points, got " + counter.totalDataPoints());
|
||||||
|
assertTrue(counter.totalDataPoints() >= 7000,
|
||||||
|
"expected at least 7000 data points, got " + counter.totalDataPoints());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void coalescedDataPointsExpireTogether() {
|
||||||
|
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
|
||||||
|
|
||||||
|
long now = 0;
|
||||||
|
counter.updateAndAdd(5, now);
|
||||||
|
// Within the same 1ms bucket: merged into the previous point
|
||||||
|
counter.updateAndAdd(7, now + 500_000L);
|
||||||
|
assertEquals(12, counter.getSum());
|
||||||
|
assertEquals(1, counter.totalDataPoints());
|
||||||
|
|
||||||
|
// A new bucket starts a new point
|
||||||
|
counter.updateAndAdd(1, now + 1_000_000L);
|
||||||
|
assertEquals(13, counter.getSum());
|
||||||
|
assertEquals(2, counter.totalDataPoints());
|
||||||
|
|
||||||
|
// The merged bucket carries the timestamp of its first point and expires with it
|
||||||
|
counter.updateCurrentTime(now + INTERVAL + 1);
|
||||||
|
assertEquals(1, counter.getSum());
|
||||||
|
assertEquals(1, counter.totalDataPoints());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user