Merge branch 'dev/3.0.0' into dev/5.0.0

# Conflicts:
#	api/build.gradle.kts
#	gradle.properties
#	proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
This commit is contained in:
Andrew Steinborn
2023-05-14 02:52:09 -04:00
20 changed files with 190 additions and 726 deletions

1
.gitignore vendored
View File

@@ -82,6 +82,7 @@ gradle-app.setting
### Other trash ### ### Other trash ###
logs/ logs/
/velocity.toml /velocity.toml
/forwarding.secret
server-icon.png server-icon.png
/bin/ /bin/
run/ run/

View File

@@ -1,6 +1,6 @@
# Velocity # Velocity
[![Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Velocity/gradle.yml)](https://papermc.io/downloads#Velocity) [![Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Velocity/gradle.yml)](https://papermc.io/downloads/velocity)
[![Join our Discord](https://img.shields.io/discord/289587909051416579.svg?logo=discord&label=)](https://discord.gg/papermc) [![Join our Discord](https://img.shields.io/discord/289587909051416579.svg?logo=discord&label=)](https://discord.gg/papermc)
A Minecraft server proxy with unparalleled server support, scalability, A Minecraft server proxy with unparalleled server support, scalability,
@@ -32,5 +32,5 @@ Once you've built Velocity, you can copy and run the `-all` JAR from
`proxy/build/libs`. Velocity will generate a default configuration file `proxy/build/libs`. Velocity will generate a default configuration file
and you can configure it from there. and you can configure it from there.
Alternatively, you can get the proxy JAR from the [downloads](https://papermc.io/downloads#Velocity) Alternatively, you can get the proxy JAR from the [downloads](https://papermc.io/downloads/velocity)
page. page.

View File

@@ -87,6 +87,13 @@ public interface Player extends
*/ */
PlayerSettings getPlayerSettings(); PlayerSettings getPlayerSettings();
/**
* Returns whether the player has sent its client settings.
*
* @return true if the player has sent its client settings
*/
boolean hasSentPlayerSettings();
/** /**
* Returns the player's mod info if they have a modded client. * Returns the player's mod info if they have a modded client.
* *

View File

@@ -6,13 +6,6 @@ plugins {
`java-library` `java-library`
} }
val junitVersion: String by project.extra
allprojects {
group = "com.velocitypowered"
version = "3.2.0-SNAPSHOT"
}
subprojects { subprojects {
apply<JavaLibraryPlugin>() apply<JavaLibraryPlugin>()
@@ -31,8 +24,9 @@ subprojects {
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure
maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.papermc.io/repository/maven-public/")
} }
dependencies { dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testImplementation(rootProject.libs.junit)
} }
tasks { tasks {

View File

@@ -1,17 +1,12 @@
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
checkstyle checkstyle
id("net.kyori.indra.publishing") version "2.0.6" alias(libs.plugins.indra.publishing)
id("com.diffplug.spotless") version "6.12.0" alias(libs.plugins.spotless)
}
repositories {
mavenCentral()
maven("https://plugins.gradle.org/m2")
} }
dependencies { dependencies {
implementation("com.diffplug.spotless:spotless-plugin-gradle:6.12.0") implementation("com.diffplug.spotless:spotless-plugin-gradle:${libs.plugins.spotless.get().version}")
} }
gradlePlugin { gradlePlugin {

View File

@@ -0,0 +1,11 @@
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml")) // include from parent project
}
}
}

54
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,54 @@
[versions]
configurate = "3.7.3"
flare = "2.0.1"
log4j = "2.20.0"
netty = "4.1.90.Final"
[plugins]
indra-publishing = "net.kyori.indra.publishing:2.0.6"
shadow = "com.github.johnrengelman.shadow:8.1.0"
spotless = "com.diffplug.spotless:6.12.0"
[libraries]
adventure-bom = "net.kyori:adventure-bom:4.13.1"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.0"
asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3"
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.0.1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5"
checker-qual = "org.checkerframework:checker-qual:3.28.0"
completablefutures = "com.spotify:completable-futures:0.3.5"
configurate-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate" }
configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" }
configurate-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate" }
disruptor = "com.lmax:disruptor:3.4.4"
fastutil = "it.unimi.dsi:fastutil:8.5.12"
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
jline = "org.jline:jline-terminal-jansi:3.23.0"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:5.9.0"
guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:5.1.0"
lmbda = "org.lanternpowered:lmbda:2.0.0"
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" }
log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
mockito = "org.mockito:mockito-core:5.2.0"
netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" }
netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "netty" }
netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
nightconfig = "com.electronwill.night-config:toml:3.6.6"
slf4j = "org.slf4j:slf4j-api:1.7.30"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
[bundles]
configurate = ["configurate-hocon", "configurate-yaml", "configurate-gson"]
flare = ["flare-core", "flare-fastutil"]
log4j = ["log4j-api", "log4j-core", "log4j-slf4j-impl", "log4j-iostreams", "log4j-jul"]

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -3,12 +3,8 @@ plugins {
`maven-publish` `maven-publish`
} }
val guavaVersion: String by project.extra
val nettyVersion: String by project.extra
val checkerFrameworkVersion: String by project.extra
dependencies { dependencies {
implementation("com.google.guava:guava:${guavaVersion}") implementation(libs.guava)
implementation("io.netty:netty-handler:${nettyVersion}") implementation(libs.netty.handler)
implementation("org.checkerframework:checker-qual:${checkerFrameworkVersion}") implementation(libs.checker.qual)
} }

View File

@@ -3,7 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCach
plugins { plugins {
application application
`set-manifest-impl-version` `set-manifest-impl-version`
id("com.github.johnrengelman.shadow") version "8.1.0" alias(libs.plugins.shadow)
} }
application { application {
@@ -88,65 +88,33 @@ tasks {
} }
} }
val adventureVersion: String by project.extra
val adventureFacetVersion: String by project.extra
val asyncHttpClientVersion: String by project.extra
val bstatsVersion: String by project.extra
val completableFuturesVersion: String by project.extra
val disruptorVersion: String by project.extra
val fastutilVersion: String by project.extra
val flareVersion: String by project.extra
val jlineVersion: String by project.extra
val joptSimpleVersion: String by project.extra
val lmbdaVersion: String by project.extra
val log4jVersion: String by project.extra
val nettyVersion: String by project.extra
val nightConfigVersion: String by project.extra
val semver4jVersion: String by project.extra
val terminalConsoleAppenderVersion: String by project.extra
dependencies { dependencies {
implementation(project(":velocity-api")) implementation(project(":velocity-api"))
implementation(project(":velocity-native")) implementation(project(":velocity-native"))
implementation("io.netty:netty-codec:${nettyVersion}") implementation(libs.bundles.log4j)
implementation("io.netty:netty-codec-haproxy:${nettyVersion}") implementation(libs.netty.codec)
implementation("io.netty:netty-codec-http:${nettyVersion}") implementation(libs.netty.codec.haproxy)
implementation("io.netty:netty-handler:${nettyVersion}") implementation(libs.netty.codec.http)
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}") implementation(libs.netty.handler)
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64") implementation(libs.netty.transport.native.epoll)
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64") implementation(variantOf(libs.netty.transport.native.epoll) { classifier("linux-x86_64") })
implementation(variantOf(libs.netty.transport.native.epoll) { classifier("linux-aarch_64") })
implementation("org.apache.logging.log4j:log4j-api:${log4jVersion}") implementation(libs.jopt)
implementation("org.apache.logging.log4j:log4j-core:${log4jVersion}") implementation(libs.terminalconsoleappender)
implementation("org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}") runtimeOnly(libs.jline)
implementation("org.apache.logging.log4j:log4j-iostreams:${log4jVersion}") runtimeOnly(libs.disruptor)
implementation("org.apache.logging.log4j:log4j-jul:${log4jVersion}") implementation(libs.fastutil)
implementation(platform(libs.adventure.bom))
implementation("net.sf.jopt-simple:jopt-simple:$joptSimpleVersion") // command-line options
implementation("net.minecrell:terminalconsoleappender:$terminalConsoleAppenderVersion")
runtimeOnly("org.jline:jline-terminal-jansi:$jlineVersion") // Needed for JLine
runtimeOnly("com.lmax:disruptor:$disruptorVersion") // Async loggers
implementation("it.unimi.dsi:fastutil-core:$fastutilVersion")
implementation(platform("net.kyori:adventure-bom:$adventureVersion"))
implementation("net.kyori:adventure-nbt") implementation("net.kyori:adventure-nbt")
implementation("net.kyori:adventure-platform-facet:$adventureFacetVersion") implementation(libs.adventure.facet)
implementation(libs.asynchttpclient)
implementation("org.asynchttpclient:async-http-client:$asyncHttpClientVersion") implementation(libs.completablefutures)
implementation(libs.nightconfig)
implementation("com.spotify:completable-futures:$completableFuturesVersion") implementation(libs.bstats)
implementation(libs.lmbda)
implementation("com.electronwill.night-config:toml:$nightConfigVersion") implementation(libs.bundles.flare)
compileOnly(libs.spotbugs.annotations)
implementation("org.bstats:bstats-base:$bstatsVersion") testImplementation(libs.mockito)
implementation("org.lanternpowered:lmbda:$lmbdaVersion")
implementation("space.vectrix.flare:flare:$flareVersion")
implementation("space.vectrix.flare:flare-fastutil:$flareVersion")
compileOnly("com.github.spotbugs:spotbugs-annotations:4.7.3")
testImplementation("org.mockito:mockito-core:3.+")
} }

View File

@@ -39,6 +39,7 @@ import com.velocitypowered.proxy.command.registrar.CommandRegistrar;
import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar; import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar;
import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar; import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar;
import com.velocitypowered.proxy.event.VelocityEventManager; import com.velocitypowered.proxy.event.VelocityEventManager;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -47,7 +48,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.kyori.adventure.identity.Identity; import java.util.stream.Collectors;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedBy;
@@ -114,14 +115,21 @@ public class VelocityCommandManager implements CommandManager {
Preconditions.checkNotNull(meta, "meta"); Preconditions.checkNotNull(meta, "meta");
Preconditions.checkNotNull(command, "command"); Preconditions.checkNotNull(command, "command");
// TODO Warn if command implements multiple registrable interfaces? final List<CommandRegistrar<?>> commandRegistrars = this.implementedRegistrars(command);
for (final CommandRegistrar<?> registrar : this.registrars) { if (commandRegistrars.isEmpty()) {
if (this.tryRegister(registrar, command, meta)) { throw new IllegalArgumentException(
return; // success command + " does not implement a registrable Command subinterface");
} } else if (commandRegistrars.size() > 1) {
final String implementedInterfaces = commandRegistrars.stream()
.map(CommandRegistrar::registrableSuperInterface)
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));
throw new IllegalArgumentException(
command + " implements multiple registrable Command subinterfaces: "
+ implementedInterfaces);
} else {
this.internalRegister(commandRegistrars.get(0), command, meta);
} }
throw new IllegalArgumentException(
command + " does not implement a registrable Command subinterface");
} }
/** /**
@@ -133,21 +141,26 @@ public class VelocityCommandManager implements CommandManager {
* @param command the command to register * @param command the command to register
* @param meta the command metadata * @param meta the command metadata
* @param <T> the type of the command * @param <T> the type of the command
* @return true if the command implements the registrable superinterface of the registrar; false
* otherwise.
* @throws IllegalArgumentException if the registrar cannot register the command * @throws IllegalArgumentException if the registrar cannot register the command
*/ */
private <T extends Command> boolean tryRegister(final CommandRegistrar<T> registrar, private <T extends Command> void internalRegister(final CommandRegistrar<T> registrar,
final Command command, final CommandMeta meta) { final Command command, final CommandMeta meta) {
final Class<T> superInterface = registrar.registrableSuperInterface(); final Class<T> superInterface = registrar.registrableSuperInterface();
if (!superInterface.isInstance(command)) {
return false;
}
registrar.register(meta, superInterface.cast(command)); registrar.register(meta, superInterface.cast(command));
for (String alias : meta.getAliases()) { for (String alias : meta.getAliases()) {
commandMetas.put(alias, meta); commandMetas.put(alias, meta);
} }
return true; }
private List<CommandRegistrar<?>> implementedRegistrars(final Command command) {
final List<CommandRegistrar<?>> registrarsFound = new ArrayList<>(2);
for (final CommandRegistrar<?> registrar : this.registrars) {
final Class<?> superInterface = registrar.registrableSuperInterface();
if (superInterface.isInstance(command)) {
registrarsFound.add(registrar);
}
}
return registrarsFound;
} }
@Override @Override
@@ -215,7 +228,7 @@ public class VelocityCommandManager implements CommandManager {
boolean isSyntaxError = !e.getType().equals( boolean isSyntaxError = !e.getType().equals(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand()); CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
if (isSyntaxError) { if (isSyntaxError) {
source.sendMessage(Identity.nil(), Component.text(e.getMessage(), NamedTextColor.RED)); source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
// This is, of course, a lie, but the API will need to change... // This is, of course, a lie, but the API will need to change...
return true; return true;
} else { } else {

View File

@@ -102,6 +102,9 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Send keep alive to try to avoid timeouts // Send keep alive to try to avoid timeouts
player.sendKeepAlive(); player.sendKeepAlive();
// Reset Tablist header and footer to prevent desync
player.clearHeaderAndFooter();
} }
// The goods are in hand! We got JoinGame. Let's transition completely to the new state. // The goods are in hand! We got JoinGame. Let's transition completely to the new state.

View File

@@ -35,7 +35,6 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.registry.DimensionRegistry;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.Handshake;
@@ -54,6 +53,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -71,7 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private boolean gracefulDisconnect = false; private boolean gracefulDisconnect = false;
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
private final Map<Long, Long> pendingPings = new HashMap<>(); private final Map<Long, Long> pendingPings = new HashMap<>();
private @MonotonicNonNull DimensionRegistry activeDimensionRegistry; private @MonotonicNonNull CompoundBinaryTag activeDimensionRegistry;
/** /**
* Initializes a new server connection. * Initializes a new server connection.
@@ -360,11 +360,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
return hasCompletedJoin; return hasCompletedJoin;
} }
public DimensionRegistry getActiveDimensionRegistry() { public CompoundBinaryTag getActiveDimensionRegistry() {
return activeDimensionRegistry; return activeDimensionRegistry;
} }
public void setActiveDimensionRegistry(DimensionRegistry activeDimensionRegistry) { public void setActiveDimensionRegistry(CompoundBinaryTag activeDimensionRegistry) {
this.activeDimensionRegistry = activeDimensionRegistry; this.activeDimensionRegistry = activeDimensionRegistry;
} }
} }

View File

@@ -473,7 +473,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16 destination.setActiveDimensionRegistry(joinGame.getRegistry()); // 1.16
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
// track them. // track them.

View File

@@ -283,6 +283,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
} }
@Override
public boolean hasSentPlayerSettings() {
return settings != null;
}
void setPlayerSettings(ClientSettings settings) { void setPlayerSettings(ClientSettings settings) {
ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); ClientSettingsWrapper cs = new ClientSettingsWrapper(settings);
this.settings = cs; this.settings = cs;
@@ -1245,10 +1250,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public void fireAndForget() { public void fireAndForget() {
connectWithIndication().exceptionally((ex) -> { connectWithIndication();
logger.error("Exception while connecting with indication", ex);
return null;
});
} }
} }
} }

View File

@@ -1,388 +0,0 @@
/*
* Copyright (C) 2020-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a dimension sent to the client by the server.
*/
public final class DimensionData {
private static final String UNKNOWN_DIMENSION_ID = "velocity:unknown_dimension";
private final String registryIdentifier;
private final @Nullable Integer dimensionId;
private final boolean isNatural;
private final float ambientLight;
private final boolean isShrunk;
private final boolean isUltrawarm;
private final boolean hasCeiling;
private final boolean hasSkylight;
private final boolean isPiglinSafe;
private final boolean doBedsWork;
private final boolean doRespawnAnchorsWork;
private final boolean hasRaids;
private final int logicalHeight;
private final String burningBehaviourIdentifier;
private final @Nullable Long fixedTime;
private final @Nullable Boolean createDragonFight;
private final @Nullable Double coordinateScale;
private final @Nullable String effects;
private final @Nullable Integer minY; // Required and added by 1.17
private final @Nullable Integer height; // Required and added by 1.17
private final @Nullable Integer monsterSpawnBlockLightLimit; // Required and added by 1.19
private final @Nullable Integer monsterSpawnLightLevel; // Required and added by 1.19
/**
* Initializes a new {@link DimensionData} instance.
*
* @param registryIdentifier the identifier for the dimension from the registry.
* @param dimensionId the dimension ID contained in the registry (the "id" tag)
* @param isNatural indicates if the dimension use natural world generation
* (e.g. overworld)
* @param ambientLight the light level the client sees without external lighting
* @param isShrunk indicates if the world is shrunk, aka not the full 256
* blocks (e.g. nether)
* @param isUltrawarm internal dimension warmth flag
* @param hasCeiling indicates if the dimension has a ceiling layer
* @param hasSkylight indicates if the dimension should display the sun
* @param isPiglinSafe indicates if piglins should naturally zombify in this
* dimension
* @param doBedsWork indicates if players should be able to sleep in beds in this
* dimension
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this
* dimension
* @param hasRaids indicates if raids can be spawned in the dimension
* @param logicalHeight the natural max height for the given dimension
* @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension
* @param fixedTime optional. If set to any game daytime value will deactivate
* time cycle
* @param createDragonFight optional. Internal flag used in the end dimension
* @param coordinateScale optional, unknown purpose
* @param effects optional, unknown purpose
* @param minY the world effective lowest build-level
* @param height the world height above zero
* @param monsterSpawnBlockLightLimit an integer controlling the block light needed to prevent
* monster spawns.
* @param monsterSpawnLightLevel an int provider which is evaluated to find a value to
* compare the current overall brightness with to determine if
* a monster should be allowed to spawn.
*/
public DimensionData(String registryIdentifier,
@Nullable Integer dimensionId,
boolean isNatural,
float ambientLight, boolean isShrunk, boolean isUltrawarm,
boolean hasCeiling, boolean hasSkylight,
boolean isPiglinSafe, boolean doBedsWork,
boolean doRespawnAnchorsWork, boolean hasRaids,
int logicalHeight, String burningBehaviourIdentifier,
@Nullable Long fixedTime, @Nullable Boolean createDragonFight,
@Nullable Double coordinateScale,
@Nullable String effects,
@Nullable Integer minY, @Nullable Integer height,
@Nullable Integer monsterSpawnBlockLightLimit,
@Nullable Integer monsterSpawnLightLevel) {
this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit;
this.monsterSpawnLightLevel = monsterSpawnLightLevel;
Preconditions.checkNotNull(
registryIdentifier, "registryIdentifier cannot be null");
Preconditions.checkArgument(registryIdentifier.length() > 0,
"registryIdentifier cannot be empty");
Preconditions.checkArgument(logicalHeight >= 0, "localHeight must be >= 0");
Preconditions.checkNotNull(
burningBehaviourIdentifier, "burningBehaviourIdentifier cannot be null");
Preconditions.checkArgument(burningBehaviourIdentifier.length() > 0,
"burningBehaviourIdentifier cannot be empty");
this.registryIdentifier = registryIdentifier;
this.dimensionId = dimensionId;
this.isNatural = isNatural;
this.ambientLight = ambientLight;
this.isShrunk = isShrunk;
this.isUltrawarm = isUltrawarm;
this.hasCeiling = hasCeiling;
this.hasSkylight = hasSkylight;
this.isPiglinSafe = isPiglinSafe;
this.doBedsWork = doBedsWork;
this.doRespawnAnchorsWork = doRespawnAnchorsWork;
this.hasRaids = hasRaids;
this.logicalHeight = logicalHeight;
this.burningBehaviourIdentifier = burningBehaviourIdentifier;
this.fixedTime = fixedTime;
this.createDragonFight = createDragonFight;
this.coordinateScale = coordinateScale;
this.effects = effects;
this.minY = minY;
this.height = height;
}
public String getRegistryIdentifier() {
return registryIdentifier;
}
public @Nullable Integer getDimensionId() {
return dimensionId;
}
public boolean isNatural() {
return isNatural;
}
public float getAmbientLight() {
return ambientLight;
}
public boolean isShrunk() {
return isShrunk;
}
public boolean isUltrawarm() {
return isUltrawarm;
}
public boolean hasCeiling() {
return hasCeiling;
}
public boolean hasSkylight() {
return hasSkylight;
}
public boolean isPiglinSafe() {
return isPiglinSafe;
}
public boolean doBedsWork() {
return doBedsWork;
}
public boolean doRespawnAnchorsWork() {
return doRespawnAnchorsWork;
}
public boolean hasRaids() {
return hasRaids;
}
public int getLogicalHeight() {
return logicalHeight;
}
public String getBurningBehaviourIdentifier() {
return burningBehaviourIdentifier;
}
public @Nullable Long getFixedTime() {
return fixedTime;
}
public @Nullable Boolean getCreateDragonFight() {
return createDragonFight;
}
public @Nullable Double getCoordinateScale() {
return coordinateScale;
}
public @Nullable Integer getMinY() {
return minY;
}
public @Nullable Integer getHeight() {
return height;
}
/**
* Returns a fresh {@link DimensionData} with the specified {@code registryIdentifier} and
* {@code dimensionId}.
*
* @param registryIdentifier the identifier for the dimension from the registry
* @param dimensionId optional, dimension ID
* @return a new {@link DimensionData}
*/
public DimensionData annotateWith(String registryIdentifier,
@Nullable Integer dimensionId) {
return new DimensionData(registryIdentifier, dimensionId, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, createDragonFight,
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit,
monsterSpawnLightLevel);
}
public boolean isUnannotated() {
return this.registryIdentifier.equalsIgnoreCase(UNKNOWN_DIMENSION_ID);
}
/**
* Parses a given CompoundTag to a DimensionData instance. Assumes the data only contains
* dimension details.
*
* @param details the compound from the registry to read
* @param version the protocol version
* @return game dimension data
*/
public static DimensionData decodeBaseCompoundTag(CompoundBinaryTag details,
ProtocolVersion version) {
boolean isNatural = details.getBoolean("natural");
float ambientLight = details.getFloat("ambient_light");
boolean isShrunk = details.getBoolean("shrunk");
boolean isUltrawarm = details.getBoolean("ultrawarm");
boolean hasCeiling = details.getBoolean("has_ceiling");
boolean hasSkylight = details.getBoolean("has_skylight");
boolean isPiglinSafe = details.getBoolean("piglin_safe");
boolean doBedsWork = details.getBoolean("bed_works");
boolean doRespawnAnchorsWork = details.getBoolean("respawn_anchor_works");
boolean hasRaids = details.getBoolean("has_raids");
int logicalHeight = details.getInt("logical_height");
String burningBehaviourIdentifier = details.getString("infiniburn");
Long fixedTime = details.keySet().contains("fixed_time")
? details.getLong("fixed_time") : null;
Boolean hasEnderdragonFight = details.keySet().contains("has_enderdragon_fight")
? details.getBoolean("has_enderdragon_fight") : null;
Double coordinateScale = details.keySet().contains("coordinate_scale")
? details.getDouble("coordinate_scale") : null;
String effects = details.keySet().contains("effects") ? details.getString("effects")
: null;
Integer minY = details.keySet().contains("min_y") ? details.getInt("min_y") : null;
Integer height = details.keySet().contains("height") ? details.getInt("height") : null;
Integer monsterSpawnBlockLightLimit =
details.keySet().contains("monster_spawn_block_light_limit")
? details.getInt("monster_spawn_block_light_limit") : null;
Integer monsterSpawnLightLevel =
details.keySet().contains("monster_spawn_light_level") ? details.getInt(
"monster_spawn_block_light_limit") :
null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
Preconditions.checkNotNull(height,
"DimensionData requires 'height' to be present for this version");
Preconditions.checkNotNull(minY,
"DimensionData requires 'minY' to be present for this version");
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
Preconditions.checkNotNull(monsterSpawnBlockLightLimit,
"DimensionData requires 'monster_spawn_block_light_limit' to be present"
+ " for this version.");
Preconditions.checkNotNull(monsterSpawnLightLevel,
"DimensionData requires 'monster_spawn_light_level' to be present"
+ " for this version.");
}
return new DimensionData(
UNKNOWN_DIMENSION_ID, null, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, hasEnderdragonFight,
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit,
monsterSpawnLightLevel);
}
/**
* Parses a given CompoundTag to a DimensionData instance. Assumes the data is part of a dimension
* registry.
*
* @param dimTag the compound from the registry to read
* @param version the protocol version
* @return game dimension data
*/
public static DimensionData decodeRegistryEntry(CompoundBinaryTag dimTag,
ProtocolVersion version) {
String registryIdentifier = dimTag.getString("name");
CompoundBinaryTag details;
Integer dimensionId = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
dimensionId = dimTag.getInt("id");
details = dimTag.getCompound("element");
} else {
details = dimTag;
}
DimensionData deserializedDetails = decodeBaseCompoundTag(details, version);
return deserializedDetails.annotateWith(registryIdentifier, dimensionId);
}
/**
* Encodes the Dimension data as CompoundTag.
*
* @param version the version to serialize as
* @return compound containing the dimension data
*/
public CompoundBinaryTag encodeAsCompoundTag(ProtocolVersion version) {
CompoundBinaryTag details = serializeDimensionDetails();
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
if (dimensionId == null) {
throw new IllegalStateException("Tried to serialize a 1.16.2+ dimension registry entry "
+ "without an ID");
}
return CompoundBinaryTag.builder()
.putString("name", registryIdentifier)
.putInt("id", dimensionId)
.put("element", details)
.build();
} else {
return details.putString("name", registryIdentifier);
}
}
/**
* Serializes details of this dimension.
*
* @return serialized details of this dimension
*/
public CompoundBinaryTag serializeDimensionDetails() {
CompoundBinaryTag.Builder ret = CompoundBinaryTag.builder();
ret.putBoolean("natural", isNatural);
ret.putFloat("ambient_light", ambientLight);
ret.putBoolean("shrunk", isShrunk);
ret.putBoolean("ultrawarm", isUltrawarm);
ret.putBoolean("has_ceiling", hasCeiling);
ret.putBoolean("has_skylight", hasSkylight);
ret.putBoolean("piglin_safe", isPiglinSafe);
ret.putBoolean("bed_works", doBedsWork);
ret.putBoolean("respawn_anchor_works", doRespawnAnchorsWork);
ret.putBoolean("has_raids", hasRaids);
ret.putInt("logical_height", logicalHeight);
ret.putString("infiniburn", burningBehaviourIdentifier);
if (fixedTime != null) {
ret.putLong("fixed_time", fixedTime);
}
if (createDragonFight != null) {
ret.putBoolean("has_enderdragon_fight", createDragonFight);
}
if (coordinateScale != null) {
ret.putDouble("coordinate_scale", coordinateScale);
}
if (effects != null) {
ret.putString("effects", effects);
}
if (minY != null) {
ret.putInt("min_y", minY);
}
if (height != null) {
ret.putInt("height", height);
}
if (monsterSpawnBlockLightLimit != null) {
ret.putInt("monster_spawn_block_light_limit", monsterSpawnBlockLightLimit);
}
if (monsterSpawnLightLevel != null) {
ret.putInt("monster_spawn_light_level", monsterSpawnLightLevel);
}
return ret.build();
}
}

View File

@@ -1,128 +0,0 @@
/*
* Copyright (C) 2020-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.velocitypowered.api.network.ProtocolVersion;
import java.util.Map;
import java.util.Set;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a registry of dimensions sent to the client by the server.
*/
public final class DimensionRegistry {
private final Map<String, DimensionData> registeredDimensions;
private final ImmutableSet<String> levelNames;
/**
* Initializes a new {@link DimensionRegistry} instance. This registry is required for 1.16+
* clients/servers to communicate, it constrains the dimension types and names the client can be
* sent in a Respawn action (dimension change). This WILL raise an IllegalArgumentException if the
* following is not met: - At least one valid DimensionData instance is provided - At least one
* valid world name is provided
*
* @param registeredDimensions a populated {@link ImmutableSet} containing dimension data types
* @param levelNames a populated {@link ImmutableSet} of the level (world) names the
* server offers
*/
public DimensionRegistry(ImmutableSet<DimensionData> registeredDimensions,
ImmutableSet<String> levelNames) {
Preconditions.checkNotNull(registeredDimensions,
"registeredDimensions cannot be null");
Preconditions.checkNotNull(levelNames,
"levelNames cannot be null");
Preconditions.checkArgument(registeredDimensions.size() > 0,
"registeredDimensions needs to be populated");
Preconditions.checkArgument(levelNames.size() > 0,
"levelNames needs to populated");
this.registeredDimensions = Maps.uniqueIndex(
registeredDimensions, DimensionData::getRegistryIdentifier);
this.levelNames = levelNames;
}
public Map<String, DimensionData> getRegisteredDimensions() {
return registeredDimensions;
}
public Set<String> getLevelNames() {
return levelNames;
}
/**
* Returns the internal dimension data type as used by the game.
*
* @param dimensionIdentifier how the dimension is identified by the connection
* @return game dimension data or null if not registered
*/
public @Nullable DimensionData getDimensionData(String dimensionIdentifier) {
return registeredDimensions.get(dimensionIdentifier);
}
/**
* Checks a {@link DimensionInfo} against this registry.
*
* @param toValidate the {@link DimensionInfo} to validate
* @return true: the dimension information is valid for this registry
*/
public boolean isValidFor(DimensionInfo toValidate) {
if (toValidate == null) {
return false;
}
return registeredDimensions.containsKey(toValidate.getRegistryIdentifier())
&& levelNames.contains(toValidate.getLevelName());
}
/**
* Encodes the stored Dimension registry as CompoundTag.
*
* @return the CompoundTag containing identifier:type mappings
*/
public ListBinaryTag encodeRegistry(ProtocolVersion version) {
ListBinaryTag.Builder<CompoundBinaryTag> listBuilder = ListBinaryTag
.builder(BinaryTagTypes.COMPOUND);
for (DimensionData iter : registeredDimensions.values()) {
listBuilder.add(iter.encodeAsCompoundTag(version));
}
return listBuilder.build();
}
/**
* Decodes a CompoundTag storing a dimension registry.
*
* @param toParse CompoundTag containing a dimension registry
*/
public static ImmutableSet<DimensionData> fromGameData(ListBinaryTag toParse,
ProtocolVersion version) {
Preconditions.checkNotNull(toParse, "ListTag cannot be null");
ImmutableSet.Builder<DimensionData> mappings = ImmutableSet.builder();
for (BinaryTag iter : toParse) {
if (iter instanceof CompoundBinaryTag) {
mappings.add(DimensionData.decodeRegistryEntry((CompoundBinaryTag) iter, version));
}
}
return mappings.build();
}
}

View File

@@ -20,16 +20,12 @@ package com.velocitypowered.proxy.protocol.packet;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
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.connection.registry.DimensionData;
import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.connection.registry.DimensionRegistry;
import com.velocitypowered.proxy.protocol.*; import com.velocitypowered.proxy.protocol.*;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class JoinGame implements MinecraftPacket { public class JoinGame implements MinecraftPacket {
@@ -46,15 +42,13 @@ public class JoinGame implements MinecraftPacket {
private int viewDistance; // 1.14+ private int viewDistance; // 1.14+
private boolean reducedDebugInfo; private boolean reducedDebugInfo;
private boolean showRespawnScreen; private boolean showRespawnScreen;
private DimensionRegistry dimensionRegistry; // 1.16+ private ImmutableSet<String> levelNames; // 1.16+
private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+ private DimensionInfo dimensionInfo; // 1.16+
private DimensionData currentDimensionData; // 1.16.2+ private CompoundBinaryTag currentDimensionData; // 1.16.2+
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private CompoundBinaryTag biomeRegistry; // 1.16.2+
private int simulationDistance; // 1.18+ private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition; private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private CompoundBinaryTag chatTypeRegistry; // placeholder, 1.19+
private @Nullable CompoundBinaryTag originalRegistryContainerTag;
public int getEntityId() { public int getEntityId() {
return entityId; return entityId;
@@ -132,14 +126,6 @@ public class JoinGame implements MinecraftPacket {
this.dimensionInfo = dimensionInfo; this.dimensionInfo = dimensionInfo;
} }
public DimensionRegistry getDimensionRegistry() {
return dimensionRegistry;
}
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public short getPreviousGamemode() { public short getPreviousGamemode() {
return previousGamemode; return previousGamemode;
} }
@@ -156,15 +142,7 @@ public class JoinGame implements MinecraftPacket {
this.isHardcore = isHardcore; this.isHardcore = isHardcore;
} }
public CompoundBinaryTag getBiomeRegistry() { public CompoundBinaryTag getCurrentDimensionData() {
return biomeRegistry;
}
public void setBiomeRegistry(CompoundBinaryTag biomeRegistry) {
this.biomeRegistry = biomeRegistry;
}
public DimensionData getCurrentDimensionData() {
return currentDimensionData; return currentDimensionData;
} }
@@ -184,12 +162,8 @@ public class JoinGame implements MinecraftPacket {
this.lastDeathPosition = lastDeathPosition; this.lastDeathPosition = lastDeathPosition;
} }
public CompoundBinaryTag getChatTypeRegistry() { public CompoundBinaryTag getRegistry() {
return chatTypeRegistry; return registry;
}
public void setChatTypeRegistry(CompoundBinaryTag chatTypeRegistry) {
this.chatTypeRegistry = chatTypeRegistry;
} }
@Override @Override
@@ -200,12 +174,16 @@ public class JoinGame implements MinecraftPacket {
+ ", dimension=" + dimension + ", dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed + ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty + ", difficulty=" + difficulty
+ ", isHardcore=" + isHardcore
+ ", maxPlayers=" + maxPlayers + ", maxPlayers=" + maxPlayers
+ ", levelType='" + levelType + '\'' + ", levelType='" + levelType + '\''
+ ", viewDistance=" + viewDistance + ", viewDistance=" + viewDistance
+ ", reducedDebugInfo=" + reducedDebugInfo + ", reducedDebugInfo=" + reducedDebugInfo
+ ", dimensionRegistry='" + dimensionRegistry + '\'' + ", showRespawnScreen=" + showRespawnScreen
+ ", levelNames=" + levelNames
+ ", registry='" + registry + '\''
+ ", dimensionInfo='" + dimensionInfo + '\'' + ", dimensionInfo='" + dimensionInfo + '\''
+ ", currentDimensionData='" + currentDimensionData + '\''
+ ", previousGamemode=" + previousGamemode + ", previousGamemode=" + previousGamemode
+ ", simulationDistance=" + simulationDistance + ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\'' + ", lastDeathPosition='" + lastDeathPosition + '\''
@@ -263,39 +241,16 @@ public class JoinGame implements MinecraftPacket {
this.isHardcore = (this.gamemode & 0x08) != 0; this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08; this.gamemode &= ~0x08;
} }
this.previousGamemode = buf.readByte(); this.previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
this.registry = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
ListBinaryTag dimensionRegistryContainer;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
.getList("value", BinaryTagTypes.COMPOUND);
this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome");
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
this.chatTypeRegistry = registryContainer.getCompound("minecraft:chat_type");
} else {
this.chatTypeRegistry = CompoundBinaryTag.empty();
}
} else {
dimensionRegistryContainer = registryContainer.getList("dimension",
BinaryTagTypes.COMPOUND);
}
ImmutableSet<DimensionData> readData =
DimensionRegistry.fromGameData(dimensionRegistryContainer, version);
this.dimensionRegistry = new DimensionRegistry(readData, levelNames);
this.originalRegistryContainerTag = registryContainer;
String dimensionIdentifier; String dimensionIdentifier;
String levelName = null; String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
.annotateWith(dimensionIdentifier, null);
} else { } else {
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf); levelName = ProtocolUtils.readString(buf);
@@ -315,9 +270,11 @@ public class JoinGame implements MinecraftPacket {
this.reducedDebugInfo = buf.readBoolean(); this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean(); this.showRespawnScreen = buf.readBoolean();
boolean isDebug = buf.readBoolean(); boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean(); boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
// optional death location // optional death location
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
@@ -379,40 +336,25 @@ public class JoinGame implements MinecraftPacket {
buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode); buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
} }
buf.writeByte(previousGamemode); buf.writeByte(previousGamemode);
ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames().toArray(new String[0]));
if (this.originalRegistryContainerTag != null) { ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
ProtocolUtils.writeCompoundTag(buf, this.originalRegistryContainerTag); ProtocolUtils.writeCompoundTag(buf, this.registry);
} else {
CompoundBinaryTag.Builder registryContainer = CompoundBinaryTag.builder();
ListBinaryTag encodedDimensionRegistry = dimensionRegistry.encodeRegistry(version);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag.Builder dimensionRegistryEntry = CompoundBinaryTag.builder();
dimensionRegistryEntry.putString("type", "minecraft:dimension_type");
dimensionRegistryEntry.put("value", encodedDimensionRegistry);
registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build());
registryContainer.put("minecraft:worldgen/biome", biomeRegistry);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
registryContainer.put("minecraft:chat_type", chatTypeRegistry);
}
} else {
registryContainer.put("dimension", encodedDimensionRegistry);
}
ProtocolUtils.writeCompoundTag(buf, registryContainer.build());
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails()); ProtocolUtils.writeCompoundTag(buf, currentDimensionData);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else { } else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
} }
buf.writeLong(partialHashedSeed); buf.writeLong(partialHashedSeed);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
ProtocolUtils.writeVarInt(buf, maxPlayers); ProtocolUtils.writeVarInt(buf, maxPlayers);
} else { } else {
buf.writeByte(maxPlayers); buf.writeByte(maxPlayers);
} }
ProtocolUtils.writeVarInt(buf, viewDistance); ProtocolUtils.writeVarInt(buf, viewDistance);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_18) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_18) >= 0) {
ProtocolUtils.writeVarInt(buf, simulationDistance); ProtocolUtils.writeVarInt(buf, simulationDistance);
@@ -420,6 +362,7 @@ public class JoinGame implements MinecraftPacket {
buf.writeBoolean(reducedDebugInfo); buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen); buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat()); buf.writeBoolean(dimensionInfo.isFlat());

View File

@@ -19,7 +19,6 @@ package com.velocitypowered.proxy.protocol.packet;
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.connection.registry.DimensionData;
import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
@@ -39,7 +38,7 @@ public class Respawn implements MinecraftPacket {
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 short previousGamemode; // 1.16+
private DimensionData 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+
public Respawn() { public Respawn() {
@@ -47,7 +46,7 @@ public class Respawn implements MinecraftPacket {
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo, String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData, short previousGamemode, CompoundBinaryTag currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition) { @Nullable Pair<String, Long> lastDeathPosition) {
this.dimension = dimension; this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed; this.partialHashedSeed = partialHashedSeed;
@@ -155,10 +154,8 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader()); this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader());
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
.annotateWith(dimensionIdentifier, null);
} else { } else {
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf); levelName = ProtocolUtils.readString(buf);
@@ -198,7 +195,7 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails()); ProtocolUtils.writeCompoundTag(buf, currentDimensionData);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else { } else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());

View File

@@ -1,19 +1,15 @@
pluginManagement {
repositories {
gradlePluginPortal()
}
}
plugins { plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0"
} }
rootProject.name = "velocity" rootProject.name = "velocity"
include(
"api", sequenceOf(
"proxy", "api",
"native" "proxy",
) "native",
findProject(":api")?.name = "velocity-api" ).forEach {
findProject(":proxy")?.name = "velocity-proxy" val project = ":velocity-$it"
findProject(":native")?.name = "velocity-native" include(project)
project(project).projectDir = file(it)
}