Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 15:00:11 +01:00
Merge remote-tracking branch 'upstream/master' into new-mcpl
Dieser Commit ist enthalten in:
Commit
a15bca640d
@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id("geyser.base-conventions")
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
plugins {
|
||||
id("geyser.platform-conventions")
|
||||
id("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
|
||||
|
@ -111,6 +111,21 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
if (geyser == null) {
|
||||
return; // Config did not load properly!
|
||||
}
|
||||
|
||||
// After Geyser initialize for parity with other platforms.
|
||||
var sourceConverter = new CommandSourceConverter<>(
|
||||
CommandSender.class,
|
||||
id -> getProxy().getPlayer(id),
|
||||
() -> getProxy().getConsole(),
|
||||
BungeeCommandSource::new
|
||||
);
|
||||
CommandManager<GeyserCommandSource> cloud = new BungeeCommandManager<>(
|
||||
this,
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
sourceConverter
|
||||
);
|
||||
this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
|
||||
|
||||
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
|
||||
// task that waits for a field to be filled which is set after the plugin enable
|
||||
// process is complete
|
||||
@ -150,19 +165,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
} else {
|
||||
var sourceConverter = new CommandSourceConverter<>(
|
||||
CommandSender.class,
|
||||
id -> getProxy().getPlayer(id),
|
||||
() -> getProxy().getConsole(),
|
||||
BungeeCommandSource::new
|
||||
);
|
||||
CommandManager<GeyserCommandSource> cloud = new BungeeCommandManager<>(
|
||||
this,
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
sourceConverter
|
||||
);
|
||||
this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
|
||||
}
|
||||
|
||||
// Force-disable query if enabled, or else Geyser won't enable
|
||||
|
@ -1,3 +1,7 @@
|
||||
plugins {
|
||||
id("geyser.modded-conventions")
|
||||
}
|
||||
|
||||
architectury {
|
||||
common("neoforge", "fabric")
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
plugins {
|
||||
id("geyser.modded-conventions")
|
||||
id("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
|
||||
architectury {
|
||||
platformSetupLoomIde()
|
||||
fabric()
|
||||
|
@ -1,13 +1,18 @@
|
||||
// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
|
||||
// NeoForge's class loader is *really* annoying.
|
||||
provided("org.cloudburstmc.math", "api")
|
||||
provided("com.google.errorprone", "error_prone_annotations")
|
||||
plugins {
|
||||
id("geyser.modded-conventions")
|
||||
id("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
|
||||
architectury {
|
||||
platformSetupLoomIde()
|
||||
neoForge()
|
||||
}
|
||||
|
||||
// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
|
||||
// NeoForge's class loader is *really* annoying.
|
||||
provided("org.cloudburstmc.math", "api")
|
||||
provided("com.google.errorprone", "error_prone_annotations")
|
||||
|
||||
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
|
||||
|
||||
dependencies {
|
||||
|
@ -82,6 +82,7 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
|
||||
);
|
||||
GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud);
|
||||
this.setCommandRegistry(registry);
|
||||
// An auxiliary listener for registering undefined permissions belonging to commands. See javadocs for more info.
|
||||
NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
plugins {
|
||||
id("geyser.platform-conventions")
|
||||
id("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
api(libs.erosion.bukkit.common) {
|
||||
|
@ -181,7 +181,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create command manager early so we can add Geyser extension commands
|
||||
// Register commands after Geyser initialization, but before the server starts.
|
||||
var sourceConverter = new CommandSourceConverter<>(
|
||||
CommandSender.class,
|
||||
Bukkit::getPlayer,
|
||||
|
@ -2,6 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCach
|
||||
|
||||
plugins {
|
||||
application
|
||||
id("geyser.platform-conventions")
|
||||
}
|
||||
|
||||
val terminalConsoleVersion = "1.2.0"
|
||||
|
@ -1,3 +1,8 @@
|
||||
plugins {
|
||||
id("geyser.platform-conventions")
|
||||
id("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor(libs.velocity.api)
|
||||
api(projects.core)
|
||||
|
@ -109,6 +109,22 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
|
||||
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
|
||||
|
||||
// We need to register commands here, rather than in onGeyserEnable which is invoked during the appropriate ListenerBoundEvent.
|
||||
// Reason: players can connect after a listener is bound, and a player join locks registration to the cloud CommandManager.
|
||||
var sourceConverter = new CommandSourceConverter<>(
|
||||
CommandSource.class,
|
||||
id -> proxyServer.getPlayer(id).orElse(null),
|
||||
proxyServer::getConsoleCommandSource,
|
||||
VelocityCommandSource::new
|
||||
);
|
||||
CommandManager<GeyserCommandSource> cloud = new VelocityCommandManager<>(
|
||||
container,
|
||||
proxyServer,
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
sourceConverter
|
||||
);
|
||||
this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -123,20 +139,6 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
} else {
|
||||
var sourceConverter = new CommandSourceConverter<>(
|
||||
CommandSource.class,
|
||||
id -> proxyServer.getPlayer(id).orElse(null),
|
||||
proxyServer::getConsoleCommandSource,
|
||||
VelocityCommandSource::new
|
||||
);
|
||||
CommandManager<GeyserCommandSource> cloud = new VelocityCommandManager<>(
|
||||
container,
|
||||
proxyServer,
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
sourceConverter
|
||||
);
|
||||
this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
|
||||
}
|
||||
|
||||
GeyserImpl.start();
|
||||
|
@ -1,3 +1,7 @@
|
||||
plugins {
|
||||
id("geyser.platform-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
|
||||
|
@ -12,9 +12,14 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// This is for the LibsAccessor.kt hack
|
||||
// this is OK as long as the same version catalog is used in the main build and build-logic
|
||||
// see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
|
||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||
|
||||
// This is for applying plugins, and using the version from the libs.versions.toml
|
||||
// Unfortunately they still need to be applied by their string name in the convention scripts.
|
||||
implementation(libs.lombok)
|
||||
implementation(libs.indra)
|
||||
implementation(libs.shadow)
|
||||
implementation(libs.architectury.plugin)
|
||||
|
@ -8,4 +8,4 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-logic"
|
||||
rootProject.name = "build-logic"
|
||||
|
@ -118,3 +118,12 @@ open class DownloadFilesTask : DefaultTask() {
|
||||
private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String =
|
||||
if (excludedOn and bit > 0) section else ""
|
||||
|
||||
fun projectVersion(project: Project): String =
|
||||
project.version.toString().replace("SNAPSHOT", "b" + buildNumber())
|
||||
|
||||
fun versionName(project: Project): String =
|
||||
"Geyser-" + project.name.replaceFirstChar { it.uppercase() } + "-" + projectVersion(project)
|
||||
|
||||
fun buildNumber(): Int =
|
||||
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
|
||||
|
||||
|
@ -3,9 +3,10 @@ plugins {
|
||||
id("net.kyori.indra")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.checkerframework", "checker-qual", "3.19.0")
|
||||
}
|
||||
val rootProperties: Map<String, *> = project.rootProject.properties
|
||||
group = rootProperties["group"] as String + "." + rootProperties["id"] as String
|
||||
version = rootProperties["version"] as String
|
||||
description = rootProperties["description"] as String
|
||||
|
||||
indra {
|
||||
github("GeyserMC", "Geyser") {
|
||||
@ -20,18 +21,52 @@ indra {
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
// Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge
|
||||
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) {
|
||||
expand(
|
||||
"id" to "geyser",
|
||||
"name" to "Geyser",
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"url" to "https://geysermc.org",
|
||||
"author" to "GeyserMC"
|
||||
)
|
||||
}
|
||||
dependencies {
|
||||
compileOnly("org.checkerframework", "checker-qual", libs.checker.qual.get().version)
|
||||
}
|
||||
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
|
||||
mavenCentral()
|
||||
|
||||
// Floodgate, Cumulus etc.
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
|
||||
// Paper, Velocity
|
||||
maven("https://repo.papermc.io/repository/maven-public")
|
||||
|
||||
// Spigot
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
}
|
||||
|
||||
// BungeeCord
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// NeoForge
|
||||
maven("https://maven.neoforged.net/releases") {
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
maven("https://libraries.minecraft.net") {
|
||||
name = "minecraft"
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// ViaVersion
|
||||
maven("https://repo.viaversion.com") {
|
||||
name = "viaversion"
|
||||
}
|
||||
|
||||
// Jitpack for e.g. MCPL
|
||||
maven("https://jitpack.io") {
|
||||
content { includeGroupByRegex("com\\.github\\..*") }
|
||||
}
|
||||
|
||||
// For Adventure snapshots
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
|
||||
mavenCentral()
|
||||
|
||||
// Floodgate, Cumulus etc.
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
|
||||
// Paper, Velocity
|
||||
maven("https://repo.papermc.io/repository/maven-public")
|
||||
|
||||
// Spigot
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// BungeeCord
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// NeoForge
|
||||
maven("https://maven.neoforged.net/releases") {
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
maven("https://libraries.minecraft.net") {
|
||||
name = "minecraft"
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// ViaVersion
|
||||
maven("https://repo.viaversion.com") {
|
||||
name = "viaversion"
|
||||
}
|
||||
|
||||
// Jitpack for e.g. MCPL
|
||||
maven("https://jitpack.io") {
|
||||
content { includeGroupByRegex("com\\.github\\..*") }
|
||||
}
|
||||
|
||||
// For Adventure snapshots
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
@ -2,11 +2,9 @@
|
||||
|
||||
import net.fabricmc.loom.task.RemapJarTask
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.maven
|
||||
|
||||
plugins {
|
||||
id("geyser.build-logic")
|
||||
id("geyser.publish-conventions")
|
||||
id("geyser.platform-conventions")
|
||||
id("architectury-plugin")
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
@ -87,7 +85,7 @@ tasks {
|
||||
register("remapModrinthJar", RemapJarTask::class) {
|
||||
dependsOn(shadowJar)
|
||||
inputFile.set(shadowJar.get().archiveFile)
|
||||
archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER"))
|
||||
archiveVersion.set(versionName(project))
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody)
|
||||
modrinth {
|
||||
token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
|
||||
projectId.set("geyser")
|
||||
versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
|
||||
versionName.set(versionName(project))
|
||||
versionNumber.set(projectVersion(project))
|
||||
versionType.set("beta")
|
||||
changelog.set(System.getenv("CHANGELOG") ?: "")
|
||||
gameVersions.addAll("1.21", libs.minecraft.get().version as String)
|
||||
|
@ -1,3 +1,20 @@
|
||||
plugins {
|
||||
id("geyser.publish-conventions")
|
||||
}
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
// Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge
|
||||
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) {
|
||||
expand(
|
||||
"id" to "geyser",
|
||||
"name" to "Geyser",
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"url" to "https://geysermc.org",
|
||||
"author" to "GeyserMC"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,5 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
// Ensure AP works in eclipse (no effect on other IDEs)
|
||||
eclipse
|
||||
id("geyser.build-logic")
|
||||
alias(libs.plugins.lombok) apply false
|
||||
id("geyser.base-conventions")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = properties["group"] as String + "." + properties["id"] as String
|
||||
version = properties["version"] as String
|
||||
description = properties["description"] as String
|
||||
}
|
||||
|
||||
val basePlatforms = setOf(
|
||||
projects.bungeecord,
|
||||
projects.spigot,
|
||||
projects.standalone,
|
||||
projects.velocity,
|
||||
projects.viaproxy
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val moddedPlatforms = setOf(
|
||||
projects.fabric,
|
||||
projects.neoforge,
|
||||
projects.mod
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val modrinthPlatforms = setOf(
|
||||
projects.bungeecord,
|
||||
projects.fabric,
|
||||
projects.neoforge,
|
||||
projects.spigot,
|
||||
projects.velocity
|
||||
).map { it.dependencyProject }
|
||||
|
||||
subprojects {
|
||||
apply {
|
||||
plugin("java-library")
|
||||
plugin("io.freefair.lombok")
|
||||
plugin("geyser.build-logic")
|
||||
}
|
||||
|
||||
when (this) {
|
||||
in basePlatforms -> plugins.apply("geyser.platform-conventions")
|
||||
in moddedPlatforms -> plugins.apply("geyser.modded-conventions")
|
||||
else -> plugins.apply("geyser.base-conventions")
|
||||
}
|
||||
|
||||
// Not combined with platform-conventions as that also contains
|
||||
// platforms which we cant publish to modrinth
|
||||
if (modrinthPlatforms.contains(this)) {
|
||||
plugins.apply("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id("geyser.publish-conventions")
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -3,6 +3,7 @@ plugins {
|
||||
idea
|
||||
alias(libs.plugins.blossom)
|
||||
id("geyser.publish-conventions")
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -103,9 +104,6 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildNumber(): Int =
|
||||
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
|
||||
|
||||
fun isDevBuild(branch: String, repository: String): Boolean {
|
||||
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
|
||||
}
|
||||
@ -139,7 +137,7 @@ inner class GitInfo {
|
||||
|
||||
buildNumber = buildNumber()
|
||||
isDev = isDevBuild(branch, repository)
|
||||
val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}")
|
||||
val projectVersion = if (isDev) project.version else projectVersion(project)
|
||||
version = "$projectVersion ($gitVersion)"
|
||||
}
|
||||
}
|
||||
|
@ -363,22 +363,6 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
String broadcastPort = System.getProperty("geyserBroadcastPort", "");
|
||||
if (!broadcastPort.isEmpty()) {
|
||||
int parsedPort;
|
||||
try {
|
||||
parsedPort = Integer.parseInt(broadcastPort);
|
||||
if (parsedPort < 1 || parsedPort > 65535) {
|
||||
throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error(String.format("Invalid broadcast port: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")"));
|
||||
parsedPort = config.getBedrock().port();
|
||||
}
|
||||
config.getBedrock().setBroadcastPort(parsedPort);
|
||||
logger.info("Broadcast port set from system property: " + parsedPort);
|
||||
}
|
||||
|
||||
if (platformType != PlatformType.VIAPROXY) {
|
||||
boolean floodgatePresent = bootstrap.testFloodgatePluginPresent();
|
||||
if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) {
|
||||
@ -393,6 +377,26 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the Bedrock port may have been changed, also check the broadcast port (configurable on all platforms)
|
||||
String broadcastPort = System.getProperty("geyserBroadcastPort", "");
|
||||
if (!broadcastPort.isEmpty()) {
|
||||
try {
|
||||
int parsedPort = Integer.parseInt(broadcastPort);
|
||||
if (parsedPort < 1 || parsedPort > 65535) {
|
||||
throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!");
|
||||
}
|
||||
config.getBedrock().setBroadcastPort(parsedPort);
|
||||
logger.info("Broadcast port set from system property: " + parsedPort);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error(String.format("Invalid broadcast port from system property: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
// It's set to 0 only if no system property or manual config value was set
|
||||
if (config.getBedrock().broadcastPort() == 0) {
|
||||
config.getBedrock().setBroadcastPort(config.getBedrock().port());
|
||||
}
|
||||
|
||||
String remoteAddress = config.getRemote().address();
|
||||
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
|
||||
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
|
||||
|
@ -42,7 +42,11 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
@ -278,7 +282,13 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector3f position) {
|
||||
super.setPosition(position.add(0, definition.offset(), 0));
|
||||
if (this.bedPosition != null) {
|
||||
// As of Bedrock 1.21.22 and Fabric 1.21.1
|
||||
// Messes with Bedrock if we send this to the client itself, though.
|
||||
super.setPosition(position.up(0.2f));
|
||||
} else {
|
||||
super.setPosition(position.add(0, definition.offset(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -140,7 +140,7 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
if (valid) { // Don't update during session init
|
||||
session.getCollisionManager().updatePlayerBoundingBox(position);
|
||||
}
|
||||
super.setPosition(position);
|
||||
this.position = position.add(0, definition.offset(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
public class ErosionCancellationException extends CancellationException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@ -42,7 +42,6 @@ public final class GeyserboundHandshakePacketHandler extends AbstractGeyserbound
|
||||
public void handleHandshake(GeyserboundHandshakePacket packet) {
|
||||
boolean useTcp = packet.getTransportType().getSocketAddress() == null;
|
||||
GeyserboundPacketHandlerImpl handler = new GeyserboundPacketHandlerImpl(session, useTcp ? new GeyserErosionPacketSender(session) : new NettyPacketSender<>());
|
||||
session.setErosionHandler(handler);
|
||||
if (!useTcp) {
|
||||
if (session.getGeyser().getErosionUnixListener() == null) {
|
||||
session.disconnect("Erosion configurations using Unix socket handling are not supported on this hardware!");
|
||||
@ -52,6 +51,7 @@ public final class GeyserboundHandshakePacketHandler extends AbstractGeyserbound
|
||||
} else {
|
||||
handler.onConnect();
|
||||
}
|
||||
session.setErosionHandler(handler);
|
||||
session.ensureInEventLoop(() -> session.getChunkCache().clear());
|
||||
}
|
||||
|
||||
|
@ -171,10 +171,10 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
|
||||
|
||||
@Override
|
||||
public void handleHandshake(GeyserboundHandshakePacket packet) {
|
||||
this.close();
|
||||
var handler = new GeyserboundHandshakePacketHandler(this.session);
|
||||
session.setErosionHandler(handler);
|
||||
handler.handleHandshake(packet);
|
||||
this.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,6 +198,17 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
|
||||
|
||||
public void close() {
|
||||
this.packetSender.close();
|
||||
|
||||
if (pendingLookup != null) {
|
||||
pendingLookup.completeExceptionally(new ErosionCancellationException());
|
||||
}
|
||||
if (pendingBatchLookup != null) {
|
||||
pendingBatchLookup.completeExceptionally(new ErosionCancellationException());
|
||||
}
|
||||
if (pickBlockLookup != null) {
|
||||
pickBlockLookup.completeExceptionally(new ErosionCancellationException());
|
||||
}
|
||||
asyncPendingLookups.forEach(($, future) -> future.completeExceptionally(new ErosionCancellationException()));
|
||||
}
|
||||
|
||||
public int getNextTransactionId() {
|
||||
|
@ -162,6 +162,27 @@ public final class ClickPlan {
|
||||
finished = true;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the item stacks with another item in the specified slot.
|
||||
* This will check the simulated inventory without copying.
|
||||
*/
|
||||
public boolean canStack(int slot, GeyserItemStack item) {
|
||||
GeyserItemStack slotItem = simulatedItems.getOrDefault(slot, inventory.getItem(slot));
|
||||
return InventoryUtils.canStack(slotItem, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the specified slot is empty.
|
||||
* This will check the simulated inventory without copying.
|
||||
*/
|
||||
public boolean isEmpty(int slot) {
|
||||
return simulatedItems.getOrDefault(slot, inventory.getItem(slot)).isEmpty();
|
||||
}
|
||||
|
||||
public GeyserItemStack getItem(int slot) {
|
||||
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public record Enchantment(String identifier,
|
||||
|
||||
// TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java,
|
||||
// but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name.
|
||||
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null;
|
||||
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(context.session(), data) : null;
|
||||
|
||||
return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel,
|
||||
description, anvilCost, exclusiveSet, bedrockEnchantment);
|
||||
|
@ -35,6 +35,7 @@ import org.geysermc.erosion.packet.backendbound.BackendboundBatchBlockRequestPac
|
||||
import org.geysermc.erosion.packet.backendbound.BackendboundBlockRequestPacket;
|
||||
import org.geysermc.erosion.packet.backendbound.BackendboundPickBlockPacket;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.erosion.ErosionCancellationException;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
@ -49,6 +50,8 @@ public class GeyserWorldManager extends WorldManager {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return session.getChunkCache().getBlockAt(x, y, z);
|
||||
} else if (session.isClosed()) {
|
||||
throw new ErosionCancellationException();
|
||||
}
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>(); // Boxes
|
||||
erosionHandler.setPendingLookup(future);
|
||||
@ -61,6 +64,8 @@ public class GeyserWorldManager extends WorldManager {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getBlockAtAsync(session, x, y, z);
|
||||
} else if (session.isClosed()) {
|
||||
return CompletableFuture.failedFuture(new ErosionCancellationException());
|
||||
}
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>(); // Boxes
|
||||
int transactionId = erosionHandler.getNextTransactionId();
|
||||
@ -74,6 +79,8 @@ public class GeyserWorldManager extends WorldManager {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getBlocksAt(session, iter);
|
||||
} else if (session.isClosed()) {
|
||||
throw new ErosionCancellationException();
|
||||
}
|
||||
CompletableFuture<int[]> future = new CompletableFuture<>();
|
||||
erosionHandler.setPendingBatchLookup(future);
|
||||
@ -124,6 +131,8 @@ public class GeyserWorldManager extends WorldManager {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getPickItemComponents(session, x, y, z, addNbtData);
|
||||
} else if (session.isClosed()) {
|
||||
return CompletableFuture.failedFuture(new ErosionCancellationException());
|
||||
}
|
||||
CompletableFuture<Int2ObjectMap<byte[]>> future = new CompletableFuture<>();
|
||||
erosionHandler.setPickBlockLookup(future);
|
||||
|
@ -44,7 +44,7 @@ public record JukeboxSong(String soundEvent, String description) {
|
||||
soundEvent = "";
|
||||
GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject);
|
||||
}
|
||||
String description = MessageTranslator.deserializeDescription(data);
|
||||
String description = MessageTranslator.deserializeDescription(context.session(), data);
|
||||
return new JukeboxSong(soundEvent, description);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.compat.BedrockCompat;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
||||
@ -120,10 +119,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
session.disconnect(disconnectMessage);
|
||||
return false;
|
||||
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
if (protocolVersion < Bedrock_v622.CODEC.getProtocolVersion()) {
|
||||
// https://github.com/GeyserMC/Geyser/issues/4378
|
||||
session.getUpstream().getSession().setCodec(BedrockCompat.CODEC_LEGACY);
|
||||
}
|
||||
// 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:
|
||||
session.getUpstream().getSession().setCodec(BedrockCompat.disconnectCompat(protocolVersion));
|
||||
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||
return false;
|
||||
} else {
|
||||
|
@ -144,11 +144,6 @@ public final class GeyserServer {
|
||||
this.proxiedAddresses = null;
|
||||
}
|
||||
|
||||
// It's set to 0 only if no system property or manual config value was set
|
||||
if (geyser.getConfig().getBedrock().broadcastPort() == 0) {
|
||||
geyser.getConfig().getBedrock().setBroadcastPort(geyser.getConfig().getBedrock().port());
|
||||
}
|
||||
|
||||
this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort();
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.Clien
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
|
||||
import io.netty.channel.EventLoop;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.erosion.ErosionCancellationException;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
@ -87,6 +88,8 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
|
||||
|
||||
try {
|
||||
translator.translate(session, packet);
|
||||
} catch (ErosionCancellationException ex) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Caught ErosionCancellationException");
|
||||
} catch (Throwable ex) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);
|
||||
ex.printStackTrace();
|
||||
|
@ -171,6 +171,11 @@ public class ItemRegistryPopulator {
|
||||
List<ItemData> creativeItems = new ArrayList<>();
|
||||
Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
|
||||
|
||||
// Fix: Usage of structure blocks/voids in recipes
|
||||
// https://github.com/GeyserMC/Geyser/issues/2890
|
||||
noBlockDefinitions.add("minecraft:structure_block");
|
||||
noBlockDefinitions.add("minecraft:structure_void");
|
||||
|
||||
AtomicInteger creativeNetId = new AtomicInteger();
|
||||
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
|
||||
ItemData item = itemBuilder.netId(creativeNetId.incrementAndGet()).build();
|
||||
|
@ -40,6 +40,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.raphimc.minecraftauth.responsehandler.exception.MinecraftRequestException;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCProfile;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCToken;
|
||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||
@ -66,6 +67,7 @@ import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GameType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType;
|
||||
@ -85,6 +87,7 @@ import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
|
||||
@ -130,6 +133,7 @@ import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
|
||||
import org.geysermc.geyser.erosion.ErosionCancellationException;
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraData;
|
||||
@ -228,6 +232,7 @@ import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -258,7 +263,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
@NonNull
|
||||
@Setter
|
||||
private AbstractGeyserboundPacketHandler erosionHandler;
|
||||
private volatile AbstractGeyserboundPacketHandler erosionHandler;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Setter
|
||||
@ -567,16 +572,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
private float walkSpeed;
|
||||
|
||||
/**
|
||||
* Caches current rain status.
|
||||
* Caches current rain strength.
|
||||
* Value between 0 and 1.
|
||||
*/
|
||||
@Setter
|
||||
private boolean raining = false;
|
||||
private float rainStrength = 0.0f;
|
||||
|
||||
/**
|
||||
* Caches current thunder status.
|
||||
* Caches current thunder strength.
|
||||
* Value between 0 and 1.
|
||||
*/
|
||||
@Setter
|
||||
private boolean thunder = false;
|
||||
private float thunderStrength = 0.0f;
|
||||
|
||||
/**
|
||||
* Stores a map of all statistics sent from the server.
|
||||
@ -876,7 +881,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
return task.getAuthentication().handle((result, ex) -> {
|
||||
if (ex != null) {
|
||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||
disconnect(ex.toString());
|
||||
if (ex instanceof CompletionException ce
|
||||
&& ce.getCause() instanceof MinecraftRequestException mre
|
||||
&& mre.getResponse().getStatusCode() == 404) {
|
||||
// Player is trying to join with a Microsoft account that doesn't have Java Edition purchased
|
||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", locale()));
|
||||
} else {
|
||||
disconnect(ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1172,9 +1184,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
tickThread.cancel(false);
|
||||
}
|
||||
|
||||
erosionHandler.close();
|
||||
|
||||
// Mark session as closed before cancelling erosion futures
|
||||
closed = true;
|
||||
erosionHandler.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1195,6 +1207,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
eventLoop.execute(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (ErosionCancellationException e) {
|
||||
geyser.getLogger().debug("Caught ErosionCancellationException");
|
||||
} catch (Throwable e) {
|
||||
geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e);
|
||||
}
|
||||
@ -1212,6 +1226,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
if (!closed) {
|
||||
runnable.run();
|
||||
}
|
||||
} catch (ErosionCancellationException e) {
|
||||
geyser.getLogger().debug("Caught ErosionCancellationException");
|
||||
} catch (Throwable e) {
|
||||
geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e);
|
||||
}
|
||||
@ -1984,6 +2000,71 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet to update rain strength.
|
||||
* Stops rain if strength is 0.
|
||||
*
|
||||
* @param strength value between 0 and 1
|
||||
*/
|
||||
public void updateRain(float strength) {
|
||||
boolean wasRaining = isRaining();
|
||||
this.rainStrength = strength;
|
||||
|
||||
LevelEventPacket rainPacket = new LevelEventPacket();
|
||||
rainPacket.setType(isRaining() ? LevelEvent.START_RAINING : LevelEvent.STOP_RAINING);
|
||||
rainPacket.setData((int) (strength * 65535));
|
||||
rainPacket.setPosition(Vector3f.ZERO);
|
||||
sendUpstreamPacket(rainPacket);
|
||||
|
||||
// Keep thunder in sync with rain when starting/stopping a storm
|
||||
if ((wasRaining != isRaining()) && isThunder()) {
|
||||
if (isRaining()) {
|
||||
LevelEventPacket thunderPacket = new LevelEventPacket();
|
||||
thunderPacket.setType(LevelEvent.START_THUNDERSTORM);
|
||||
thunderPacket.setData((int) (this.thunderStrength * 65535));
|
||||
thunderPacket.setPosition(Vector3f.ZERO);
|
||||
sendUpstreamPacket(thunderPacket);
|
||||
} else {
|
||||
LevelEventPacket thunderPacket = new LevelEventPacket();
|
||||
thunderPacket.setType(LevelEvent.STOP_THUNDERSTORM);
|
||||
thunderPacket.setData(0);
|
||||
thunderPacket.setPosition(Vector3f.ZERO);
|
||||
sendUpstreamPacket(thunderPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet to update thunderstorm strength.
|
||||
* Stops thunderstorm if strength is 0.
|
||||
*
|
||||
* @param strength value between 0 and 1
|
||||
*/
|
||||
public void updateThunder(float strength) {
|
||||
this.thunderStrength = strength;
|
||||
|
||||
// Do not send thunder packet if not raining
|
||||
// The bedrock client will start raining automatically when updating thunder strength
|
||||
// https://github.com/GeyserMC/Geyser/issues/3679
|
||||
if (!isRaining()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LevelEventPacket thunderPacket = new LevelEventPacket();
|
||||
thunderPacket.setType(isThunder() ? LevelEvent.START_THUNDERSTORM : LevelEvent.STOP_THUNDERSTORM);
|
||||
thunderPacket.setData((int) (strength * 65535));
|
||||
thunderPacket.setPosition(Vector3f.ZERO);
|
||||
sendUpstreamPacket(thunderPacket);
|
||||
}
|
||||
|
||||
public boolean isRaining() {
|
||||
return this.rainStrength > 0;
|
||||
}
|
||||
|
||||
public boolean isThunder() {
|
||||
return this.thunderStrength > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String bedrockUsername() {
|
||||
return authData.name();
|
||||
|
@ -49,7 +49,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||
import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry;
|
||||
import org.geysermc.geyser.text.TextDecoration;
|
||||
import org.geysermc.geyser.text.ChatDecoration;
|
||||
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||
@ -78,7 +78,7 @@ public final class RegistryCache {
|
||||
private static final Map<Key, BiConsumer<RegistryCache, List<RegistryEntry>>> REGISTRIES = new HashMap<>();
|
||||
|
||||
static {
|
||||
register("chat_type", cache -> cache.chatTypes, TextDecoration::readChatType);
|
||||
register("chat_type", cache -> cache.chatTypes, ChatDecoration::readChatType);
|
||||
register("dimension_type", cache -> cache.dimensions, JavaDimension::read);
|
||||
register("enchantment", cache -> cache.enchantments, Enchantment::read);
|
||||
register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read);
|
||||
|
@ -25,17 +25,19 @@
|
||||
|
||||
package org.geysermc.geyser.text;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public record TextDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
|
||||
public record ChatDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
|
||||
|
||||
@Override
|
||||
public NbtMap style() {
|
||||
@ -53,38 +55,22 @@ public record TextDecoration(String translationKey, List<Parameter> parameters,
|
||||
String translationKey = chat.getString("translation_key");
|
||||
|
||||
NbtMap styleTag = chat.getCompound("style");
|
||||
Style style = deserializeStyle(styleTag);
|
||||
Style style = MessageTranslator.getStyleFromNbtMap(styleTag);
|
||||
|
||||
List<ChatTypeDecoration.Parameter> parameters = new ArrayList<>();
|
||||
List<String> parametersNbt = chat.getList("parameters", NbtType.STRING);
|
||||
for (String parameter : parametersNbt) {
|
||||
parameters.add(ChatTypeDecoration.Parameter.valueOf(parameter.toUpperCase(Locale.ROOT)));
|
||||
}
|
||||
return new ChatType(new TextDecoration(translationKey, parameters, style), null);
|
||||
return new ChatType(new ChatDecoration(translationKey, parameters, style), null);
|
||||
}
|
||||
return new ChatType(null, null);
|
||||
}
|
||||
|
||||
public static Style getStyle(ChatTypeDecoration decoration) {
|
||||
if (decoration instanceof TextDecoration textDecoration) {
|
||||
return textDecoration.deserializedStyle();
|
||||
if (decoration instanceof ChatDecoration chatDecoration) {
|
||||
return chatDecoration.deserializedStyle();
|
||||
}
|
||||
return deserializeStyle(decoration.style());
|
||||
}
|
||||
|
||||
private static Style deserializeStyle(NbtMap styleTag) {
|
||||
Style.Builder builder = Style.style();
|
||||
if (!styleTag.isEmpty()) {
|
||||
String color = styleTag.getString("color", null);
|
||||
if (color != null) {
|
||||
builder.color(NamedTextColor.NAMES.value(color));
|
||||
}
|
||||
//TODO implement the rest
|
||||
boolean italic = styleTag.getBoolean("italic");
|
||||
if (italic) {
|
||||
builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
return MessageTranslator.getStyleFromNbtMap(decoration.style());
|
||||
}
|
||||
}
|
@ -201,6 +201,9 @@ public abstract class InventoryTranslator {
|
||||
public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
ClickPlan plan = new ClickPlan(session, this, inventory);
|
||||
IntSet affectedSlots = new IntOpenHashSet();
|
||||
int pendingOutput = 0;
|
||||
int savedTempSlot = -1;
|
||||
|
||||
for (ItemStackRequestAction action : request.getActions()) {
|
||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||
switch (action.getType()) {
|
||||
@ -241,6 +244,65 @@ public abstract class InventoryTranslator {
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
|
||||
// Handle partial transfer of output slot
|
||||
if (pendingOutput == 0 && !isSourceCursor && getSlotType(sourceSlot) == SlotType.OUTPUT
|
||||
&& transferAction.getCount() < plan.getItem(sourceSlot).getAmount()) {
|
||||
// Cursor as dest should always be full transfer.
|
||||
if (isDestCursor) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
if (!plan.getCursor().isEmpty()) {
|
||||
savedTempSlot = findTempSlot(plan, plan.getCursor(), true);
|
||||
if (savedTempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
plan.add(Click.LEFT, savedTempSlot);
|
||||
}
|
||||
|
||||
// Pickup entire stack from output
|
||||
pendingOutput = plan.getItem(sourceSlot).getAmount();
|
||||
plan.add(Click.LEFT, sourceSlot);
|
||||
}
|
||||
|
||||
// Continue transferring items from output that is currently stored in the cursor
|
||||
if (pendingOutput > 0) {
|
||||
if (isSourceCursor || getSlotType(sourceSlot) != SlotType.OUTPUT
|
||||
|| transferAction.getCount() > pendingOutput
|
||||
|| destSlot == savedTempSlot
|
||||
|| isDestCursor) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
// Make sure item can be placed here
|
||||
GeyserItemStack destItem = plan.getItem(destSlot);
|
||||
if (!destItem.isEmpty() && !InventoryUtils.canStack(destItem, plan.getCursor())) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
// TODO: Optimize using max stack size
|
||||
if (pendingOutput == transferAction.getCount()) {
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
} else {
|
||||
for (int i = 0; i < transferAction.getCount(); i++) {
|
||||
plan.add(Click.RIGHT, destSlot);
|
||||
}
|
||||
}
|
||||
|
||||
pendingOutput -= transferAction.getCount();
|
||||
if (pendingOutput != plan.getCursor().getAmount()) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
if (pendingOutput == 0 && savedTempSlot != -1) {
|
||||
plan.add(Click.LEFT, savedTempSlot);
|
||||
savedTempSlot = -1;
|
||||
}
|
||||
|
||||
// Skip to next action
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSourceCursor && isDestCursor) { //???
|
||||
return rejectRequest(request);
|
||||
} else if (isSourceCursor) { //releasing cursor
|
||||
@ -271,7 +333,7 @@ public abstract class InventoryTranslator {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
if (transferAction.getCount() != sourceAmount) {
|
||||
int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot);
|
||||
int tempSlot = findTempSlot(plan, cursor, false, sourceSlot);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
@ -292,7 +354,7 @@ public abstract class InventoryTranslator {
|
||||
} else { //transfer from one slot to another
|
||||
int tempSlot = -1;
|
||||
if (!plan.getCursor().isEmpty()) {
|
||||
tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot);
|
||||
tempSlot = findTempSlot(plan, cursor, getSlotType(sourceSlot) != SlotType.NORMAL, sourceSlot, destSlot);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
@ -440,6 +502,11 @@ public abstract class InventoryTranslator {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingOutput != 0) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
plan.execute(false);
|
||||
affectedSlots.addAll(plan.getAffectedSlots());
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
||||
@ -536,7 +603,7 @@ public abstract class InventoryTranslator {
|
||||
}
|
||||
} else {
|
||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||
int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot);
|
||||
int tempSlot = findTempSlot(plan, cursor, true, sourceSlot, destSlot);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
@ -699,7 +766,7 @@ public abstract class InventoryTranslator {
|
||||
int javaSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||
if (isCursor(transferAction.getDestination())) { //TODO
|
||||
if (timesCrafted > 1) {
|
||||
tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true);
|
||||
tempSlot = findTempSlot(plan, GeyserItemStack.from(output), true);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
@ -836,49 +903,68 @@ public abstract class InventoryTranslator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a slot that can temporarily store the given item.
|
||||
* Try to find a slot that is preferably empty, or does not stack with a given item.
|
||||
* Only looks in the main inventory and hotbar (excluding offhand).
|
||||
* Only slots that are empty or contain a different type of item are valid.
|
||||
* <p>
|
||||
* Slots are searched in the reverse order that the bedrock client uses for quick moving.
|
||||
*
|
||||
* @return java id for the temporary slot, or -1 if no viable slot was found
|
||||
* @param plan used to check the simulated inventory
|
||||
* @param item the item to temporarily store
|
||||
* @param emptyOnly if only empty slots should be considered
|
||||
* @param slotBlacklist list of slots to exclude; the items contained in these slots will also be checked for stacking
|
||||
* @return the temp slot, or -1 if no suitable slot was found
|
||||
*/
|
||||
//TODO: compatibility for simulated inventory (ClickPlan)
|
||||
private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
|
||||
int offset = inventory.getJavaId() == 0 ? 1 : 0; //offhand is not a viable temp slot
|
||||
HashSet<GeyserItemStack> itemBlacklist = new HashSet<>(slotBlacklist.length + 1);
|
||||
itemBlacklist.add(item);
|
||||
private static int findTempSlot(ClickPlan plan, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
|
||||
IntSortedSet potentialSlots = new IntLinkedOpenHashSet(PLAYER_INVENTORY_SIZE);
|
||||
int hotbarOffset = plan.getInventory().getOffsetForHotbar(0);
|
||||
|
||||
IntSet potentialSlots = new IntOpenHashSet(36);
|
||||
for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
|
||||
// Add main inventory slots in reverse
|
||||
for (int i = hotbarOffset - 1; i >= hotbarOffset - 27; i--) {
|
||||
potentialSlots.add(i);
|
||||
}
|
||||
|
||||
// Add hotbar slots in reverse
|
||||
for (int i = hotbarOffset + 8; i >= hotbarOffset; i--) {
|
||||
potentialSlots.add(i);
|
||||
}
|
||||
|
||||
for (int i : slotBlacklist) {
|
||||
potentialSlots.remove(i);
|
||||
GeyserItemStack blacklistedItem = inventory.getItem(i);
|
||||
if (!blacklistedItem.isEmpty()) {
|
||||
itemBlacklist.add(blacklistedItem);
|
||||
}
|
||||
|
||||
// Prefer empty slots
|
||||
IntIterator it = potentialSlots.iterator();
|
||||
while (it.hasNext()) {
|
||||
int slot = it.nextInt();
|
||||
if (plan.isEmpty(slot)) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i : potentialSlots) {
|
||||
GeyserItemStack testItem = inventory.getItem(i);
|
||||
if ((emptyOnly && !testItem.isEmpty())) {
|
||||
if (emptyOnly) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// No empty slots. Look for a slot that does not stack
|
||||
it = potentialSlots.iterator();
|
||||
|
||||
outer:
|
||||
while (it.hasNext()) {
|
||||
int slot = it.nextInt();
|
||||
if (plan.canStack(slot, item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean viable = true;
|
||||
for (GeyserItemStack blacklistedItem : itemBlacklist) {
|
||||
if (InventoryUtils.canStack(testItem, blacklistedItem)) {
|
||||
viable = false;
|
||||
break;
|
||||
for (int blacklistedSlot : slotBlacklist) {
|
||||
GeyserItemStack blacklistedItem = plan.getItem(blacklistedSlot);
|
||||
if (plan.canStack(slot, blacklistedItem)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (!viable) {
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
|
||||
return slot;
|
||||
}
|
||||
//could not find a viable temp slot
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion()));
|
||||
} else {
|
||||
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack()));
|
||||
slotPacket.setItem(inventory.getItem(i).getItemData(session));
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(slotPacket);
|
||||
|
@ -58,6 +58,14 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.getBedPosition() != null) {
|
||||
// https://github.com/GeyserMC/Geyser/issues/5001
|
||||
// Bedrock 1.21.22 started sending a MovePlayerPacket as soon as it got into a bed.
|
||||
// This trips up Fabric.
|
||||
return;
|
||||
}
|
||||
|
||||
float yaw = packet.getRotation().getY();
|
||||
float pitch = packet.getRotation().getX();
|
||||
float headYaw = packet.getRotation().getY();
|
||||
|
@ -33,7 +33,6 @@ import org.geysermc.erosion.Constants;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
@ -57,11 +56,6 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
entity.setEntityId(packet.getEntityId());
|
||||
|
||||
if (session.getErosionHandler().isActive()) {
|
||||
session.getErosionHandler().close();
|
||||
session.setErosionHandler(new GeyserboundHandshakePacketHandler(session));
|
||||
}
|
||||
|
||||
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
|
||||
JavaDimension newDimension = session.getRegistryCache().dimensions().byId(spawnInfo.getDimension());
|
||||
|
||||
|
@ -25,9 +25,6 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
@ -76,21 +73,11 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
||||
session.setGameMode(spawnInfo.getGameMode());
|
||||
|
||||
if (session.isRaining()) {
|
||||
LevelEventPacket stopRainPacket = new LevelEventPacket();
|
||||
stopRainPacket.setType(LevelEvent.STOP_RAINING);
|
||||
stopRainPacket.setData(0);
|
||||
stopRainPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopRainPacket);
|
||||
session.setRaining(false);
|
||||
session.updateRain(0);
|
||||
}
|
||||
|
||||
if (session.isThunder()) {
|
||||
LevelEventPacket stopThunderPacket = new LevelEventPacket();
|
||||
stopThunderPacket.setType(LevelEvent.STOP_THUNDERSTORM);
|
||||
stopThunderPacket.setData(0);
|
||||
stopThunderPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopThunderPacket);
|
||||
session.setThunder(false);
|
||||
session.updateThunder(0);
|
||||
}
|
||||
|
||||
JavaDimension newDimension = session.getRegistryCache().dimensions().byId(spawnInfo.getDimension());
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java;
|
||||
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundStartConfigurationPacket;
|
||||
|
||||
@Translator(packet = ClientboundStartConfigurationPacket.class)
|
||||
public class JavaStartConfigurationTranslator extends PacketTranslator<ClientboundStartConfigurationPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundStartConfigurationPacket packet) {
|
||||
var erosionHandler = session.getErosionHandler();
|
||||
if (erosionHandler.isActive()) {
|
||||
// Set new handler before closing
|
||||
session.setErosionHandler(new GeyserboundHandshakePacketHandler(session));
|
||||
erosionHandler.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecuteInEventLoop() {
|
||||
// Execute outside of event loop to cancel any pending erosion futures
|
||||
return false;
|
||||
}
|
||||
}
|
@ -25,8 +25,13 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.TranslationArgument;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
@ -34,18 +39,47 @@ import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
||||
|
||||
@Translator(packet = ClientboundSystemChatPacket.class)
|
||||
public class JavaSystemChatTranslator extends PacketTranslator<ClientboundSystemChatPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundSystemChatPacket packet) {
|
||||
if (packet.getContent() instanceof TranslatableComponent component && component.key().equals("chat.disabled.missingProfileKey")) {
|
||||
// We likely got this message as a response to a player trying to chat
|
||||
// As there SHOULD be no false flags for this, print every time it shows up in chat.
|
||||
if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) {
|
||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale()));
|
||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat"));
|
||||
if (packet.getContent() instanceof TranslatableComponent component) {
|
||||
if (component.key().equals("chat.disabled.missingProfileKey")) {
|
||||
// We likely got this message as a response to a player trying to chat
|
||||
// As there SHOULD be no false flags for this, print every time it shows up in chat.
|
||||
if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) {
|
||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale()));
|
||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat"));
|
||||
}
|
||||
} else if (component.key().equals("sleep.players_sleeping")) {
|
||||
if (component.arguments().size() == 2) {
|
||||
// Hack FYI, but it allows Bedrock players to easily understand this information
|
||||
// without it being covered up or saying the night is being slept through.
|
||||
Integer numPlayersSleeping = convertToInt(component.arguments().get(0));
|
||||
Integer totalPlayersNeeded = convertToInt(component.arguments().get(1));
|
||||
if (numPlayersSleeping != null && totalPlayersNeeded != null) {
|
||||
LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket();
|
||||
sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS);
|
||||
sleepInfoPacket.setTag(NbtMap.builder()
|
||||
.putInt("ableToSleep", totalPlayersNeeded)
|
||||
.putInt("overworldPlayerCount", totalPlayersNeeded)
|
||||
.putInt("sleepingPlayerCount", numPlayersSleeping)
|
||||
.build());
|
||||
session.sendUpstreamPacket(sleepInfoPacket);
|
||||
}
|
||||
}
|
||||
} else if (component.key().equals("sleep.skipping_night")) {
|
||||
LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket();
|
||||
sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS);
|
||||
sleepInfoPacket.setTag(NbtMap.builder()
|
||||
.putInt("ableToSleep", 1)
|
||||
.putInt("overworldPlayerCount", 1)
|
||||
.putInt("sleepingPlayerCount", 1)
|
||||
.build());
|
||||
session.sendUpstreamPacket(sleepInfoPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,4 +102,19 @@ public class JavaSystemChatTranslator extends PacketTranslator<ClientboundSystem
|
||||
session.getUpstream().queuePostStartGamePacket(textPacket);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Integer convertToInt(TranslationArgument translationArgument) {
|
||||
Object value = translationArgument.value();
|
||||
if (value instanceof Number number) {
|
||||
return number.intValue();
|
||||
}
|
||||
if (value instanceof TextComponent textComponent) {
|
||||
try {
|
||||
return Integer.parseInt(textComponent.content());
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.notify.RespawnScreenV
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.notify.ThunderStrengthValue;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundGameEventPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
@ -48,9 +46,6 @@ import org.geysermc.geyser.util.EntityUtils;
|
||||
|
||||
@Translator(packet = ClientboundGameEventPacket.class)
|
||||
public class JavaGameEventTranslator extends PacketTranslator<ClientboundGameEventPacket> {
|
||||
// Strength of rainstorms and thunderstorms is a 0-1 float on Java, while on Bedrock it is a 0-65535 int
|
||||
private static final int MAX_STORM_STRENGTH = 65535;
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundGameEventPacket packet) {
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
@ -65,42 +60,20 @@ public class JavaGameEventTranslator extends PacketTranslator<ClientboundGameEve
|
||||
// As a result many developers use these packets for the opposite of what their names implies
|
||||
// Behavior last verified with Java 1.19.4 and Bedrock 1.19.71
|
||||
case START_RAIN:
|
||||
LevelEventPacket stopRainPacket = new LevelEventPacket();
|
||||
stopRainPacket.setType(LevelEvent.STOP_RAINING);
|
||||
stopRainPacket.setData(0);
|
||||
stopRainPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopRainPacket);
|
||||
session.setRaining(false);
|
||||
session.updateRain(0);
|
||||
break;
|
||||
case STOP_RAIN:
|
||||
LevelEventPacket startRainPacket = new LevelEventPacket();
|
||||
startRainPacket.setType(LevelEvent.START_RAINING);
|
||||
startRainPacket.setData(MAX_STORM_STRENGTH);
|
||||
startRainPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(startRainPacket);
|
||||
session.setRaining(true);
|
||||
session.updateRain(1);
|
||||
break;
|
||||
case RAIN_STRENGTH:
|
||||
float rainStrength = ((RainStrengthValue) packet.getValue()).getStrength();
|
||||
boolean isCurrentlyRaining = rainStrength > 0f;
|
||||
LevelEventPacket changeRainPacket = new LevelEventPacket();
|
||||
changeRainPacket.setType(isCurrentlyRaining ? LevelEvent.START_RAINING : LevelEvent.STOP_RAINING);
|
||||
// This is the rain strength on LevelEventType.START_RAINING, but can be any value on LevelEventType.STOP_RAINING
|
||||
changeRainPacket.setData((int) (rainStrength * MAX_STORM_STRENGTH));
|
||||
changeRainPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(changeRainPacket);
|
||||
session.setRaining(isCurrentlyRaining);
|
||||
float rainStrength = ((RainStrengthValue) packet.getValue()).getStrength();
|
||||
session.updateRain(rainStrength);
|
||||
break;
|
||||
case THUNDER_STRENGTH:
|
||||
// See above, same process
|
||||
float thunderStrength = ((ThunderStrengthValue) packet.getValue()).getStrength();
|
||||
boolean isCurrentlyThundering = thunderStrength > 0f;
|
||||
LevelEventPacket changeThunderPacket = new LevelEventPacket();
|
||||
changeThunderPacket.setType(isCurrentlyThundering ? LevelEvent.START_THUNDERSTORM : LevelEvent.STOP_THUNDERSTORM);
|
||||
changeThunderPacket.setData((int) (thunderStrength * MAX_STORM_STRENGTH));
|
||||
changeThunderPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(changeThunderPacket);
|
||||
session.setThunder(isCurrentlyThundering);
|
||||
session.updateThunder(thunderStrength);
|
||||
break;
|
||||
case CHANGE_GAMEMODE:
|
||||
GameMode gameMode = (GameMode) packet.getValue();
|
||||
|
@ -291,10 +291,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
||||
session.sendUpstreamPacket(soundEventPacket);
|
||||
}
|
||||
}
|
||||
case PARTICLES_DRAGON_BLOCK_BREAK -> {
|
||||
effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_GENERIC_SPAWN);
|
||||
effectPacket.setData(61);
|
||||
}
|
||||
case PARTICLES_DRAGON_BLOCK_BREAK -> effectPacket.setType(ParticleType.DRAGON_DESTROY_BLOCK);
|
||||
case PARTICLES_WATER_EVAPORATING -> {
|
||||
effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EVAPORATE_WATER);
|
||||
effectPacket.setPosition(pos.add(-0.5f, 0.5f, -0.5f));
|
||||
|
@ -26,16 +26,21 @@
|
||||
package org.geysermc.geyser.translator.text;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -341,16 +346,16 @@ public class MessageTranslator {
|
||||
// Though, Bedrock cannot care about the signed stuff.
|
||||
TranslatableComponent.Builder withDecoration = Component.translatable()
|
||||
.key(chat.translationKey())
|
||||
.style(TextDecoration.getStyle(chat));
|
||||
.style(ChatDecoration.getStyle(chat));
|
||||
List<ChatTypeDecoration.Parameter> parameters = chat.parameters();
|
||||
List<Component> args = new ArrayList<>(3);
|
||||
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
|
||||
if (parameters.contains(ChatDecoration.Parameter.TARGET)) {
|
||||
args.add(targetName);
|
||||
}
|
||||
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
|
||||
if (parameters.contains(ChatDecoration.Parameter.SENDER)) {
|
||||
args.add(sender);
|
||||
}
|
||||
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
|
||||
if (parameters.contains(ChatDecoration.Parameter.CONTENT)) {
|
||||
args.add(message);
|
||||
}
|
||||
withDecoration.arguments(args);
|
||||
@ -426,17 +431,91 @@ public class MessageTranslator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an NbtMap provided from a registry into a string.
|
||||
* Deserialize an NbtMap with a description text component (usually provided from a registry) into a Bedrock-formatted string.
|
||||
*/
|
||||
// This may be a Component in the future.
|
||||
public static String deserializeDescription(NbtMap tag) {
|
||||
public static String deserializeDescription(GeyserSession session, NbtMap tag) {
|
||||
NbtMap description = tag.getCompound("description");
|
||||
String translate = description.getString("translate", null);
|
||||
if (translate == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag);
|
||||
return "";
|
||||
Component parsed = componentFromNbtTag(description);
|
||||
return convertMessage(session, parsed);
|
||||
}
|
||||
|
||||
public static Component componentFromNbtTag(Object nbtTag) {
|
||||
return componentFromNbtTag(nbtTag, Style.empty());
|
||||
}
|
||||
|
||||
private static Component componentFromNbtTag(Object nbtTag, Style style) {
|
||||
if (nbtTag instanceof String literal) {
|
||||
return Component.text(literal).style(style);
|
||||
} else if (nbtTag instanceof List<?> list) {
|
||||
return Component.join(JoinConfiguration.noSeparators(), componentsFromNbtList(list, style));
|
||||
} else if (nbtTag instanceof NbtMap map) {
|
||||
Component component = null;
|
||||
String text = map.getString("text", null);
|
||||
if (text != null) {
|
||||
component = Component.text(text);
|
||||
} else {
|
||||
String translateKey = map.getString("translate", null);
|
||||
if (translateKey != null) {
|
||||
String fallback = map.getString("fallback", "");
|
||||
List<Component> args = new ArrayList<>();
|
||||
|
||||
Object with = map.get("with");
|
||||
if (with instanceof List<?> list) {
|
||||
args = componentsFromNbtList(list, style);
|
||||
} else if (with != null) {
|
||||
args.add(componentFromNbtTag(with, style));
|
||||
}
|
||||
component = Component.translatable(translateKey, fallback, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (component != null) {
|
||||
Style newStyle = getStyleFromNbtMap(map, style);
|
||||
component = component.style(newStyle);
|
||||
|
||||
Object extra = map.get("extra");
|
||||
if (extra != null) {
|
||||
component = component.append(componentFromNbtTag(extra, newStyle));
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
return translate;
|
||||
|
||||
throw new IllegalArgumentException("Expected tag to be a literal string, a list of components, or a component object with a text/translate key");
|
||||
}
|
||||
|
||||
private static List<Component> componentsFromNbtList(List<?> list, Style style) {
|
||||
List<Component> components = new ArrayList<>();
|
||||
for (Object entry : list) {
|
||||
components.add(componentFromNbtTag(entry, style));
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
public static Style getStyleFromNbtMap(NbtMap map) {
|
||||
Style.Builder style = Style.style();
|
||||
|
||||
String colorString = map.getString("color", null);
|
||||
if (colorString != null) {
|
||||
if (colorString.startsWith(TextColor.HEX_PREFIX)) {
|
||||
style.color(TextColor.fromHexString(colorString));
|
||||
} else {
|
||||
style.color(NamedTextColor.NAMES.value(colorString));
|
||||
}
|
||||
}
|
||||
|
||||
map.listenForBoolean("bold", value -> style.decoration(TextDecoration.BOLD, value));
|
||||
map.listenForBoolean("italic", value -> style.decoration(TextDecoration.ITALIC, value));
|
||||
map.listenForBoolean("underlined", value -> style.decoration(TextDecoration.UNDERLINED, value));
|
||||
map.listenForBoolean("strikethrough", value -> style.decoration(TextDecoration.STRIKETHROUGH, value));
|
||||
map.listenForBoolean("obfuscated", value -> style.decoration(TextDecoration.OBFUSCATED, value));
|
||||
|
||||
return style.build();
|
||||
}
|
||||
|
||||
public static Style getStyleFromNbtMap(NbtMap map, Style base) {
|
||||
return base.merge(getStyleFromNbtMap(map));
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
@ -28,11 +28,9 @@ package org.geysermc.geyser.util;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ChangeDimensionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket;
|
||||
@ -89,18 +87,8 @@ public class DimensionUtils {
|
||||
entityEffects.clear();
|
||||
|
||||
// Always reset weather, as it sometimes suddenly starts raining. See https://github.com/GeyserMC/Geyser/issues/3679
|
||||
LevelEventPacket stopRainPacket = new LevelEventPacket();
|
||||
stopRainPacket.setType(LevelEvent.STOP_RAINING);
|
||||
stopRainPacket.setData(0);
|
||||
stopRainPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopRainPacket);
|
||||
session.setRaining(false);
|
||||
LevelEventPacket stopThunderPacket = new LevelEventPacket();
|
||||
stopThunderPacket.setType(LevelEvent.STOP_THUNDERSTORM);
|
||||
stopThunderPacket.setData(0);
|
||||
stopThunderPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopThunderPacket);
|
||||
session.setThunder(false);
|
||||
session.updateRain(0);
|
||||
session.updateThunder(0);
|
||||
|
||||
finalizeDimensionSwitch(session, player);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Gradle settings
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
org.gradle.daemon=false
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.vfs.watch=false
|
||||
|
@ -10,9 +10,9 @@ netty-io-uring = "0.0.25.Final-SNAPSHOT"
|
||||
guava = "29.0-jre"
|
||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||
websocket = "1.5.1"
|
||||
protocol-connection = "3.0.0.Beta3-20240819.124045-12"
|
||||
protocol-common = "3.0.0.Beta3-20240819.124045-10"
|
||||
protocol-codec = "3.0.0.Beta3-20240819.124045-13"
|
||||
protocol-connection = "3.0.0.Beta4-20240828.162251-1"
|
||||
protocol-common = "3.0.0.Beta4-20240828.162251-1"
|
||||
protocol-codec = "3.0.0.Beta4-20240828.162251-1"
|
||||
raknet = "1.0.0.CR3-20240416.144209-1"
|
||||
minecraftauth = "4.1.1-20240806.235051-7"
|
||||
mcprotocollib = "324f066"
|
||||
@ -134,6 +134,7 @@ protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-con
|
||||
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
|
||||
|
||||
# plugins
|
||||
lombok = { group = "io.freefair.gradle", name = "lombok-plugin", version.ref = "lombok" }
|
||||
indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" }
|
||||
shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
|
||||
architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" }
|
||||
@ -141,7 +142,6 @@ architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury.
|
||||
minotaur = { group = "com.modrinth.minotaur", name = "Minotaur", version.ref = "minotaur" }
|
||||
|
||||
[plugins]
|
||||
lombok = { id = "io.freefair.lombok", version.ref = "lombok" }
|
||||
indra = { id = "net.kyori.indra", version.ref = "indra" }
|
||||
blossom = { id = "net.kyori.blossom", version.ref = "blossom" }
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren