mirror of
https://github.com/GeyserMC/Geyser.git
synced 2026-01-06 15:41:50 +00:00
Merge remote-tracking branch 'upstream/master' into feature/floodgate-merge
# Conflicts: # bootstrap/mod/fabric/build.gradle.kts # bootstrap/velocity/base/build.gradle.kts # core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java
This commit is contained in:
@@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
## Supported Versions
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.93 and Minecraft Java 1.21.7. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.94 and Minecraft Java 1.21.7 - 1.21.8. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
||||
|
||||
@@ -34,7 +34,7 @@ public interface MinecraftVersion {
|
||||
|
||||
/**
|
||||
* Gets the Minecraft version as a String.
|
||||
* Example: "1.20.2", or "1.20.40/1.20.41"
|
||||
* Example formats: "1.21", "1.21.1", "1.21.22"
|
||||
*
|
||||
* @return the version string
|
||||
*/
|
||||
|
||||
@@ -6,8 +6,6 @@ plugins {
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
|
||||
compileOnly(libs.netty.transport.native.io.uring)
|
||||
|
||||
implementation(libs.cloud.bungee)
|
||||
implementation(libs.adventure.text.serializer.bungeecord)
|
||||
compileOnlyApi(libs.bungeecord.proxy)
|
||||
@@ -17,7 +15,6 @@ dependencies {
|
||||
|
||||
platformRelocate("net.md_5.bungee.jni")
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
|
||||
platformRelocate("net.kyori")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
@@ -31,16 +28,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
|
||||
exclude(dependency("io.netty:netty-handler:.*"))
|
||||
exclude(dependency("io.netty:netty-common:.*"))
|
||||
exclude(dependency("io.netty:netty-buffer:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver:.*"))
|
||||
exclude(dependency("io.netty:netty-transport:.*"))
|
||||
exclude(dependency("io.netty:netty-codec:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns:.*"))
|
||||
exclude(dependency("io.netty.*:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
package org.geysermc.geyser.platform.bungeecord;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.IoEventLoop;
|
||||
import io.netty.channel.IoEventLoopGroup;
|
||||
|
||||
@@ -104,6 +104,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
geyserLogger.warning("Unable to check the versions supported by this proxy! " + e.getMessage());
|
||||
}
|
||||
|
||||
// See https://github.com/SpigotMC/BungeeCord/blob/e62fc6c2916a991e00177c580986d8b1a22fdb41/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java#L138
|
||||
if (Boolean.getBoolean("bungee.io_uring")) {
|
||||
System.setProperty("Mcpl.io_uring", "true");
|
||||
}
|
||||
|
||||
if (!this.loadConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,25 +15,23 @@ dependencies {
|
||||
modApi(libs.fabric.api)
|
||||
|
||||
api(project(":mod", configuration = "namedElements"))
|
||||
shadow(project(path = ":mod", configuration = "transformProductionFabric")) {
|
||||
isTransitive = false
|
||||
}
|
||||
shadow(projects.core) { isTransitive = false }
|
||||
shadowBundle(project(path = ":mod", configuration = "transformProductionFabric"))
|
||||
shadowBundle(projects.core)
|
||||
includeTransitive(projects.core)
|
||||
|
||||
// These are NOT transitively included, and instead shadowed + relocated.
|
||||
// Avoids fabric complaining about non-SemVer versioning
|
||||
shadow(libs.protocol.connection) { isTransitive = false }
|
||||
shadow(libs.protocol.common) { isTransitive = false }
|
||||
shadow(libs.protocol.codec) { isTransitive = false }
|
||||
shadow(libs.raknet) { isTransitive = false }
|
||||
shadow(libs.mcprotocollib) { isTransitive = false }
|
||||
shadowBundle(libs.protocol.connection)
|
||||
shadowBundle(libs.protocol.common)
|
||||
shadowBundle(libs.protocol.codec)
|
||||
shadowBundle(libs.raknet)
|
||||
shadowBundle(libs.mcprotocollib)
|
||||
|
||||
// Since we also relocate cloudburst protocol: shade erosion common
|
||||
shadow(libs.erosion.common) { isTransitive = false }
|
||||
shadowBundle(libs.erosion.common)
|
||||
|
||||
// Let's shade in our own api/common module
|
||||
shadow(projects.api) { isTransitive = false }
|
||||
// Let's shade in our own api module
|
||||
shadowBundle(projects.api)
|
||||
|
||||
modImplementation(libs.cloud.fabric)
|
||||
include(libs.cloud.fabric)
|
||||
|
||||
@@ -29,19 +29,17 @@ dependencies {
|
||||
neoForge(libs.neoforge.minecraft)
|
||||
|
||||
api(project(":mod", configuration = "namedElements"))
|
||||
shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) {
|
||||
isTransitive = false
|
||||
}
|
||||
shadow(projects.core) { isTransitive = false }
|
||||
shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge"))
|
||||
shadowBundle(projects.core)
|
||||
|
||||
// Minecraft (1.21.2+) includes jackson. But an old version!
|
||||
shadow(libs.jackson.core) { isTransitive = false }
|
||||
shadow(libs.jackson.databind) { isTransitive = false }
|
||||
shadow(libs.jackson.dataformat.yaml) { isTransitive = false }
|
||||
shadow(libs.jackson.annotations) { isTransitive = false }
|
||||
shadowBundle(libs.jackson.core)
|
||||
shadowBundle(libs.jackson.databind)
|
||||
shadowBundle(libs.jackson.dataformat.yaml)
|
||||
shadowBundle(libs.jackson.annotations)
|
||||
|
||||
// Let's shade in our own api
|
||||
shadow(projects.api) { isTransitive = false }
|
||||
shadowBundle(projects.api)
|
||||
|
||||
// Include all transitive deps of core via JiJ
|
||||
includeTransitive(projects.core)
|
||||
|
||||
@@ -45,8 +45,6 @@ platformRelocate("me.lucko.commodore")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.viaversion)
|
||||
provided("com.mojang", "authlib")
|
||||
|
||||
@@ -62,23 +60,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
|
||||
// We cannot shade Netty, or else native libraries will not load
|
||||
// Needed because older Spigot builds do not provide the haproxy module
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
|
||||
exclude(dependency("io.netty:netty-handler:.*"))
|
||||
exclude(dependency("io.netty:netty-common:.*"))
|
||||
exclude(dependency("io.netty:netty-buffer:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver:.*"))
|
||||
exclude(dependency("io.netty:netty-transport:.*"))
|
||||
exclude(dependency("io.netty:netty-codec:.*"))
|
||||
exclude(dependency("io.netty:netty-codec-dns:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
|
||||
exclude("io.netty", libs.netty.codec.haproxy)
|
||||
|
||||
// Commodore includes Brigadier
|
||||
exclude(dependency("com.mojang:.*"))
|
||||
@@ -88,7 +71,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
modrinth {
|
||||
uploadFile.set(tasks.getByPath("shadowJar"))
|
||||
gameVersions.addAll("1.16.5", "1.17", "1.17.1", "1.18", "1.18.1", "1.18.2", "1.19",
|
||||
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6")
|
||||
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.21.6")
|
||||
loaders.addAll("spigot", "paper")
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation(libs.bundles.jline)
|
||||
|
||||
implementation(libs.bundles.log4j)
|
||||
}
|
||||
|
||||
@@ -33,11 +32,6 @@ tasks.named<Jar>("jar") {
|
||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
archiveBaseName.set("Geyser-Standalone")
|
||||
|
||||
// temporary measure - incubator's io_uring is not compatible with 4.2.1
|
||||
dependencies {
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
}
|
||||
|
||||
transform(Log4j2PluginsCacheFileTransformer())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,31 +19,6 @@ platformRelocate("org.yaml")
|
||||
platformRelocate("org.bstats") //todo
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
|
||||
exclude("com.google.*:*")
|
||||
|
||||
// Needed because Velocity provides every dependency except netty-resolver-dns
|
||||
exclude("io.netty.incubator:.*")
|
||||
exclude("io.netty:netty-transport-native-epoll:*")
|
||||
exclude("io.netty:netty-transport-native-unix-common:*")
|
||||
exclude("io.netty:netty-transport-native-kqueue:*")
|
||||
exclude("io.netty:netty-handler:*")
|
||||
exclude("io.netty:netty-common:*")
|
||||
exclude("io.netty:netty-buffer:*")
|
||||
exclude("io.netty:netty-resolver:*")
|
||||
exclude("io.netty:netty-transport:*")
|
||||
exclude("io.netty:netty-codec:*")
|
||||
exclude("io.netty:netty-codec-haproxy:*")
|
||||
exclude("org.slf4j:*")
|
||||
exclude("org.ow2.asm:*")
|
||||
|
||||
// Exclude all Kyori dependencies except the legacy NBT serializer
|
||||
exclude("net.kyori:adventure-api:*")
|
||||
exclude("net.kyori:examination-api:*")
|
||||
exclude("net.kyori:examination-string:*")
|
||||
exclude("net.kyori:adventure-text-serializer-gson:*")
|
||||
exclude("net.kyori:adventure-text-serializer-legacy:*")
|
||||
exclude("net.kyori:adventure-nbt:*")
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.velocity.api)
|
||||
@@ -53,27 +28,11 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
// Needed because Velocity provides every dependency except netty-resolver-dns
|
||||
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
|
||||
exclude(dependency("io.netty:netty-handler:.*"))
|
||||
exclude(dependency("io.netty:netty-common:.*"))
|
||||
exclude(dependency("io.netty:netty-buffer:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver:.*"))
|
||||
exclude(dependency("io.netty:netty-transport:.*"))
|
||||
exclude(dependency("io.netty:netty-codec:.*"))
|
||||
exclude(dependency("io.netty:netty-codec-haproxy:.*"))
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
exclude(dependency("io.netty:.*"))
|
||||
exclude(dependency("org.slf4j:.*"))
|
||||
exclude(dependency("org.ow2.asm:.*"))
|
||||
// Exclude all Kyori dependencies except the legacy NBT serializer
|
||||
exclude(dependency("net.kyori:adventure-api:.*"))
|
||||
exclude(dependency("net.kyori:examination-api:.*"))
|
||||
exclude(dependency("net.kyori:examination-string:.*"))
|
||||
exclude(dependency("net.kyori:adventure-text-serializer-gson:.*"))
|
||||
exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*"))
|
||||
exclude(dependency("net.kyori:adventure-nbt:.*"))
|
||||
// Exclude all Kyori dependencies
|
||||
exclude(dependency("net.kyori:.*:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,11 @@ public class GeyserVelocityPlatform implements GeyserBootstrap, IsolatedPlatform
|
||||
geyserLogger.error("/_____________\\");
|
||||
}
|
||||
|
||||
// Only use io_uring when velocity does as well
|
||||
if (Boolean.getBoolean("velocity.enable-iouring-transport")) {
|
||||
System.setProperty("Mcpl.io_uring", "true");
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
exclude(dependency("io.netty:.*"))
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
exclude(dependency("org.slf4j:.*"))
|
||||
exclude(dependency("org.ow2.asm:.*"))
|
||||
}
|
||||
|
||||
@@ -42,9 +42,12 @@ fun Project.relocate(pattern: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.exclude(group: String) {
|
||||
// Excludes all dependencies from a module - except one
|
||||
fun Project.exclude(group: String, lib: Provider<MinimalExternalModuleDependency>) {
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
exclude(group)
|
||||
dependencies {
|
||||
exclude { it.moduleGroup == group && it.moduleName != lib.get().module.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +61,6 @@ fun Project.platformRelocate(pattern: String, exclusion: String = "") {
|
||||
|
||||
val providedDependencies = mutableMapOf<String, MutableSet<String>>()
|
||||
|
||||
fun getProvidedDependenciesForProject(projectName: String): MutableSet<String> {
|
||||
return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet()
|
||||
}
|
||||
|
||||
fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) {
|
||||
providedDependencies.getOrPut(project.name) { mutableSetOf() }
|
||||
.add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}")
|
||||
|
||||
@@ -41,11 +41,6 @@ repositories {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// BungeeCord
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// NeoForge
|
||||
maven("https://maven.neoforged.net/releases") {
|
||||
mavenContent { releasesOnly() }
|
||||
@@ -62,9 +57,6 @@ repositories {
|
||||
name = "viaversion"
|
||||
}
|
||||
|
||||
// For Adventure snapshots
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
|
||||
// Jitpack for e.g. MCPL
|
||||
maven("https://jitpack.io") {
|
||||
content { includeGroupByRegex("com\\.github\\..*") }
|
||||
|
||||
@@ -24,8 +24,8 @@ provided("io.netty", "netty-transport-native-epoll")
|
||||
provided("io.netty", "netty-transport-native-unix-common")
|
||||
provided("io.netty", "netty-transport-classes-kqueue")
|
||||
provided("io.netty", "netty-transport-native-kqueue")
|
||||
provided("io.netty.incubator", "netty-incubator-transport-native-io_uring")
|
||||
provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring")
|
||||
provided("io.netty", "netty-transport-native-io_uring")
|
||||
provided("io.netty", "netty-transport-classes-io_uring")
|
||||
provided("io.netty", "netty-handler")
|
||||
provided("io.netty", "netty-common")
|
||||
provided("io.netty", "netty-buffer")
|
||||
@@ -33,9 +33,6 @@ provided("io.netty", "netty-resolver")
|
||||
provided("io.netty", "netty-transport")
|
||||
provided("io.netty", "netty-codec")
|
||||
provided("io.netty", "netty-codec-base")
|
||||
provided("io.netty", "netty-resolver-dns")
|
||||
provided("io.netty", "netty-resolver-dns-native-macos")
|
||||
provided("io.netty", "netty-resolver-dns-classes-macos")
|
||||
provided("org.ow2.asm", "asm")
|
||||
|
||||
// cloud-fabric/cloud-neoforge jij's all cloud depends already
|
||||
@@ -58,6 +55,11 @@ indra {
|
||||
|
||||
configurations {
|
||||
create("includeTransitive").isTransitive = true
|
||||
create("shadowBundle") {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
isTransitive = false
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -71,7 +73,7 @@ tasks {
|
||||
|
||||
shadowJar {
|
||||
// Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be
|
||||
configurations = listOf(project.configurations.shadow.get())
|
||||
configurations = listOf(project.configurations.getByName("shadowBundle"))
|
||||
// The remapped shadowJar is the final desired mod jar
|
||||
archiveVersion.set(project.version.toString())
|
||||
archiveClassifier.set("shaded")
|
||||
@@ -93,18 +95,16 @@ tasks {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val providedDependencies = getProvidedDependenciesForProject(project.name)
|
||||
|
||||
// These are shaded, no need to JiJ them
|
||||
configurations["shadow"].dependencies.forEach {shadowed ->
|
||||
//println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
|
||||
providedDependencies.add("${shadowed.group}:${shadowed.name}")
|
||||
}
|
||||
val providedDependencies = providedDependencies[project.name]!!
|
||||
val shadedDependencies = configurations.getByName("shadowBundle")
|
||||
.dependencies.stream().map { dependency -> "${dependency.group}:${dependency.name}" }.toList()
|
||||
|
||||
// Now: Include all transitive dependencies that aren't excluded
|
||||
configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep ->
|
||||
if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}")
|
||||
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) {
|
||||
val name = "${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}"
|
||||
if (!shadedDependencies.contains(name) and !providedDependencies.contains(name)
|
||||
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")
|
||||
) {
|
||||
println("Including dependency via JiJ: ${dep.id}")
|
||||
dependencies.add("include", dep.moduleVersion.id.toString())
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@ modrinth {
|
||||
versionNumber.set(projectVersion(project))
|
||||
versionType.set("beta")
|
||||
changelog.set(System.getenv("CHANGELOG") ?: "")
|
||||
gameVersions.add(libs.minecraft.get().version as String)
|
||||
gameVersions.addAll("1.21.7", libs.minecraft.get().version as String)
|
||||
failSilently.set(true)
|
||||
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
|
||||
@@ -37,18 +37,17 @@ dependencies {
|
||||
exclude("io.netty", "*")
|
||||
}
|
||||
|
||||
implementation(libs.netty.resolver.dns)
|
||||
implementation(libs.netty.resolver.dns.native.macos) { artifact { classifier = "osx-x86_64" } }
|
||||
implementation(libs.netty.codec.haproxy)
|
||||
|
||||
// Network dependencies we are updating ourselves
|
||||
api(libs.netty.handler)
|
||||
implementation(libs.netty.codec.haproxy)
|
||||
|
||||
api(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
|
||||
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
|
||||
// kqueue is macos only
|
||||
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
|
||||
//api(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
|
||||
//implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
|
||||
api(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
|
||||
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
|
||||
|
||||
// Adventure text serialization
|
||||
api(libs.bundles.adventure)
|
||||
|
||||
@@ -42,7 +42,6 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils;
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
@@ -110,7 +109,6 @@ import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -796,11 +794,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
@Override
|
||||
public @NonNull List<MinecraftVersion> supportedBedrockVersions() {
|
||||
ArrayList<MinecraftVersion> versions = new ArrayList<>();
|
||||
for (BedrockCodec codec : GameProtocol.SUPPORTED_BEDROCK_CODECS) {
|
||||
versions.add(new MinecraftVersionImpl(codec.getMinecraftVersion(), codec.getProtocolVersion()));
|
||||
}
|
||||
return Collections.unmodifiableList(versions);
|
||||
return Collections.unmodifiableList(GameProtocol.SUPPORTED_BEDROCK_VERSIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
@@ -43,6 +43,25 @@ import java.util.List;
|
||||
|
||||
public class VersionCommand extends GeyserCommand {
|
||||
|
||||
private static final String SUPPORTED_BEDROCK_RANGE;
|
||||
private static final String SUPPORTED_JAVA_RANGE;
|
||||
|
||||
static {
|
||||
List<MinecraftVersion> bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_VERSIONS;
|
||||
if (bedrockVersions.size() > 1) {
|
||||
SUPPORTED_BEDROCK_RANGE = bedrockVersions.get(0).versionString() + " - " + bedrockVersions.get(bedrockVersions.size() - 1).versionString();
|
||||
} else {
|
||||
SUPPORTED_BEDROCK_RANGE = bedrockVersions.get(0).versionString();
|
||||
}
|
||||
|
||||
List<String> javaVersions = GameProtocol.getJavaVersions();
|
||||
if (javaVersions.size() > 1) {
|
||||
SUPPORTED_JAVA_RANGE = javaVersions.get(0) + " - " + javaVersions.get(javaVersions.size() - 1);
|
||||
} else {
|
||||
SUPPORTED_JAVA_RANGE = javaVersions.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
|
||||
public VersionCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
@@ -54,23 +73,8 @@ public class VersionCommand extends GeyserCommand {
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
|
||||
String bedrockVersions;
|
||||
List<BedrockCodec> supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS;
|
||||
if (supportedCodecs.size() > 1) {
|
||||
bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion();
|
||||
} else {
|
||||
bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.get(0).getMinecraftVersion();
|
||||
}
|
||||
String javaVersions;
|
||||
List<String> supportedJavaVersions = GameProtocol.getJavaVersions();
|
||||
if (supportedJavaVersions.size() > 1) {
|
||||
javaVersions = supportedJavaVersions.get(0) + " - " + supportedJavaVersions.get(supportedJavaVersions.size() - 1);
|
||||
} else {
|
||||
javaVersions = supportedJavaVersions.get(0);
|
||||
}
|
||||
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(),
|
||||
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
|
||||
GeyserImpl.NAME, GeyserImpl.VERSION, SUPPORTED_JAVA_RANGE, SUPPORTED_BEDROCK_RANGE));
|
||||
|
||||
// Disable update checking in dev mode and for players in Geyser Standalone
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
|
||||
@@ -34,11 +34,11 @@ import com.google.common.io.Files;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.api.util.BedrockPlatform;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@@ -223,9 +223,9 @@ public class DumpInfo {
|
||||
private final int javaProtocol;
|
||||
|
||||
MCInfo() {
|
||||
this.bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockCodec::getMinecraftVersion).toList();
|
||||
this.bedrockProtocols = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockCodec::getProtocolVersion).toList();
|
||||
this.defaultBedrockProtocol = GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion();
|
||||
this.bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_VERSIONS.stream().map(MinecraftVersion::versionString).toList();
|
||||
this.bedrockProtocols = GameProtocol.SUPPORTED_BEDROCK_PROTOCOLS;
|
||||
this.defaultBedrockProtocol = GameProtocol.DEFAULT_BEDROCK_PROTOCOL;
|
||||
this.javaVersions = GameProtocol.getJavaVersions();
|
||||
this.javaProtocol = GameProtocol.getJavaProtocolVersion();
|
||||
}
|
||||
|
||||
@@ -1191,6 +1191,7 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataTypes.INT, WolfEntity::setCollarColor)
|
||||
.addTranslator(MetadataTypes.INT, WolfEntity::setWolfAngerTime)
|
||||
.addTranslator(MetadataTypes.WOLF_VARIANT, WolfEntity::setVariant)
|
||||
.addTranslator(null) // sound variant; these aren't clientsided anyways... right??
|
||||
.build();
|
||||
|
||||
// As of 1.18 these don't track entity data at all
|
||||
|
||||
@@ -75,10 +75,11 @@ public class FireworkEntity extends Entity {
|
||||
if (optional.isPresent() && optional.getAsInt() == session.getPlayerEntity().getEntityId()) {
|
||||
// If we don't send this, the bedrock client will always stop boosting after 20 ticks
|
||||
// However this is not the case for Java as the player will stop boosting after entity despawn.
|
||||
// So we let player boost "infinitely" and then only stop them when the entity despawn.
|
||||
// So we let player boost for a really long time and then only stop them when the entity despawn.
|
||||
// Also doing this allow player to boost simply by having a fireworks rocket attached to them
|
||||
// and not necessary have to use a rocket (as some plugin do this to boost player)
|
||||
sendElytraBoost(Integer.MAX_VALUE);
|
||||
// You can't really send Integer.MAX_VALUE since Bedrock client doesn't seem to like way too large number very much (as of 1.21.73).
|
||||
sendElytraBoost(1000000);
|
||||
this.attachedToSession = true;
|
||||
|
||||
// We need to keep track of the fireworks rockets.
|
||||
@@ -99,7 +100,7 @@ public class FireworkEntity extends Entity {
|
||||
// Else player will stop boosting even if the fireworks is not attached to them or there is a fireworks that is boosting them
|
||||
// and not just this one.
|
||||
if (this.attachedToSession && session.getAttachedFireworkRockets().isEmpty()) {
|
||||
// Since we send an effect packet for player to boost "infinitely", we have to stop them when the entity despawn.
|
||||
// Since we send an effect packet for player to boost really long, we have to stop them when the entity despawn.
|
||||
sendElytraBoost(0);
|
||||
this.attachedToSession = false;
|
||||
}
|
||||
@@ -112,6 +113,7 @@ public class FireworkEntity extends Entity {
|
||||
movementEffect.setDuration(duration);
|
||||
movementEffect.setEffectType(MovementEffectType.GLIDE_BOOST);
|
||||
movementEffect.setEntityRuntimeId(session.getPlayerEntity().getGeyserId());
|
||||
movementEffect.setTick(session.getClientTicks());
|
||||
session.sendUpstreamPacket(movementEffect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,12 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
@Getter @Setter
|
||||
private Vector3f lastTickEndVelocity = Vector3f.ZERO;
|
||||
|
||||
/**
|
||||
* The client claimed interact rotation, intended for touch (pocket) user.
|
||||
*/
|
||||
@Getter @Setter
|
||||
private Vector2f bedrockInteractRotation = Vector2f.ZERO;
|
||||
|
||||
/**
|
||||
* Determines if our position is currently out-of-sync with the Java server
|
||||
* due to our workaround for the void floor
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.network;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
|
||||
@@ -32,13 +34,15 @@ import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.impl.MinecraftVersionImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.PacketCodec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Contains information about the supported protocols in Geyser.
|
||||
@@ -46,17 +50,30 @@ import java.util.StringJoiner;
|
||||
public final class GameProtocol {
|
||||
|
||||
/**
|
||||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
* All Bedrock protocol codecs that Geyser uses
|
||||
*/
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v819.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.93")
|
||||
.build());
|
||||
private static final List<BedrockCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
* All bedrock protocol versions that Geyser supports
|
||||
*/
|
||||
public static final List<BedrockCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
|
||||
public static final IntList SUPPORTED_BEDROCK_PROTOCOLS = new IntArrayList();
|
||||
|
||||
/**
|
||||
* All bedrock minecraft versions that Geyser supports.
|
||||
* There may be multiple MinecraftVersions with the same protocol version.
|
||||
*/
|
||||
public static final List<MinecraftVersion> SUPPORTED_BEDROCK_VERSIONS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The latest Bedrock protocol version that Geyser supports.
|
||||
*/
|
||||
public static final int DEFAULT_BEDROCK_PROTOCOL;
|
||||
|
||||
/**
|
||||
* The latest Bedrock Minecraft version that Geyser supports.
|
||||
*/
|
||||
public static final String DEFAULT_BEDROCK_VERSION;
|
||||
|
||||
/**
|
||||
* Java codec that is supported. We only ever support one version for
|
||||
@@ -65,16 +82,44 @@ public final class GameProtocol {
|
||||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v786.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.70 - 1.21.73")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v800.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.80 - 1.21.84")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add((CodecProcessor.processCodec(Bedrock_v818.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.90 - 1.21.92")
|
||||
.build())));
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
// Strict ordering
|
||||
register(Bedrock_v786.CODEC, "1.21.70", "1.21.71", "1.21.72", "1.21.73");
|
||||
register(Bedrock_v800.CODEC, "1.21.80", "1.21.81", "1.21.82", "1.21.83", "1.21.84");
|
||||
register(Bedrock_v818.CODEC, "1.21.90", "1.21.91", "1.21.92");
|
||||
register(Bedrock_v819.CODEC, "1.21.93", "1.21.94");
|
||||
|
||||
MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1);
|
||||
DEFAULT_BEDROCK_VERSION = latestBedrock.versionString();
|
||||
DEFAULT_BEDROCK_PROTOCOL = latestBedrock.protocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a bedrock codec, along with its protocol version and minecraft version(s).
|
||||
* This method must be called in ascending order in terms of protocol version.
|
||||
*
|
||||
* @param codec the codec to register
|
||||
* @param minecraftVersions all versions the codec supports, in ascending order
|
||||
*/
|
||||
private static void register(BedrockCodec codec, String... minecraftVersions) {
|
||||
// modify packet serializers to better fit our use
|
||||
codec = CodecProcessor.processCodec(codec);
|
||||
|
||||
SUPPORTED_BEDROCK_CODECS.add(codec);
|
||||
SUPPORTED_BEDROCK_PROTOCOLS.add(codec.getProtocolVersion());
|
||||
|
||||
for (String version : minecraftVersions) {
|
||||
SUPPORTED_BEDROCK_VERSIONS.add(new MinecraftVersionImpl(version, codec.getProtocolVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a bedrock codec, its protocol version, and a single minecraft version which is taken from the codec.
|
||||
* This method must be called in ascending order in terms of protocol version.
|
||||
*
|
||||
* @param codec the codec to register
|
||||
*/
|
||||
private static void register(BedrockCodec codec) {
|
||||
register(codec, codec.getMinecraftVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,22 +154,13 @@ public final class GameProtocol {
|
||||
return session.protocolVersion() == Bedrock_v800.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
|
||||
*
|
||||
* @return the packet codec for Minecraft: Java Edition
|
||||
*/
|
||||
public static PacketCodec getJavaCodec() {
|
||||
return DEFAULT_JAVA_CODEC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported Minecraft: Java Edition version names.
|
||||
*
|
||||
* @return the supported Minecraft: Java Edition version names
|
||||
*/
|
||||
public static List<String> getJavaVersions() {
|
||||
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion());
|
||||
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion(), "1.21.8");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,31 +178,23 @@ public final class GameProtocol {
|
||||
* @return the supported Minecraft: Java Edition version
|
||||
*/
|
||||
public static String getJavaMinecraftVersion() {
|
||||
return DEFAULT_JAVA_CODEC.getMinecraftVersion();
|
||||
return "1.21.8";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string showing all supported Bedrock versions for this Geyser instance
|
||||
*/
|
||||
public static String getAllSupportedBedrockVersions() {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (BedrockCodec packetCodec : SUPPORTED_BEDROCK_CODECS) {
|
||||
joiner.add(packetCodec.getMinecraftVersion());
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
return SUPPORTED_BEDROCK_VERSIONS.stream()
|
||||
.map(MinecraftVersion::versionString)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string showing all supported Java versions for this Geyser instance
|
||||
*/
|
||||
public static String getAllSupportedJavaVersions() {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (String version : getJavaVersions()) {
|
||||
joiner.add(version);
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
return String.join(", ", getJavaVersions());
|
||||
}
|
||||
|
||||
private GameProtocol() {
|
||||
|
||||
@@ -28,6 +28,7 @@ package org.geysermc.geyser.network;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||
@@ -41,16 +42,13 @@ import java.net.InetSocketAddress;
|
||||
public class GeyserServerInitializer extends BedrockServerInitializer {
|
||||
private final GeyserImpl geyser;
|
||||
// There is a constructor that doesn't require inputting threads, but older Netty versions don't have it
|
||||
@Getter
|
||||
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser player thread"));
|
||||
|
||||
public GeyserServerInitializer(GeyserImpl geyser) {
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
public DefaultEventLoopGroup getEventLoopGroup() {
|
||||
return eventLoopGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSession(@NonNull BedrockServerSession bedrockServerSession) {
|
||||
try {
|
||||
|
||||
@@ -60,11 +60,11 @@ import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
|
||||
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.pack.url.GeyserUrlPackCodec;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
@@ -120,8 +120,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
private boolean setCorrectCodec(int protocolVersion) {
|
||||
BedrockCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
|
||||
if (packetCodec == null) {
|
||||
// None of our Bedrock codecs support this client version, so we can simply compare it to our default protocol.
|
||||
String supportedVersions = GameProtocol.getAllSupportedBedrockVersions();
|
||||
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_PROTOCOL) {
|
||||
// Too early to determine session locale
|
||||
String disconnectMessage = GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions);
|
||||
// If the latest release matches this version, then let the user know.
|
||||
@@ -132,7 +133,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
}
|
||||
session.disconnect(disconnectMessage);
|
||||
return false;
|
||||
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_PROTOCOL) {
|
||||
// A note on the following line: various older client versions have different forms of DisconnectPacket.
|
||||
// Using only the latest BedrockCompat for such clients leads to inaccurate disconnect messages: https://github.com/GeyserMC/Geyser/issues/4378
|
||||
// This updates the BedrockCompat protocol if necessary:
|
||||
@@ -344,21 +345,18 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourcePack pack = holder.pack();
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
PackCodec codec = pack.codec();
|
||||
|
||||
PackCodec codec = holder.codec();
|
||||
// If a remote pack ends up here, that usually implies that a client was not able to download the pack
|
||||
if (codec instanceof UrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.testRemotePack(session, urlPackCodec, packet.getPackId(), packet.getPackVersion());
|
||||
|
||||
if (!resourcePackLoadEvent.value(pack.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
|
||||
if (codec instanceof GeyserUrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.testRemotePack(session, urlPackCodec, holder);
|
||||
if (!resourcePackLoadEvent.value(holder.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
|
||||
session.disconnect("Unable to provide downloaded resource pack. Contact an administrator!");
|
||||
currentlySendingChunks = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setPackVersion(packet.getPackVersion());
|
||||
|
||||
@@ -30,14 +30,10 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.kqueue.KQueue;
|
||||
import io.netty.channel.kqueue.KQueueDatagramChannel;
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.uring.IoUring;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import lombok.Getter;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
||||
@@ -64,13 +60,13 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.network.helper.TransportHelper;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT;
|
||||
@@ -82,7 +78,7 @@ public final class GeyserServer {
|
||||
/*
|
||||
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
||||
*/
|
||||
private static final String PING_VERSION = pingVersion();
|
||||
private static final String PING_VERSION = GameProtocol.DEFAULT_BEDROCK_VERSION;
|
||||
private static final int PING_VERSION_BYTES_LENGTH = PING_VERSION.getBytes(StandardCharsets.UTF_8).length;
|
||||
private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length;
|
||||
/**
|
||||
@@ -90,7 +86,8 @@ public final class GeyserServer {
|
||||
*/
|
||||
private static final int MAGIC_RAKNET_LENGTH = 338;
|
||||
|
||||
private static final Transport TRANSPORT = compatibleTransport();
|
||||
// Let MCPL determine the transport type -> less code duplication and risk of ending up with 2 different types
|
||||
private static final TransportHelper.TransportType TRANSPORT = TransportHelper.TRANSPORT_TYPE;
|
||||
|
||||
/**
|
||||
* See {@link EventLoopGroup#shutdownGracefully(long, long, TimeUnit)}
|
||||
@@ -124,8 +121,8 @@ public final class GeyserServer {
|
||||
this.geyser = geyser;
|
||||
this.listenCount = Bootstraps.isReusePortAvailable() ? Integer.getInteger("Geyser.ListenCount", 2) : 1;
|
||||
GeyserImpl.getInstance().getLogger().debug("Listen thread count: " + listenCount);
|
||||
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount);
|
||||
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
|
||||
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount, new DefaultThreadFactory("GeyserServer", true));
|
||||
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount, new DefaultThreadFactory("GeyserServerChild", true));
|
||||
|
||||
this.bootstrap = this.createBootstrap();
|
||||
// setup SO_REUSEPORT if exists - or, if the option does not actually exist, reset listen count
|
||||
@@ -201,16 +198,18 @@ public final class GeyserServer {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
private ServerBootstrap createBootstrap() {
|
||||
if (this.geyser.getConfig().isDebugMode()) {
|
||||
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
|
||||
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
|
||||
this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name());
|
||||
if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) {
|
||||
if (System.getProperties().contains("disableNativeEventLoop")) {
|
||||
this.geyser.getLogger().debug("EventLoop type is NIO because native event loops are disabled.");
|
||||
} else {
|
||||
// Use lambda here, not method reference, or else NoClassDefFoundError for Epoll/KQueue will not be caught
|
||||
this.geyser.getLogger().debug("Reason for no Epoll: " + throwableOrCaught(() -> Epoll.unavailabilityCause()));
|
||||
this.geyser.getLogger().debug("Reason for no KQueue: " + throwableOrCaught(() -> KQueue.unavailabilityCause()));
|
||||
this.geyser.getLogger().debug("Reason for no IoUring: " + throwableOrCaught(() -> IoUring.unavailabilityCause()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,7 +228,7 @@ public final class GeyserServer {
|
||||
this.geyser.getLogger().debug("Setting RakNet send cookie to " + rakSendCookie);
|
||||
|
||||
return new ServerBootstrap()
|
||||
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
|
||||
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass()))
|
||||
.group(group, childGroup)
|
||||
.option(RakChannelOption.RAK_HANDLE_PING, true)
|
||||
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
|
||||
@@ -312,7 +311,7 @@ public final class GeyserServer {
|
||||
.edition("MCPE")
|
||||
.gameType("Survival") // Can only be Survival or Creative as of 1.16.210.59
|
||||
.nintendoLimited(false)
|
||||
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
|
||||
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_PROTOCOL)
|
||||
.version(PING_VERSION)
|
||||
.ipv4Port(this.broadcastPort)
|
||||
.ipv6Port(this.broadcastPort)
|
||||
@@ -387,17 +386,6 @@ public final class GeyserServer {
|
||||
return pong;
|
||||
}
|
||||
|
||||
private static String pingVersion() {
|
||||
// BedrockPong version is required to not be empty as of 1.16.210.59.
|
||||
// Can only contain . and numbers, so use the latest version instead of sending all
|
||||
var version = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
|
||||
var versionSplit = version.split("/");
|
||||
if (versionSplit.length > 1) {
|
||||
version = versionSplit[versionSplit.length - 1];
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the throwable from the given supplier, or the throwable caught while calling the supplier.
|
||||
*/
|
||||
@@ -429,38 +417,4 @@ public final class GeyserServer {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static Transport compatibleTransport() {
|
||||
// FIXME supporting io_uring post 4.2 requires more changes
|
||||
// if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
|
||||
// && IOUring.isAvailable()
|
||||
// && Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
|
||||
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
|
||||
// }
|
||||
|
||||
if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) {
|
||||
return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new);
|
||||
}
|
||||
|
||||
if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) {
|
||||
return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new);
|
||||
}
|
||||
|
||||
return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new);
|
||||
}
|
||||
|
||||
private record Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used so implementations can opt to remove these dependencies if so desired
|
||||
*/
|
||||
private static boolean isClassAvailable(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* Manages a Minecraft Java session over our LocalChannel implementations.
|
||||
*/
|
||||
public final class LocalSession extends ClientNetworkSession {
|
||||
private static DefaultEventLoopGroup DEFAULT_EVENT_LOOP_GROUP;
|
||||
private static EventLoopGroup DEFAULT_EVENT_LOOP_GROUP;
|
||||
private static PreferredDirectByteBufAllocator PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR = null;
|
||||
|
||||
private final SocketAddress spoofedRemoteAddress;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.With;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
@@ -32,6 +33,7 @@ import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@With
|
||||
public record GeyserResourcePack(
|
||||
@NonNull PackCodec codec,
|
||||
@NonNull ResourcePackManifest manifest,
|
||||
|
||||
@@ -25,11 +25,17 @@
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.With;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.option.PriorityOption;
|
||||
import org.geysermc.geyser.pack.option.OptionHolder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@With
|
||||
public record ResourcePackHolder(
|
||||
@NonNull GeyserResourcePack pack,
|
||||
@NonNull OptionHolder optionHolder
|
||||
@@ -42,4 +48,16 @@ public record ResourcePackHolder(
|
||||
public ResourcePack resourcePack() {
|
||||
return this.pack;
|
||||
}
|
||||
|
||||
public PackCodec codec() {
|
||||
return this.pack.codec();
|
||||
}
|
||||
|
||||
public UUID uuid() {
|
||||
return this.pack.uuid();
|
||||
}
|
||||
|
||||
public ResourcePackManifest.Version version() {
|
||||
return pack.manifest().header().version();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ package org.geysermc.geyser.pack.url;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
@@ -46,6 +50,12 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
private GeyserUrlPackCodec(@NonNull String url, PathPackCodec fallback) {
|
||||
Objects.requireNonNull(url);
|
||||
this.fallback = fallback;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the sha256!");
|
||||
@@ -87,4 +97,43 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
public @NonNull String url() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether Geyser's "mirror" of the remote pack needs to be updated.
|
||||
* This is triggered if a Bedrock client is unable to download a pack
|
||||
* @param holder the current resource pack holder with the "originally" known manifest
|
||||
*/
|
||||
public void testForChanges(ResourcePackHolder holder) {
|
||||
ResourcePackLoader.downloadPack(url, true)
|
||||
.thenAccept(backingPathCodec -> {
|
||||
GeyserResourcePack updatedPack = ResourcePackLoader.readPack(backingPathCodec.path())
|
||||
.contentKey(holder.pack().contentKey())
|
||||
.build();
|
||||
if (updatedPack.uuid().equals(holder.uuid())) {
|
||||
var currentVersion = holder.version().toString();
|
||||
var updatedVersion = updatedPack.manifest().header().version().toString();
|
||||
if (currentVersion.equals(updatedVersion)) {
|
||||
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
|
||||
return;
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
|
||||
.formatted(currentVersion, updatedVersion, url));
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
|
||||
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
|
||||
}
|
||||
|
||||
// Update to new url pack codec (same url, updated fallback), and keep content key
|
||||
GeyserResourcePack pack = updatedPack.withCodec(new GeyserUrlPackCodec(url, backingPathCodec));
|
||||
// Keep the pack options that were previously set
|
||||
Registries.RESOURCE_PACKS.get().put(updatedPack.uuid(), holder.withPack(pack));
|
||||
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), throwable);
|
||||
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -80,7 +79,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
* If a client rejects such a pack, it falls back to the old method, and Geyser serves a cached variant.
|
||||
*/
|
||||
private static final Cache<String, UrlPackCodec> CACHED_FAILED_PACKS = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.expireAfterWrite(15, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||
@@ -268,77 +267,28 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
*
|
||||
* @param codec the codec of the resource pack that wasn't successfully downloaded by a Bedrock client.
|
||||
*/
|
||||
public static void testRemotePack(GeyserSession session, UrlPackCodec codec, UUID packId, String packVersion) {
|
||||
public static void testRemotePack(GeyserSession session, GeyserUrlPackCodec codec, ResourcePackHolder holder) {
|
||||
if (CACHED_FAILED_PACKS.getIfPresent(codec.url()) == null) {
|
||||
String url = codec.url();
|
||||
CACHED_FAILED_PACKS.put(url, codec);
|
||||
CACHED_FAILED_PACKS.put(codec.url(), codec);
|
||||
GeyserImpl.getInstance().getLogger().warning(
|
||||
"Bedrock client (%s, playing on %s) was not able to download the resource pack at %s. Checking for changes now:"
|
||||
"Bedrock client (%s, playing on %s) was not able to download the resource pack at %s!"
|
||||
.formatted(session.bedrockUsername(), session.getClientData().getDeviceOs().name(), codec.url())
|
||||
);
|
||||
|
||||
downloadPack(codec.url(), true).whenComplete((pathPackCodec, e) -> {
|
||||
if (e != null) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
|
||||
if (GeyserImpl.getInstance().getLogger().isDebug()) {
|
||||
e.printStackTrace();
|
||||
if (pathPackCodec != null) {
|
||||
deleteFile(pathPackCodec.path());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GeyserResourcePack newPack = readPack(pathPackCodec.path()).build();
|
||||
if (newPack.uuid().equals(packId)) {
|
||||
if (packVersion.equals(newPack.manifest().header().version().toString())) {
|
||||
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
|
||||
.formatted(packVersion, newPack.manifest().header().version().toString(), url));
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
|
||||
}
|
||||
|
||||
// This should be safe to do as we're not directly using registries to read packs.
|
||||
// Instead, they're cached per-session in the SessionLoadResourcePacks event
|
||||
Registries.RESOURCE_PACKS.get().remove(packId);
|
||||
Registries.RESOURCE_PACKS.get().put(newPack.uuid(), ResourcePackHolder.of(newPack));
|
||||
|
||||
if (codec instanceof GeyserUrlPackCodec geyserUrlPackCodec
|
||||
&& geyserUrlPackCodec.getFallback() != null) {
|
||||
Path path = geyserUrlPackCodec.getFallback().path();
|
||||
try {
|
||||
GeyserImpl.getInstance().getScheduledThread().schedule(() -> {
|
||||
CACHED_FAILED_PACKS.invalidate(codec.url());
|
||||
deleteFile(path);
|
||||
}, 5, TimeUnit.MINUTES);
|
||||
} catch (RejectedExecutionException exception) {
|
||||
// No scheduling here, probably because we're shutting down?
|
||||
deleteFile(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFile(Path path) {
|
||||
if (path.toFile().exists()) {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to delete old pack! " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
if (!Registries.RESOURCE_PACKS.get().containsKey(holder.uuid())) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Skipping remote resource pack check as pack is not present in global resource pack registry.");
|
||||
return;
|
||||
}
|
||||
|
||||
codec.testForChanges(holder);
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean force) throws IllegalArgumentException {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Path path;
|
||||
try {
|
||||
path = WebUtils.downloadRemotePack(url, testing);
|
||||
path = WebUtils.downloadRemotePack(url, force);
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.BlockItem;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.level.BedrockDimension;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
@@ -683,6 +684,12 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
|
||||
*/
|
||||
private int ticks;
|
||||
|
||||
/**
|
||||
* The number of ticks that have elapsed since the start of this session according to the client.
|
||||
*/
|
||||
@Setter
|
||||
private long clientTicks;
|
||||
|
||||
/**
|
||||
* The world time in ticks according to the server
|
||||
* <p>
|
||||
@@ -691,12 +698,6 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
|
||||
@Setter
|
||||
private long worldTicks;
|
||||
|
||||
/**
|
||||
* Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator
|
||||
*/
|
||||
@Setter
|
||||
private ScheduledFuture<?> lookBackScheduledFuture = null;
|
||||
|
||||
/**
|
||||
* Used to return players back to their vehicles if the server doesn't want them unmounting.
|
||||
*/
|
||||
@@ -1397,15 +1398,27 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to reduce amount of duplicate code. Sends ServerboundUseItemPacket.
|
||||
* Same as useItem but always default to useTouchRotation false.
|
||||
*/
|
||||
public void useItem(Hand hand) {
|
||||
useItem(hand, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to reduce amount of duplicate code. Sends ServerboundUseItemPacket.
|
||||
*/
|
||||
public void useItem(Hand hand, boolean useTouchRotation) {
|
||||
if (playerEntity.getFlag(EntityFlag.USING_ITEM)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendDownstreamGamePacket(new ServerboundUseItemPacket(
|
||||
hand, worldCache.nextPredictionSequence(), playerEntity.getYaw(), playerEntity.getPitch()));
|
||||
float yaw = playerEntity.getYaw(), pitch = playerEntity.getPitch();
|
||||
if (useTouchRotation) { // Only use touch rotation when we actually needed to, resolve https://github.com/GeyserMC/Geyser/issues/5704
|
||||
yaw = playerEntity.getBedrockInteractRotation().getY();
|
||||
pitch = playerEntity.getBedrockInteractRotation().getX();
|
||||
}
|
||||
|
||||
sendDownstreamGamePacket(new ServerboundUseItemPacket(hand, worldCache.nextPredictionSequence(), yaw, pitch));
|
||||
}
|
||||
|
||||
public void releaseItem() {
|
||||
|
||||
@@ -290,7 +290,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
if (packet.getItemInHand() != null && session.getItemMappings().getMapping(packet.getItemInHand()).getJavaItem() instanceof SpawnEggItem) {
|
||||
if (blockState.is(Blocks.WATER) && blockState.getValue(Properties.LEVEL) == 0) {
|
||||
// Otherwise causes multiple mobs to spawn - just send a use item packet
|
||||
useItem(session, packet, blockState.javaId());
|
||||
useItem(session, packet, blockState.javaId(), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -313,14 +313,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
ItemDefinition definition = packet.getItemInHand().getDefinition();
|
||||
// Otherwise boats will not be able to be placed in survival and buckets, lily pads, frogspawn, and glass bottles won't work on mobile
|
||||
if (item instanceof BoatItem || item == Items.LILY_PAD || item == Items.FROGSPAWN) {
|
||||
useItem(session, packet, blockState.javaId());
|
||||
useItem(session, packet, blockState.javaId(), true);
|
||||
} else if (item == Items.GLASS_BOTTLE) {
|
||||
Block block = blockState.block();
|
||||
if (!session.isSneaking() && block instanceof CauldronBlock && block != Blocks.WATER_CAULDRON) {
|
||||
// ServerboundUseItemPacket is not sent for water cauldrons and glass bottles
|
||||
return;
|
||||
}
|
||||
useItem(session, packet, blockState.javaId());
|
||||
useItem(session, packet, blockState.javaId(), true);
|
||||
} else if (session.getItemMappings().getBuckets().contains(definition)) {
|
||||
// Don't send ServerboundUseItemPacket for powder snow buckets
|
||||
if (definition != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockDefinition()) {
|
||||
@@ -328,7 +328,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
// ServerboundUseItemPacket is not sent for cauldrons and buckets
|
||||
return;
|
||||
}
|
||||
session.setPlacedBucket(useItem(session, packet, blockState.javaId()));
|
||||
session.setPlacedBucket(useItem(session, packet, blockState.javaId(), true));
|
||||
} else {
|
||||
session.setPlacedBucket(true);
|
||||
}
|
||||
@@ -598,7 +598,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean useItem(GeyserSession session, InventoryTransactionPacket packet, int blockState) {
|
||||
private boolean useItem(GeyserSession session, InventoryTransactionPacket packet, int blockState, boolean useTouchRotation) {
|
||||
// Update the player's inventory to remove any items added by the client itself
|
||||
PlayerInventory playerInventory = session.getPlayerInventory();
|
||||
int heldItemSlot = playerInventory.getOffsetForHotbar(packet.getHotbarSlot());
|
||||
@@ -633,67 +633,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f target = packet.getBlockPosition().toFloat().add(packet.getClickPosition());
|
||||
lookAt(session, target);
|
||||
|
||||
session.useItem(Hand.MAIN_HAND);
|
||||
session.useItem(Hand.MAIN_HAND, useTouchRotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the rotation necessary to activate this transaction.
|
||||
* <p>
|
||||
* The position between the intended click position and the player can be determined with two triangles.
|
||||
* First, we compute the difference of the X and Z coordinates:
|
||||
* <p>
|
||||
* Player position (0, 0)
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* |_____________ Intended target (-3, 2)
|
||||
* <p>
|
||||
* We then use the Pythagorean Theorem to find the direct line (hypotenuse) on the XZ plane. Finding the angle of the
|
||||
* triangle from there, closest to the player, gives us our yaw rotation value
|
||||
* Then doing the same using the new XZ distance and Y difference, we can find the direct line of sight from the
|
||||
* player to the intended target, and the pitch rotation value. We can then send the necessary packets to update
|
||||
* the player's rotation.
|
||||
*
|
||||
* @param session the Geyser Session
|
||||
* @param target the position to look at
|
||||
*/
|
||||
private void lookAt(GeyserSession session, Vector3f target) {
|
||||
// Use the bounding box's position since we need the player's position seen by the Java server
|
||||
Vector3d playerPosition = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
|
||||
float xDiff = (float) (target.getX() - playerPosition.getX());
|
||||
float yDiff = (float) (target.getY() - (playerPosition.getY() + session.getEyeHeight()));
|
||||
float zDiff = (float) (target.getZ() - playerPosition.getZ());
|
||||
|
||||
// First triangle on the XZ plane
|
||||
float yaw = (float) -Math.toDegrees(Math.atan2(xDiff, zDiff));
|
||||
// Second triangle on the Y axis using the hypotenuse of the first triangle as a side
|
||||
double xzHypot = Math.sqrt(xDiff * xDiff + zDiff * zDiff);
|
||||
float pitch = (float) -Math.toDegrees(Math.atan2(yDiff, xzHypot));
|
||||
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
ServerboundMovePlayerPosRotPacket returnPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
|
||||
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), entity.getYaw(), entity.getPitch());
|
||||
// This matches Java edition behavior
|
||||
ServerboundMovePlayerPosRotPacket movementPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
|
||||
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), yaw, pitch);
|
||||
session.sendDownstreamGamePacket(movementPacket);
|
||||
|
||||
if (session.getLookBackScheduledFuture() != null) {
|
||||
session.getLookBackScheduledFuture().cancel(false);
|
||||
}
|
||||
if (Math.abs(entity.getYaw() - yaw) > 1f || Math.abs(entity.getPitch() - pitch) > 1f) {
|
||||
session.setLookBackScheduledFuture(session.scheduleInEventLoop(() -> {
|
||||
Vector3d newPlayerPosition = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
|
||||
if (!newPlayerPosition.equals(playerPosition) || entity.getYaw() != returnPacket.getYaw() || entity.getPitch() != returnPacket.getPitch()) {
|
||||
// The player moved/rotated so there is no need to change their rotation back
|
||||
return;
|
||||
}
|
||||
session.sendDownstreamGamePacket(returnPacket);
|
||||
}, 150, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ final class BedrockMovePlayer {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
if (!session.isSpawned()) return;
|
||||
|
||||
// We need to save player interact rotation value, as this rotation is used for Touch device and indicate where the player is touching.
|
||||
// This is needed so that we can interact with where player actually touch on the screen on Bedrock and not just from the center of the screen.
|
||||
entity.setBedrockInteractRotation(packet.getInteractRotation());
|
||||
|
||||
// Ignore movement packets until Bedrock's position matches the teleported position
|
||||
if (session.getUnconfirmedTeleport() != null) {
|
||||
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
||||
@@ -79,13 +83,6 @@ final class BedrockMovePlayer {
|
||||
boolean positionChangedAndShouldUpdate = !hasVehicle && (session.getInputCache().shouldSendPositionReminder() || actualPositionChanged);
|
||||
boolean rotationChanged = hasVehicle || (entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw);
|
||||
|
||||
if (session.getLookBackScheduledFuture() != null) {
|
||||
// Resend the rotation if it was changed by Geyser
|
||||
rotationChanged |= !session.getLookBackScheduledFuture().isDone();
|
||||
session.getLookBackScheduledFuture().cancel(false);
|
||||
session.setLookBackScheduledFuture(null);
|
||||
}
|
||||
|
||||
// Simulate jumping since it happened this tick, not from the last tick end.
|
||||
if (entity.isOnGround() && packet.getInputData().contains(PlayerAuthInputData.START_JUMPING)) {
|
||||
entity.setLastTickEndVelocity(Vector3f.from(entity.getLastTickEndVelocity().getX(), Math.max(entity.getLastTickEndVelocity().getY(), entity.getJumpVelocity()), entity.getLastTickEndVelocity().getZ()));
|
||||
|
||||
@@ -75,6 +75,8 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
|
||||
public void translate(GeyserSession session, PlayerAuthInputPacket packet) {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
|
||||
session.setClientTicks(packet.getTick());
|
||||
|
||||
boolean wasJumping = session.getInputCache().wasJumping();
|
||||
session.getInputCache().processInputs(entity, packet);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public final class AssetUtils {
|
||||
// Get the url for the latest version of the games manifest
|
||||
String latestInfoURL = "";
|
||||
for (Version version : versionManifest.getVersions()) {
|
||||
if (version.getId().equals(GameProtocol.getJavaCodec().getMinecraftVersion())) {
|
||||
if (version.getId().equals(GameProtocol.getJavaMinecraftVersion())) {
|
||||
latestInfoURL = version.getUrl();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -223,7 +224,9 @@ public class WebUtils {
|
||||
con.getHeaderField("ETag"),
|
||||
String.valueOf(con.getLastModified()),
|
||||
downloadLocation.getFileName().toString()
|
||||
));
|
||||
),
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
packMetadata.toFile().setLastModified(System.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
Files.delete(packMetadata);
|
||||
|
||||
@@ -6,7 +6,7 @@ erosion = "1.1-20240521.000109-3"
|
||||
events = "1.1-SNAPSHOT"
|
||||
jackson = "2.17.0"
|
||||
fastutil = "8.5.15-SNAPSHOT"
|
||||
netty = "4.2.1.Final"
|
||||
netty = "4.2.3.Final"
|
||||
guava = "29.0-jre"
|
||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||
websocket = "1.5.1"
|
||||
@@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta7-20250705.103024-11"
|
||||
protocol-codec = "3.0.0.Beta7-20250705.103024-11"
|
||||
raknet = "1.0.0.CR3-20250218.160705-18"
|
||||
minecraftauth = "4.1.1"
|
||||
mcprotocollib = "1.21.7-20250712.190650-3"
|
||||
mcprotocollib = "1.21.7-20250725.134643-4"
|
||||
adventure = "4.21.0"
|
||||
adventure-platform = "4.3.0"
|
||||
junit = "5.9.2"
|
||||
@@ -36,13 +36,13 @@ viaproxy = "3.3.2-SNAPSHOT"
|
||||
fabric-loader = "0.16.14"
|
||||
fabric-api = "0.128.1+1.21.7"
|
||||
fabric-permissions-api = "0.4.0-SNAPSHOT"
|
||||
neoforge-minecraft = "21.7.0-beta"
|
||||
neoforge-minecraft = "21.8.0-beta"
|
||||
mixin = "0.8.5"
|
||||
mixinextras = "0.3.5"
|
||||
minecraft = "1.21.7"
|
||||
minecraft = "1.21.8"
|
||||
mockito = "5.+"
|
||||
runtask = "2.3.1"
|
||||
runpaperversion = "1.21.7"
|
||||
runpaperversion = "1.21.8"
|
||||
runvelocityversion = "3.4.0-SNAPSHOT"
|
||||
|
||||
# plugin versions
|
||||
@@ -88,8 +88,6 @@ adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text
|
||||
adventure-text-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
|
||||
adventure-text-serializer-bungeecord = { group = "net.kyori", name = "adventure-text-serializer-bungeecord", version.ref = "adventure-platform" }
|
||||
|
||||
netty-resolver-dns = { group = "io.netty", name = "netty-resolver-dns", version.ref = "netty" }
|
||||
netty-resolver-dns-native-macos = { group = "io.netty", name = "netty-resolver-dns-native-macos", version.ref = "netty" }
|
||||
netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", version.ref = "netty" }
|
||||
netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" }
|
||||
netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" }
|
||||
|
||||
Reference in New Issue
Block a user