diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c32bad0c8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +tab_width = 4 +max_line_length = off + +[*.java] +ij_java_class_count_to_use_import_on_demand = 9999 +ij_java_doc_align_exception_comments = false +ij_java_doc_align_param_comments = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e94ce965..2bcf25e8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,19 +103,13 @@ jobs: bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar changelog: ${{ steps.metadata.outputs.body }} - - name: Publish to Modrinth (Fabric) + - name: Publish to Modrinth if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} env: + CHANGELOG: ${{ steps.metadata.outputs.body }} BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }} MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} - run: ./gradlew fabric:modrinth - - - name: Publish to Modrinth (NeoForge) - if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} - env: - BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }} - MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} - run: ./gradlew neoforge:modrinth + run: ./gradlew modrinth - name: Notify Discord if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }} diff --git a/README.md b/README.md index dd2d096ec..e3b5a496a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,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! -### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.0 and Minecraft Java 1.20.5/1.20.6 +### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21 ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java index f7da4b932..0dd0d3b33 100644 --- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java +++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java @@ -73,7 +73,10 @@ public interface JavaBlockState { * Gets whether the block state has block entity * * @return whether the block state has block entity + * @deprecated Does not have an effect. If you were using this to + * set piston behavior, use {@link #pistonBehavior()} instead. */ + @Deprecated(forRemoval = true) boolean hasBlockEntity(); /** @@ -104,6 +107,11 @@ public interface JavaBlockState { Builder pistonBehavior(@Nullable String pistonBehavior); + /** + * @deprecated Does not have an effect. If you were using this to + * * set piston behavior, use {@link #pistonBehavior(String)} instead. + */ + @Deprecated(forRemoval = true) Builder hasBlockEntity(boolean hasBlockEntity); JavaBlockState build(); diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index e93c096a1..910e50723 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -34,3 +34,8 @@ tasks.withType { exclude(dependency("io.netty:netty-resolver-dns:.*")) } } + +modrinth { + uploadFile.set(tasks.getByPath("shadowJar")) + loaders.add("bungeecord") +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 062ef6f76..cd6b59f64 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -81,7 +81,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { // Copied from ViaVersion. // https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43 try { - ProtocolConstants.class.getField("MINECRAFT_1_20_5"); + ProtocolConstants.class.getField("MINECRAFT_1_21"); } catch (NoSuchFieldException e) { geyserLogger.error(" / \\"); geyserLogger.error(" / \\"); diff --git a/bootstrap/mod/build.gradle.kts b/bootstrap/mod/build.gradle.kts index 7651a2df2..32224d00b 100644 --- a/bootstrap/mod/build.gradle.kts +++ b/bootstrap/mod/build.gradle.kts @@ -6,6 +6,13 @@ loom { mixin.defaultRefmapName.set("geyser-refmap.json") } +afterEvaluate { + // We don't need these + tasks.named("remapModrinthJar").configure { + enabled = false + } +} + dependencies { api(projects.core) compileOnly(libs.mixin) diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index cd513c1e4..25bd0af9d 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -63,6 +63,7 @@ tasks { modrinth { loaders.add("fabric") + uploadFile.set(tasks.getByPath("remapModrinthJar")) dependencies { required.project("fabric-api") } diff --git a/bootstrap/mod/fabric/src/main/resources/fabric.mod.json b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json index 93f48b73c..262f9833a 100644 --- a/bootstrap/mod/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json @@ -23,8 +23,8 @@ "geyser.mixins.json" ], "depends": { - "fabricloader": ">=0.15.10", + "fabricloader": ">=0.15.11", "fabric": "*", - "minecraft": ">=1.20.5" + "minecraft": ">=1.21" } } diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index 92ffae7e5..e0e7c2dfa 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -55,4 +55,5 @@ tasks { modrinth { loaders.add("neoforge") + uploadFile.set(tasks.getByPath("remapModrinthJar")) } \ No newline at end of file diff --git a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml index ff2823aa2..fa01bb6ec 100644 --- a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -14,12 +14,12 @@ config = "geyser.mixins.json" [[dependencies.geyser_neoforge]] modId="neoforge" type="required" - versionRange="[20.5.0-beta,)" + versionRange="[21.0.0-beta,)" ordering="NONE" side="BOTH" [[dependencies.geyser_neoforge]] modId="minecraft" type="required" - versionRange="[1.20.5,1.21)" + versionRange="[1.21,)" ordering="NONE" side="BOTH" \ No newline at end of file diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java index 5543dbcee..db1768737 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java @@ -30,6 +30,7 @@ import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; import net.minecraft.core.RegistryAccess; import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -39,14 +40,17 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BannerBlockEntity; import net.minecraft.world.level.block.entity.BannerPatternLayers; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.DecoratedPotBlockEntity; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.status.ChunkStatus; import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.math.vector.Vector3i; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.Holder; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer; @@ -55,6 +59,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponen import java.util.HashMap; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; public class GeyserModWorldManager extends GeyserWorldManager { @@ -160,6 +165,27 @@ public class GeyserModWorldManager extends GeyserWorldManager { return future; } + @Override + public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer> apply) { + server.execute(() -> { + ServerPlayer player = getPlayer(session); + if (player == null) { + return; + } + + BlockPos blockPos = new BlockPos(pos.getX(), pos.getY(), pos.getZ()); + // Don't create a new block entity if invalid + //noinspection resource - level() is just a getter + BlockEntity blockEntity = player.level().getChunkAt(blockPos).getBlockEntity(blockPos); + if (blockEntity instanceof DecoratedPotBlockEntity pot) { + List sherds = pot.getDecorations().ordered() + .stream().map(item -> BuiltInRegistries.ITEM.getKey(item).toString()) + .toList(); + apply.accept(sherds); + } + }); + } + private ServerPlayer getPlayer(GeyserSession session) { return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid()); } @@ -173,7 +199,7 @@ public class GeyserModWorldManager extends GeyserWorldManager { return patternLayers.layers().stream() .map(layer -> { BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern( - layer.pattern().value().assetId().toString(), layer.pattern().value().translationKey() + MinecraftKey.key(layer.pattern().value().assetId().toString()), layer.pattern().value().translationKey() ); return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId()); }) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 831cd27ae..fa24f3566 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -78,3 +78,8 @@ tasks.withType { exclude(dependency("com.mojang:.*")) } } + +modrinth { + uploadFile.set(tasks.getByPath("shadowJar")) + loaders.addAll("spigot", "paper") +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index ec731d00c..8e853b629 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -253,6 +253,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { SpigotAdapters.registerWorldAdapter(nmsVersion); geyserLogger.debug("Using spigot NMS adapter for nms version: " + nmsVersion); } catch (Exception e) { // Likely running on Paper 1.20.5+ + geyserLogger.debug("Unable to find spigot world manager: " + e.getMessage()); //noinspection deprecation int protocolVersion = Bukkit.getUnsafe().getProtocolVersion(); PaperAdapters.registerClosestWorldAdapter(protocolVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index e247a72c7..73356c4e7 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -29,10 +29,13 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.DecoratedPot; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector3i; +import org.geysermc.erosion.bukkit.BukkitUtils; import org.geysermc.erosion.bukkit.PickBlockUtils; import org.geysermc.erosion.bukkit.SchedulerUtils; import org.geysermc.geyser.GeyserImpl; @@ -43,8 +46,10 @@ 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; +import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; /** * The base world manager to use when there is no supported NMS revision @@ -146,6 +151,21 @@ public class GeyserSpigotWorldManager extends WorldManager { return future.thenApply(RAW_TRANSFORMER); } + public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer> apply) { + Player bukkitPlayer; + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) { + return; + } + Block block = bukkitPlayer.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + SchedulerUtils.runTask(this.plugin, () -> { + var state = BukkitUtils.getBlockState(block); + if (!(state instanceof DecoratedPot pot)) { + return; + } + apply.accept(pot.getShards().stream().map(material -> material.getKey().toString()).toList()); + }, block); + } + /** * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id * to the current one. diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index da826803c..4daad9784 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -69,4 +69,9 @@ tasks.withType { exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*")) exclude(dependency("net.kyori:adventure-nbt:.*")) } +} + +modrinth { + uploadFile.set(tasks.getByPath("shadowJar")) + loaders.addAll("velocity") } \ No newline at end of file diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b7aab2bf0..190386667 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -12,6 +12,9 @@ repositories { } dependencies { + // 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)) implementation(libs.indra) implementation(libs.shadow) implementation(libs.architectury.plugin) diff --git a/build-logic/src/main/kotlin/LibsAccessor.kt b/build-logic/src/main/kotlin/LibsAccessor.kt new file mode 100644 index 000000000..2a0c09eb6 --- /dev/null +++ b/build-logic/src/main/kotlin/LibsAccessor.kt @@ -0,0 +1,6 @@ +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType + +val Project.libs: LibrariesForLibs + get() = rootProject.extensions.getByType() \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index b75e9c5be..7952bcf14 100644 --- a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -8,7 +8,6 @@ plugins { id("geyser.publish-conventions") id("architectury-plugin") id("dev.architectury.loom") - id("com.modrinth.minotaur") } // These are provided by Minecraft/modded platforms already, no need to include them @@ -39,7 +38,7 @@ provided("io.netty", "netty-resolver-dns-native-macos") provided("org.ow2.asm", "asm") architectury { - minecraft = "1.20.5" + minecraft = libs.minecraft.get().version as String } loom { @@ -83,7 +82,7 @@ tasks { register("remapModrinthJar", RemapJarTask::class) { dependsOn(shadowJar) inputFile.set(shadowJar.get().archiveFile) - archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) + archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER")) archiveClassifier.set("") } } @@ -93,7 +92,7 @@ afterEvaluate { // These are shaded, no need to JiJ them configurations["shadow"].dependencies.forEach {shadowed -> - println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}") + //println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}") providedDependencies.add("${shadowed.group}:${shadowed.name}") } @@ -101,39 +100,24 @@ afterEvaluate { configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep -> if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}") and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) { - println("Including dependency via JiJ: ${dep.id}") + //println("Including dependency via JiJ: ${dep.id}") dependencies.add("include", dep.moduleVersion.id.toString()) } else { - println("Not including ${dep.id} for ${project.name}!") + //println("Not including ${dep.id} for ${project.name}!") } } } dependencies { - minecraft("com.mojang:minecraft:1.20.5") + minecraft(libs.minecraft) mappings(loom.officialMojangMappings()) } repositories { // mavenLocal() - maven("https://repo.opencollab.dev/maven-releases/") - maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://repo.opencollab.dev/main") maven("https://jitpack.io") maven("https://oss.sonatype.org/content/repositories/snapshots/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://maven.neoforged.net/releases") -} - -modrinth { - token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token? - projectId.set("wKkoqHrH") - versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER")) - versionType.set("beta") - changelog.set("A changelog can be found at https://github.com/GeyserMC/Geyser/commits") - - syncBodyFrom.set(rootProject.file("README.md").readText()) - - uploadFile.set(tasks.getByPath("remapModrinthJar")) - gameVersions.addAll("1.20.5", "1.20.6") - failSilently.set(true) } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts new file mode 100644 index 000000000..d710ae1a2 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("com.modrinth.minotaur") +} + +// Ensure that the readme is synched +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")) + versionType.set("beta") + changelog.set(System.getenv("CHANGELOG") ?: "") + gameVersions.add(libs.minecraft.get().version as String) + failSilently.set(true) + + syncBodyFrom.set(rootProject.file("README.md").readText()) +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index dfdff2187..dfbf9837f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,14 @@ val moddedPlatforms = setOf( 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") @@ -38,4 +46,10 @@ subprojects { 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") + } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5072a1550..af4076c30 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,6 +1,6 @@ -import net.kyori.blossom.BlossomExtension - plugins { + // Allow blossom to mark sources root of templates + idea alias(libs.plugins.blossom) id("geyser.publish-conventions") } @@ -78,7 +78,7 @@ tasks.processResources { expand( "branch" to info.branch, "buildNumber" to info.buildNumber, - "projectVersion" to project.version, + "projectVersion" to info.version, "commit" to info.commit, "commitAbbrev" to info.commitAbbrev, "commitMessage" to info.commitMessage, @@ -87,21 +87,30 @@ tasks.processResources { } } -configure { - val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val info = GitInfo() - - replaceToken("\${version}", "${project.version} (${info.gitVersion})", mainFile) - replaceToken("\${gitVersion}", info.gitVersion, mainFile) - replaceToken("\${buildNumber}", info.buildNumber, mainFile) - replaceToken("\${branch}", info.branch, mainFile) - replaceToken("\${commit}", info.commit, mainFile) - replaceToken("\${repository}", info.repository, mainFile) +sourceSets { + main { + blossom { + val info = GitInfo() + javaSources { + property("version", "${info.version} (${info.gitVersion})") + property("gitVersion", info.gitVersion) + property("buildNumber", info.buildNumber.toString()) + property("branch", info.branch) + property("commit", info.commit) + property("repository", info.repository) + property("devVersion", info.isDev.toString()) + } + } + } } -fun Project.buildNumber(): Int = +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() +} + inner class GitInfo { val branch: String val commit: String @@ -114,22 +123,25 @@ inner class GitInfo { val commitMessage: String val repository: String + val isDev: Boolean + init { - // On Jenkins, a detached head is checked out, so indra cannot determine the branch. - // Fortunately, this environment variable is available. - branch = indraGit.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV" + branch = indraGit.branchName() ?: "DEV" val commit = indraGit.commit() this.commit = commit?.name ?: "0".repeat(40) commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7) gitVersion = "git-${branch}-${commitAbbrev}" - version = "${project.version} ($gitVersion)" - buildNumber = buildNumber() val git = indraGit.git() commitMessage = git?.commit()?.message ?: "" repository = git?.repository?.config?.getString("remote", "origin", "url") ?: "" + + buildNumber = buildNumber() + isDev = isDevBuild(branch, repository) + val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}") + version = "$projectVersion ($gitVersion)" } } diff --git a/core/src/main/java-templates/org/geysermc/geyser/BuildData.java b/core/src/main/java-templates/org/geysermc/geyser/BuildData.java new file mode 100644 index 000000000..0e4d08bfe --- /dev/null +++ b/core/src/main/java-templates/org/geysermc/geyser/BuildData.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 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; + +// The constants are replaced before compilation +public class BuildData { + public static final String GIT_VERSION = "{{ gitVersion }}"; + public static final String VERSION = "{{ version }}"; + + public static final String BUILD_NUMBER = "{{ buildNumber }}"; + public static final String BRANCH = "{{ branch }}"; + public static final String COMMIT = "{{ commit }}"; + public static final String REPOSITORY = "{{ repository }}"; + private static final String DEV = "{{ devVersion }}"; + + public static boolean isDevBuild() { + return Boolean.parseBoolean(DEV); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index ab17084a3..40aa8ada1 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -34,7 +34,7 @@ public final class Constants { public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v2/news/"; public static final String NEWS_PROJECT_NAME = "geyser"; - public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"; + public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://geysermc.org/download#floodgate"; public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download"; public static final String UPDATE_PERMISSION = "geyser.update"; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 88249ed5b..bbe6a34aa 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -121,13 +121,14 @@ public class GeyserImpl implements GeyserApi { public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); public static final String NAME = "Geyser"; - public static final String GIT_VERSION = "${gitVersion}"; - public static final String VERSION = "${version}"; + public static final String GIT_VERSION = BuildData.GIT_VERSION; + public static final String VERSION = BuildData.VERSION; - public static final String BUILD_NUMBER = "${buildNumber}"; - public static final String BRANCH = "${branch}"; - public static final String COMMIT = "${commit}"; - public static final String REPOSITORY = "${repository}"; + public static final String BUILD_NUMBER = BuildData.BUILD_NUMBER; + public static final String BRANCH = BuildData.BRANCH; + public static final String COMMIT = BuildData.COMMIT; + public static final String REPOSITORY = BuildData.REPOSITORY; + public static final boolean IS_DEV = BuildData.isDevBuild(); /** * Oauth client ID for Microsoft authentication @@ -213,6 +214,12 @@ public class GeyserImpl implements GeyserApi { logger.info(""); logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); logger.info(""); + if (IS_DEV) { + // TODO cloud use language string + //logger.info(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc")); + logger.info("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s".formatted("https://discord.gg/geysermc")); + logger.info(""); + } logger.info("******************************************"); /* Initialize registries */ @@ -690,6 +697,7 @@ public class GeyserImpl implements GeyserApi { * * @return true if the version number is not 'DEV'. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isProductionEnvironment() { // First is if Blossom runs, second is if Blossom doesn't run //noinspection ConstantConditions,MismatchedStringCase - changes in production diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index eb2e8ff47..c6852d577 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.command.defaults; +import com.fasterxml.jackson.databind.JsonNode; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommand; @@ -37,8 +37,7 @@ import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import java.io.IOException; import java.util.List; public class VersionCommand extends GeyserCommand { @@ -72,27 +71,36 @@ public class VersionCommand extends GeyserCommand { GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions)); // Disable update checking in dev mode and for players in Geyser Standalone - if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); - try { - String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + - URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); - if (buildXML.startsWith("")) { - int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); - int buildNum = this.geyser.buildNumber(); - if (latestBuildNum == buildNum) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); - } else { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated", - sender.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION)); - } - } else { - throw new AssertionError("buildNumber missing"); - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale())); + if (!GeyserImpl.getInstance().isProductionEnvironment() || (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { + return; + } + + if (GeyserImpl.IS_DEV) { + // TODO cloud use language string + sender.sendMessage("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s" + .formatted("https://discord.gg/geysermc")); + //sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", sender.locale(), "https://discord.gg/geysermc")); + return; + } + + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); + try { + int buildNumber = this.geyser.buildNumber(); + JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest"); + int latestBuildNumber = response.get("build").asInt(); + + if (latestBuildNumber == buildNumber) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); + return; } + + sender.sendMessage(GeyserLocale.getPlayerLocaleString( + "geyser.commands.version.outdated", + sender.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download" + )); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale())); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java index 31aa7cc73..f9b65a545 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java @@ -47,6 +47,7 @@ import java.util.function.BiConsumer; * metadata translators needed to translate the properties sent from the server. The translators are structured in such * a way that inserting a new one (for example in version updates) is convenient. * + * @param identifier the Bedrock identifier of this entity * @param the entity type this definition represents */ public record EntityDefinition(EntityFactory factory, EntityType entityType, String identifier, diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 21cc526dd..1496f8a82 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.entity; +import org.geysermc.geyser.entity.type.AbstractWindChargeEntity; +import org.geysermc.geyser.entity.factory.EntityFactory; import org.geysermc.geyser.entity.type.living.monster.raid.RavagerEntity; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; @@ -63,6 +65,9 @@ public final class EntityDefinitions { public static final EntityDefinition BEE; public static final EntityDefinition BLAZE; public static final EntityDefinition BOAT; + public static final EntityDefinition BOGGED; + public static final EntityDefinition BREEZE; + public static final EntityDefinition BREEZE_WIND_CHARGE; public static final EntityDefinition CAMEL; public static final EntityDefinition CAT; public static final EntityDefinition CAVE_SPIDER; @@ -165,6 +170,7 @@ public final class EntityDefinitions { public static final EntityDefinition VINDICATOR; public static final EntityDefinition WANDERING_TRADER; public static final EntityDefinition WARDEN; + public static final EntityDefinition WIND_CHARGE; public static final EntityDefinition WITCH; public static final EntityDefinition WITHER; public static final EntityDefinition WITHER_SKELETON; @@ -375,6 +381,18 @@ public final class EntityDefinitions { .heightAndWidth(0.25f) .build(); + EntityFactory windChargeSupplier = AbstractWindChargeEntity::new; + BREEZE_WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase) + .type(EntityType.BREEZE_WIND_CHARGE) + .identifier("minecraft:breeze_wind_charge_projectile") + .heightAndWidth(0.3125f) + .build(); + WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase) + .type(EntityType.WIND_CHARGE) + .identifier("minecraft:wind_charge_projectile") + .heightAndWidth(0.3125f) + .build(); + EntityDefinition abstractArrowBase = EntityDefinition.inherited(AbstractArrowEntity::new, entityBase) .addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags) .addTranslator(null) // "Piercing level" @@ -503,11 +521,20 @@ public final class EntityDefinitions { .height(0.9f).width(0.5f) .addTranslator(MetadataType.BYTE, BatEntity::setBatFlags) .build(); + BOGGED = EntityDefinition.inherited(BoggedEntity::new, mobEntityBase) + .type(EntityType.BOGGED) + .height(1.99f).width(0.6f) + .addTranslator(MetadataType.BOOLEAN, BoggedEntity::setSheared) + .build(); BLAZE = EntityDefinition.inherited(BlazeEntity::new, mobEntityBase) .type(EntityType.BLAZE) .height(1.8f).width(0.6f) .addTranslator(MetadataType.BYTE, BlazeEntity::setBlazeFlags) .build(); + BREEZE = EntityDefinition.inherited(BreezeEntity::new, mobEntityBase) + .type(EntityType.BREEZE) + .height(1.77f).width(0.6f) + .build(); CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase) .type(EntityType.CREEPER) .height(1.7f).width(0.6f) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java new file mode 100644 index 000000000..5678c3af4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java @@ -0,0 +1,53 @@ +/* + * 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.entity.type; + +import org.cloudburstmc.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +/** + * Note that, as of 1.21, a wind charge entity does not actually implement the thrown item. We're just reusing + * the "hide until far away" aspect. + */ +public class AbstractWindChargeEntity extends ThrowableItemEntity { + public AbstractWindChargeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void tick() { + super.tick(); + } + + @Override + protected float getDrag() { + // Always, even in water. As of 1.21. + return 1f; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java index 1ee706811..ba1241434 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java @@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; @@ -46,12 +46,7 @@ public class ArrowEntity extends AbstractArrowEntity { if (potionColor == -1) { dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0); } else { - TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); - if (potion != null && potion.getJavaColor() != -1) { - dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); - } else { - dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0); - } + dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, Potion.toTippedArrowId(potionColor)); } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index d9a64ccc6..47ae6777a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -41,7 +41,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import java.util.UUID; -public class BoatEntity extends Entity implements Tickable { +public class BoatEntity extends Entity implements Leashable, Tickable { /** * Required when IS_BUOYANT is sent in order for boats to work in the water.
@@ -65,6 +65,8 @@ public class BoatEntity extends Entity implements Tickable { @Getter private int variant; + private long leashHolderBedrockId = -1; + // Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it private final float ROWING_SPEED = 0.1f; @@ -147,8 +149,18 @@ public class BoatEntity extends Entity implements Tickable { } } + @Override + public void setLeashHolderBedrockId(long bedrockId) { + this.leashHolderBedrockId = bedrockId; + dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId); + } + @Override protected InteractiveTag testInteraction(Hand hand) { + InteractiveTag tag = super.testInteraction(hand); + if (tag != InteractiveTag.NONE) { + return tag; + } if (session.isSneaking()) { return InteractiveTag.NONE; } else if (passengers.size() < 2) { @@ -160,6 +172,10 @@ public class BoatEntity extends Entity implements Tickable { @Override public InteractionResult interact(Hand hand) { + InteractionResult result = super.interact(hand); + if (result != InteractionResult.PASS) { + return result; + } if (session.isSneaking()) { return InteractionResult.PASS; } else { @@ -191,6 +207,11 @@ public class BoatEntity extends Entity implements Tickable { } } + @Override + public long leashHolderBedrockId() { + return leashHolderBedrockId; + } + private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) { AnimatePacket packet = new AnimatePacket(); packet.setRuntimeEntityId(rower.getGeyserId()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 6267ee791..08e87dc03 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -40,6 +40,7 @@ import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; +import org.geysermc.geyser.item.Items; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; @@ -557,6 +558,17 @@ public class Entity implements GeyserEntity { * Should usually mirror {@link #interact(Hand)} without any side effects. */ protected InteractiveTag testInteraction(Hand hand) { + if (isAlive() && this instanceof Leashable leashable) { + if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) { + // Note this might be client side. Has yet to be an issue though, as of Java 1.21. + return InteractiveTag.REMOVE_LEASH; + } + if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) { + // We shall leash + return InteractiveTag.LEASH; + } + } + return InteractiveTag.NONE; } @@ -565,6 +577,18 @@ public class Entity implements GeyserEntity { * to ensure packet parity as well as functionality parity (such as sound effect responses). */ public InteractionResult interact(Hand hand) { + if (isAlive() && this instanceof Leashable leashable) { + if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) { + // Note this might also update client side (a theoretical Geyser/client desync and Java parity issue). + // Has yet to be an issue though, as of Java 1.21. + return InteractionResult.SUCCESS; + } + if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) { + // We shall leash + return InteractionResult.SUCCESS; + } + } + return InteractionResult.PASS; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java b/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java new file mode 100644 index 000000000..64d95ba3c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java @@ -0,0 +1,44 @@ +/* + * 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.entity.type; + +/** + * I can haz lead + * (The item, not the mineral) + */ +public interface Leashable { + void setLeashHolderBedrockId(long bedrockId); + + long leashHolderBedrockId(); + + default boolean canBeLeashed() { + return isNotLeashed(); + } + + default boolean isNotLeashed() { + return leashHolderBedrockId() == -1L; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java index f5145c11f..09c055c84 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java @@ -30,6 +30,8 @@ import org.cloudburstmc.protocol.bedrock.packet.AddPaintingPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.level.PaintingType; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.Holder; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.PaintingVariant; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; @@ -49,8 +51,14 @@ public class PaintingEntity extends Entity { // Wait until we get the metadata needed } - public void setPaintingType(ObjectEntityMetadata entityMetadata) { - PaintingType type = PaintingType.getByPaintingType(entityMetadata.getValue()); + public void setPaintingType(ObjectEntityMetadata> entityMetadata) { + if (!entityMetadata.getValue().isId()) { + return; + } + PaintingType type = session.getRegistryCache().paintings().byId(entityMetadata.getValue().id()); + if (type == null) { + return; + } AddPaintingPacket addPaintingPacket = new AddPaintingPacket(); addPaintingPacket.setUniqueEntityId(geyserId); addPaintingPacket.setRuntimeEntityId(geyserId); @@ -78,8 +86,12 @@ public class PaintingEntity extends Entity { private Vector3f fixOffset(PaintingType paintingName) { Vector3f position = super.position; - position = position.add(0.5, 0.5, 0.5); - double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0; + // ViaVersion already adds the offset for us on older versions, + // so no need to do it then otherwise it will be spaced + if (session.isEmulatePost1_18Logic()) { + position = position.add(0.5, 0.5, 0.5); + } + double widthOffset = paintingName.getWidth() > 1 && paintingName.getWidth() != 3 ? 0.5 : 0; double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0; return switch (direction) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java index 8f81125d0..f4b80edf1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java @@ -38,7 +38,7 @@ public class AmbientEntity extends MobEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java index 6182a27f4..a0ea79d67 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java @@ -43,7 +43,7 @@ public class DolphinEntity extends WaterEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return true; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java index 95145ae60..9accf178f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.entity.type.living; -import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Leashable; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; @@ -43,11 +43,10 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import java.util.UUID; -public class MobEntity extends LivingEntity { +public class MobEntity extends LivingEntity implements Leashable { /** * If another mob is holding this mob by a leash, this variable tracks their Bedrock entity ID. */ - @Getter private long leashHolderBedrockId; public MobEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { @@ -65,6 +64,7 @@ public class MobEntity extends LivingEntity { setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01); } + @Override public void setLeashHolderBedrockId(long bedrockId) { this.leashHolderBedrockId = bedrockId; dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId); @@ -79,10 +79,7 @@ public class MobEntity extends LivingEntity { return InteractiveTag.REMOVE_LEASH; } else { GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand); - if (itemStack.asItem() == Items.LEAD && canBeLeashed()) { - // We shall leash - return InteractiveTag.LEASH; - } else if (itemStack.asItem() == Items.NAME_TAG) { + if (itemStack.asItem() == Items.NAME_TAG) { InteractionResult result = checkInteractWithNameTag(itemStack); if (result.consumesAction()) { return InteractiveTag.NAME; @@ -99,9 +96,6 @@ public class MobEntity extends LivingEntity { if (!isAlive()) { // dead lol return InteractionResult.PASS; - } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) { - // TODO looks like the client assumes it will go through and removes the attachment itself? - return InteractionResult.SUCCESS; } else { GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand); InteractionResult result = checkPriorityInteractions(itemInHand); @@ -115,10 +109,7 @@ public class MobEntity extends LivingEntity { } private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) { - if (itemInHand.asItem() == Items.LEAD && canBeLeashed()) { - // We shall leash - return InteractionResult.SUCCESS; - } else if (itemInHand.asItem() == Items.NAME_TAG) { + if (itemInHand.asItem() == Items.NAME_TAG) { InteractionResult result = checkInteractWithNameTag(itemInHand); if (result.consumesAction()) { return result; @@ -143,12 +134,14 @@ public class MobEntity extends LivingEntity { return InteractionResult.PASS; } - protected boolean canBeLeashed() { + @Override + public boolean canBeLeashed() { return isNotLeashed() && !isEnemy(); } - protected final boolean isNotLeashed() { - return leashHolderBedrockId == -1L; + @Override + public long leashHolderBedrockId() { + return leashHolderBedrockId; } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index 80a5af442..6285bd9a4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -122,7 +122,7 @@ public class SquidEntity extends WaterEntity implements Tickable { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return isNotLeashed(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java index a847c4cd7..ae9d0d659 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java @@ -38,7 +38,7 @@ public class WaterEntity extends CreatureEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java index a87b1dd5e..a0ab56ead 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -72,7 +72,7 @@ public class AxolotlEntity extends AnimalEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return true; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java index 74c937417..cc23fc607 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java @@ -63,7 +63,7 @@ public class HoglinEntity extends AnimalEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return isNotLeashed(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java index 79401f63f..aaa7c2d7e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java @@ -123,7 +123,7 @@ public class PandaEntity extends AnimalEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return false; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index b3c1128e3..16901a844 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -56,7 +56,7 @@ public class TurtleEntity extends AnimalEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java index e16823d37..ea347d193 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -84,7 +84,7 @@ public abstract class TameableEntity extends AnimalEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return isNotLeashed(); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index 4573f0e7a..e7fde2be8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -33,36 +33,28 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.enchantment.EnchantmentComponent; import org.geysermc.geyser.item.type.DyeItem; -import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.ItemUtils; +import org.geysermc.mcprotocollib.protocol.data.game.Holder; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.WolfVariant; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.util.Collections; import java.util.Locale; -import java.util.Set; import java.util.UUID; public class WolfEntity extends TameableEntity { - /** - * A list of all foods a wolf can eat on Java Edition. - * Used to display interactive tag or particles if needed. - * TODO generate - */ - private static final Set WOLF_FOODS = Set.of(Items.PUFFERFISH, Items.TROPICAL_FISH, Items.CHICKEN, Items.COOKED_CHICKEN, - Items.PORKCHOP, Items.BEEF, Items.RABBIT, Items.COOKED_PORKCHOP, Items.COOKED_BEEF, Items.ROTTEN_FLESH, Items.MUTTON, Items.COOKED_MUTTON, - Items.COOKED_RABBIT); - private byte collarColor = 14; // Red - default private boolean isCurseOfBinding = false; @@ -112,12 +104,14 @@ public class WolfEntity extends TameableEntity { } // 1.20.5+ - public void setWolfVariant(IntEntityMetadata entityMetadata) { - WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(entityMetadata.getPrimitiveValue()); - if (wolfVariant == null) { - wolfVariant = WolfVariant.PALE; - } - dirtyMetadata.put(EntityDataTypes.VARIANT, wolfVariant.ordinal()); + public void setWolfVariant(ObjectEntityMetadata> entityMetadata) { + entityMetadata.getValue().ifId(id -> { + BuiltInWolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(id); + if (wolfVariant == null) { + wolfVariant = BuiltInWolfVariant.PALE; + } + dirtyMetadata.put(EntityDataTypes.VARIANT, wolfVariant.ordinal()); + }); } @Override @@ -129,11 +123,11 @@ public class WolfEntity extends TameableEntity { @Override public void setChestplate(ItemStack stack) { super.setChestplate(stack); - isCurseOfBinding = ItemUtils.getEnchantmentLevel(stack.getDataComponents(), Enchantment.JavaEnchantment.BINDING_CURSE) > 0; + isCurseOfBinding = ItemUtils.hasEffect(session, stack, EnchantmentComponent.PREVENT_ARMOR_CHANGE); // TODO test } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed(); } @@ -187,7 +181,7 @@ public class WolfEntity extends TameableEntity { } // Ordered by bedrock id - public enum WolfVariant { + public enum BuiltInWolfVariant { PALE, ASHEN, BLACK, @@ -198,16 +192,16 @@ public class WolfEntity extends TameableEntity { STRIPED, WOODS; - private static final WolfVariant[] VALUES = values(); + private static final BuiltInWolfVariant[] VALUES = values(); private final String javaIdentifier; - WolfVariant() { + BuiltInWolfVariant() { this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT); } - public static @Nullable WolfVariant getByJavaIdentifier(String javaIdentifier) { - for (WolfVariant wolfVariant : VALUES) { + public static @Nullable BuiltInWolfVariant getByJavaIdentifier(String javaIdentifier) { + for (BuiltInWolfVariant wolfVariant : VALUES) { if (wolfVariant.javaIdentifier.equals(javaIdentifier)) { return wolfVariant; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java index 64e35e52e..2492aabd7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java @@ -47,7 +47,7 @@ public class AbstractMerchantEntity extends AgeableEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return false; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java new file mode 100644 index 000000000..806d58ed1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java @@ -0,0 +1,73 @@ +/* + * 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.entity.type.living.monster; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; + +import java.util.UUID; + +public class BoggedEntity extends AbstractSkeletonEntity { + private boolean sheared = false; + + public BoggedEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setSheared(BooleanEntityMetadata entityMetadata) { + this.sheared = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SHEARED, this.sheared); + } + + @Override + protected @NonNull InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) { + if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) { + return InteractiveTag.SHEAR; + } + return super.testMobInteraction(hand, itemInHand); + } + + @Override + protected @NonNull InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) { + if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) { + return InteractionResult.SUCCESS; + } + return super.mobInteract(hand, itemInHand); + } + + private boolean readyForShearing() { + return !this.sheared && this.isAlive(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java new file mode 100644 index 000000000..251a77fb9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java @@ -0,0 +1,48 @@ +/* + * 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.entity.type.living.monster; + +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose; + +import java.util.UUID; + +public class BreezeEntity extends MonsterEntity { + public BreezeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void setPose(Pose pose) { + // TODO Test + setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, pose == Pose.SHOOTING); + setFlag(EntityFlag.JUMP_GOAL_JUMP, pose == Pose.INHALING); + super.setPose(pose); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java index 206746fb9..3d6e381c7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java @@ -58,7 +58,7 @@ public class ZoglinEntity extends MonsterEntity { } @Override - protected boolean canBeLeashed() { + public boolean canBeLeashed() { return isNotLeashed(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index ad6729c42..45fea4d48 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -27,15 +27,18 @@ package org.geysermc.geyser.entity.type.player; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; +import lombok.Setter; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.AttributeData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.DimensionUtils; @@ -69,6 +72,15 @@ public class SessionPlayerEntity extends PlayerEntity { private int lastAirSupply = getMaxAir(); + /** + * Determines if our position is currently out-of-sync with the Java server + * due to our workaround for the void floor + *

+ * Must be reset when dying, switching worlds, or being teleported out of the void + */ + @Getter @Setter + private boolean voidPositionDesynched; + public SessionPlayerEntity(GeyserSession session) { super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null); @@ -87,10 +99,25 @@ public class SessionPlayerEntity extends PlayerEntity { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + if (voidPositionDesynched) { + if (!isBelowVoidFloor()) { + voidPositionDesynched = false; // No need to fix our offset; we've been moved + } + } super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset())); } + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + if (voidPositionDesynched) { + if (!isBelowVoidFloor()) { + voidPositionDesynched = false; // No need to fix our offset; we've been moved + } + } + super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported); + } + @Override public void setPosition(Vector3f position) { if (valid) { // Don't update during session init @@ -220,11 +247,14 @@ public class SessionPlayerEntity extends PlayerEntity { public void setLastDeathPosition(@Nullable GlobalPos pos) { if (pos != null) { dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition()); - dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension())); + dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension().asString())); dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, true); } else { dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false); } + + // We're either respawning or switching worlds, either way, we are no longer desynched + this.setVoidPositionDesynched(false); } @Override @@ -276,4 +306,48 @@ public class SessionPlayerEntity extends PlayerEntity { public void resetAir() { this.setAirSupply(getMaxAir()); } + + private boolean isBelowVoidFloor() { + return position.getY() < voidFloorPosition(); + } + + public int voidFloorPosition() { + // The void floor is offset about 40 blocks below the bottom of the world + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + return bedrockDimension.minY() - 40; + } + + /** + * This method handles teleporting the player below or above the Bedrock void floor. + * The Java server should never see this desync as we adjust the position that we send to it + * + * @param up in which direction to teleport - true to resync our position, or false to be + * teleported below the void floor. + */ + public void teleportVoidFloorFix(boolean up) { + // Safety to avoid double teleports + if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) { + return; + } + + // Work around there being a floor at the bottom of the world and teleport the player below it + // Moving from below to above the void floor works fine + Vector3f newPosition = this.getPosition(); + if (up) { + newPosition = newPosition.up(4f); + voidPositionDesynched = false; + } else { + newPosition = newPosition.down(4f); + voidPositionDesynched = true; + } + + this.setPositionManual(newPosition); + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(newPosition); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); + session.sendUpstreamPacketImmediately(movePlayerPacket); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index f2f93b266..1c5060de0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -152,7 +152,7 @@ public class SkullPlayerEntity extends PlayerEntity { case EAST -> x -= 0.24f; } } else { - rotation = (180f + (blockState.getValue(Properties.ROTATION_16) * 22.5f)) % 360; + rotation = (180f + blockState.getValue(Properties.ROTATION_16, 0) * 22.5f) % 360; } moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java index 81818613f..e78a4d2c6 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java @@ -38,7 +38,7 @@ import org.jetbrains.annotations.Range; */ @Getter public class Container extends Inventory { - private final PlayerInventory playerInventory; + protected final PlayerInventory playerInventory; private final int containerSize; /** diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java index 41452bed6..fb118252d 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java @@ -25,13 +25,19 @@ package org.geysermc.geyser.inventory; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.CrafterInventoryTranslator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; +import org.jetbrains.annotations.Range; @Getter public class CrafterContainer extends Container { + private GeyserItemStack resultItem = GeyserItemStack.EMPTY; @Setter private boolean triggered = false; @@ -46,8 +52,36 @@ public class CrafterContainer extends Container { super(title, id, size, containerType, playerInventory); } + @Override + public GeyserItemStack getItem(int slot) { + if (slot == CrafterInventoryTranslator.JAVA_RESULT_SLOT) { + return this.resultItem; + } else if (isCraftingGrid(slot)) { + return super.getItem(slot); + } else { + return playerInventory.getItem(slot - CrafterInventoryTranslator.GRID_SIZE + InventoryTranslator.PLAYER_INVENTORY_OFFSET); + } + } + + @Override + public int getOffsetForHotbar(@Range(from = 0, to = 8) int slot) { + return playerInventory.getOffsetForHotbar(slot) - InventoryTranslator.PLAYER_INVENTORY_OFFSET + CrafterInventoryTranslator.GRID_SIZE; + } + + @Override + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot == CrafterInventoryTranslator.JAVA_RESULT_SLOT) { + // Result item probably won't be an item that needs to worry about net ID or lodestone compasses + this.resultItem = newItem; + } else if (isCraftingGrid(slot)) { + super.setItem(slot, newItem, session); + } else { + playerInventory.setItem(slot - CrafterInventoryTranslator.GRID_SIZE + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session); + } + } + public void setSlot(int slot, boolean enabled) { - if (slot < 0 || slot > 8) { + if (!isCraftingGrid(slot)) { GeyserImpl.getInstance().getLogger().warning("Crafter slot out of bounds: " + slot); return; } @@ -58,4 +92,8 @@ public class CrafterContainer extends Container { disabledSlotsMask = (short) (disabledSlotsMask | (1 << slot)); } } + + private static boolean isCraftingGrid(int slot) { + return slot >= 0 && slot <= 8; + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java index 23365e392..de0bd7300 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java @@ -25,12 +25,11 @@ package org.geysermc.geyser.inventory; +import lombok.Getter; import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantData; import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantOptionData; -import lombok.Getter; import org.geysermc.geyser.session.GeyserSession; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -44,13 +43,13 @@ public class GeyserEnchantOption { * is controlled by the server. * So, of course, we have to throw in some easter eggs. ;) */ - private static final List ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better", - "explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa", - "tim two zero three", "fast walk nether", "davchoo", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned", - "more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft", - "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this", - "stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out", - "xp heals tools", "dragon proxy waz here"); + private static final List ENCHANT_NAMES = List.of("tougher armor", "lukeeey", "fall better", + "explode less", "camo toy", "armor stab", "breathe better", "water walk", "rtm five one six", "oof ouch owie", + "enemy on fire", "spider sad", "aj ferguson", "redned", "more items thx", "fast tool", "give me block", + "less breaky break", "cube craft", "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", + "come here fish", "you are elsa", "xp heals tools", "tim two zero three", "dragon proxy waz here", + "stabby stab", "supreme mortal", "i like this", "avatar i guess", "more arrows", "in and out", + "fly finder seventeen", "fast walk nether", "davchoo", "onechris", "death bringer thirteen", "kastle"); @Getter private final int javaIndex; @@ -62,7 +61,6 @@ public class GeyserEnchantOption { private boolean hasChanged; private int xpCost = 0; - private int javaEnchantIndex = -1; private int bedrockEnchantIndex = -1; private int enchantLevel = -1; @@ -74,7 +72,7 @@ public class GeyserEnchantOption { this.hasChanged = false; return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, - javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId()); + bedrockEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(bedrockEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId()); } public boolean hasChanged() { @@ -88,10 +86,9 @@ public class GeyserEnchantOption { } } - public void setEnchantIndex(int javaEnchantIndex, int bedrockEnchantIndex) { - if (this.javaEnchantIndex != javaEnchantIndex) { + public void setEnchantIndex(int bedrockEnchantIndex) { + if (this.bedrockEnchantIndex != bedrockEnchantIndex) { hasChanged = true; - this.javaEnchantIndex = javaEnchantIndex; this.bedrockEnchantIndex = bedrockEnchantIndex; } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java b/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java index 442690d7d..743fbdc7e 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java @@ -26,7 +26,9 @@ package org.geysermc.geyser.inventory.item; import lombok.Getter; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.util.MinecraftKey; import java.util.Locale; @@ -72,21 +74,23 @@ public enum BannerPattern { SKULL("sku"), FLOWER("flo"), MOJANG("moj"), - PIGLIN("pig"); + PIGLIN("pig"), + FLOW("flw"), + GUSTER("gus"); private static final BannerPattern[] VALUES = values(); - private final String javaIdentifier; + private final Key javaIdentifier; private final String bedrockIdentifier; BannerPattern(String bedrockIdentifier) { - this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT); + this.javaIdentifier = MinecraftKey.key(this.name().toLowerCase(Locale.ROOT)); this.bedrockIdentifier = bedrockIdentifier; } - public static @Nullable BannerPattern getByJavaIdentifier(String javaIdentifier) { + public static @Nullable BannerPattern getByJavaIdentifier(Key key) { for (BannerPattern bannerPattern : VALUES) { - if (bannerPattern.javaIdentifier.equals(javaIdentifier)) { + if (bannerPattern.javaIdentifier.equals(key)) { return bannerPattern; } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java b/core/src/main/java/org/geysermc/geyser/inventory/item/BedrockEnchantment.java similarity index 50% rename from core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java rename to core/src/main/java/org/geysermc/geyser/inventory/item/BedrockEnchantment.java index 773de29b1..6d3fdbc27 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/BedrockEnchantment.java @@ -25,13 +25,11 @@ package org.geysermc.geyser.inventory.item; -import lombok.Getter; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Locale; -@Getter -public enum Enchantment { +public enum BedrockEnchantment { PROTECTION, FIRE_PROTECTION, FEATHER_FALLING, @@ -69,18 +67,21 @@ public enum Enchantment { PIERCING, QUICK_CHARGE, SOUL_SPEED, - SWIFT_SNEAK; + SWIFT_SNEAK, + WIND_BURST, + DENSITY, + BREACH; - private static final Enchantment[] VALUES = values(); + private static final BedrockEnchantment[] VALUES = values(); private final String javaIdentifier; - Enchantment() { + BedrockEnchantment() { this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); } - public static @Nullable Enchantment getByJavaIdentifier(String javaIdentifier) { - for (Enchantment enchantment : VALUES) { + public static @Nullable BedrockEnchantment getByJavaIdentifier(String javaIdentifier) { + for (BedrockEnchantment enchantment : VALUES) { if (enchantment.javaIdentifier.equals(javaIdentifier) || enchantment.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(javaIdentifier)) { return enchantment; } @@ -88,88 +89,10 @@ public enum Enchantment { return null; } - public static @Nullable Enchantment getByBedrockId(int bedrockId) { + public static @Nullable BedrockEnchantment getByBedrockId(int bedrockId) { if (bedrockId >= 0 && bedrockId < VALUES.length) { return VALUES[bedrockId]; } return null; } - - /** - * Enchantments classified by their Java index - */ - public enum JavaEnchantment { - PROTECTION, - FIRE_PROTECTION, - FEATHER_FALLING, - BLAST_PROTECTION, - PROJECTILE_PROTECTION, - RESPIRATION, - AQUA_AFFINITY, - THORNS, - DEPTH_STRIDER, - FROST_WALKER, - BINDING_CURSE, - SOUL_SPEED, - SWIFT_SNEAK, - SHARPNESS, - SMITE, - BANE_OF_ARTHROPODS, - KNOCKBACK, - FIRE_ASPECT, - LOOTING, - SWEEPING_EDGE, - EFFICIENCY, - SILK_TOUCH, - UNBREAKING, - FORTUNE, - POWER, - PUNCH, - FLAME, - INFINITY, - LUCK_OF_THE_SEA, - LURE, - LOYALTY, - IMPALING, - RIPTIDE, - CHANNELING, - MULTISHOT, - QUICK_CHARGE, - PIERCING, - DENSITY, - BREACH, - WIND_BURST, - MENDING, - VANISHING_CURSE; - - private static final JavaEnchantment[] VALUES = JavaEnchantment.values(); - - public static JavaEnchantment of(int index) { - return VALUES[index]; - } - - /** - * A list of all enchantment Java identifiers for use with command suggestions. - */ - public static final String[] ALL_JAVA_IDENTIFIERS; - - public static @Nullable JavaEnchantment getByJavaIdentifier(String javaIdentifier) { - if (!javaIdentifier.startsWith("minecraft:")) { - javaIdentifier = "minecraft:" + javaIdentifier; - } - for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { - if (ALL_JAVA_IDENTIFIERS[i].equalsIgnoreCase(javaIdentifier)) { - return VALUES[i]; - } - } - return null; - } - - static { - ALL_JAVA_IDENTIFIERS = new String[VALUES.length]; - for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { - ALL_JAVA_IDENTIFIERS[i] = "minecraft:" + VALUES[i].name().toLowerCase(Locale.ENGLISH); - } - } - } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java b/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java index 86c19de80..129c365a9 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java @@ -34,63 +34,83 @@ import java.util.Locale; @Getter public enum Potion { - WATER(0), - MUNDANE(1), - THICK(3), - AWKWARD(4), - NIGHT_VISION(5), - LONG_NIGHT_VISION(6), - INVISIBILITY(7), - LONG_INVISIBILITY(8), - LEAPING(9), - LONG_LEAPING(10), - STRONG_LEAPING(11), - FIRE_RESISTANCE(12), - LONG_FIRE_RESISTANCE(13), - SWIFTNESS(14), - LONG_SWIFTNESS(15), - STRONG_SWIFTNESS(16), - SLOWNESS(17), - LONG_SLOWNESS(18), - STRONG_SLOWNESS(42), - TURTLE_MASTER(37), - LONG_TURTLE_MASTER(38), - STRONG_TURTLE_MASTER(39), - WATER_BREATHING(19), - LONG_WATER_BREATHING(20), - HEALING(21), - STRONG_HEALING(22), - HARMING(23), - STRONG_HARMING(24), - POISON(25), - LONG_POISON(26), - STRONG_POISON(27), - REGENERATION(28), - LONG_REGENERATION(29), - STRONG_REGENERATION(30), - STRENGTH(31), - LONG_STRENGTH(32), - STRONG_STRENGTH(33), - WEAKNESS(34), - LONG_WEAKNESS(35), - LUCK(2), //does not exist - SLOW_FALLING(40), - LONG_SLOW_FALLING(41); + WATER(0, ArrowParticleColors.NONE), + MUNDANE(1, ArrowParticleColors.NONE), // 2 is extended? + THICK(3, ArrowParticleColors.NONE), + AWKWARD(4, ArrowParticleColors.NONE), + NIGHT_VISION(5, ArrowParticleColors.NIGHT_VISION), + LONG_NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION), + INVISIBILITY(7, ArrowParticleColors.INVISIBILITY), + LONG_INVISIBILITY(8, ArrowParticleColors.INVISIBILITY), + LEAPING(9, ArrowParticleColors.LEAPING), + LONG_LEAPING(10, ArrowParticleColors.LEAPING), + STRONG_LEAPING(11, ArrowParticleColors.LEAPING), + FIRE_RESISTANCE(12, ArrowParticleColors.FIRE_RESISTANCE), + LONG_FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE), + SWIFTNESS(14, ArrowParticleColors.SWIFTNESS), + LONG_SWIFTNESS(15, ArrowParticleColors.SWIFTNESS), + STRONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS), + SLOWNESS(17, ArrowParticleColors.SLOWNESS), + LONG_SLOWNESS(18, ArrowParticleColors.SLOWNESS), + STRONG_SLOWNESS(42, ArrowParticleColors.SLOWNESS), + TURTLE_MASTER(37, ArrowParticleColors.TURTLE_MASTER), + LONG_TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER), + STRONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER), + WATER_BREATHING(19, ArrowParticleColors.WATER_BREATHING), + LONG_WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING), + HEALING(21, ArrowParticleColors.HEALING), + STRONG_HEALING(22, ArrowParticleColors.HEALING), + HARMING(23, ArrowParticleColors.HARMING), + STRONG_HARMING(24, ArrowParticleColors.HARMING), + POISON(25, ArrowParticleColors.POISON), + LONG_POISON(26, ArrowParticleColors.POISON), + STRONG_POISON(27, ArrowParticleColors.POISON), + REGENERATION(28, ArrowParticleColors.REGENERATION), + LONG_REGENERATION(29, ArrowParticleColors.REGENERATION), + STRONG_REGENERATION(30, ArrowParticleColors.REGENERATION), + STRENGTH(31, ArrowParticleColors.STRENGTH), + LONG_STRENGTH(32, ArrowParticleColors.STRENGTH), + STRONG_STRENGTH(33, ArrowParticleColors.STRENGTH), + WEAKNESS(34, ArrowParticleColors.WEAKNESS), + LONG_WEAKNESS(35, ArrowParticleColors.WEAKNESS), + LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock + SLOW_FALLING(40, ArrowParticleColors.SLOW_FALLING), + LONG_SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), + WIND_CHARGING(43, ArrowParticleColors.WIND_CHARGING), + WEAVING(44, ArrowParticleColors.WEAVING), + OOZING(45, ArrowParticleColors.OOZING), + INFESTATION(46, ArrowParticleColors.INFESTATION); public static final Potion[] VALUES = values(); private final String javaIdentifier; private final short bedrockId; + private final int javaColor; - Potion(int bedrockId) { + Potion(int bedrockId, int javaColor) { this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); this.bedrockId = (short) bedrockId; + this.javaColor = javaColor; + } + + public int tippedArrowId() { + // +1 likely to offset 0 as nothing? + return this.bedrockId + 1; } public PotionContents toComponent() { return new PotionContents(this.ordinal(), -1, Collections.emptyList()); } + public static Potion getByJavaIdentifier(String javaIdentifier) { + for (Potion potion : VALUES) { + if (potion.javaIdentifier.equals(javaIdentifier)) { + return potion; + } + } + return null; + } + public static @Nullable Potion getByJavaId(int javaId) { if (javaId >= 0 && javaId < VALUES.length) { return VALUES[javaId]; @@ -106,4 +126,44 @@ public enum Potion { } return null; } + + public static @Nullable Potion getByTippedArrowDamage(int bedrockId) { + return getByBedrockId(bedrockId - 1); + } + + public static byte toTippedArrowId(int javaParticleColor) { + for (Potion potion : VALUES) { + if (potion.javaColor == javaParticleColor) { + return (byte) (potion.bedrockId + 1); + } + } + return (byte) 0; + } + + /** + * For tipped arrow usage + */ + private static final class ArrowParticleColors { + static final int NONE = 1; + static final int NIGHT_VISION = 2039713; + static final int INVISIBILITY = 8356754; + static final int LEAPING = 2293580; + static final int FIRE_RESISTANCE = 14981690; + static final int SWIFTNESS = 8171462; + static final int SLOWNESS = 5926017; + static final int TURTLE_MASTER = 7691106; + static final int WATER_BREATHING = 3035801; + static final int HEALING = 16262179; + static final int HARMING = 4393481; + static final int POISON = 5149489; + static final int REGENERATION = 13458603; + static final int STRENGTH = 9643043; + static final int WEAKNESS = 4738376; + static final int LUCK = 3381504; + static final int SLOW_FALLING = 16773073; + static final int WIND_CHARGING = 12438015; + static final int WEAVING = 7891290; + static final int OOZING = 10092451; + static final int INFESTATION = 9214860; + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java b/core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java deleted file mode 100644 index b849e07e2..000000000 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.inventory.item; - -import lombok.Getter; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.Locale; - -/** - * Potion identifiers and their respective Bedrock IDs used with arrows. - * See here - */ -@Getter -public enum TippedArrowPotion { - WATER(-1, ArrowParticleColors.NONE), // Guessing this based off of the Potion enum. TODO merge? - MUNDANE(2, ArrowParticleColors.NONE), // 3 is extended? - THICK(4, ArrowParticleColors.NONE), - AWKWARD(5, ArrowParticleColors.NONE), - NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION), - LONG_NIGHT_VISION(7, ArrowParticleColors.NIGHT_VISION), - INVISIBILITY(8, ArrowParticleColors.INVISIBILITY), - LONG_INVISIBILITY(9, ArrowParticleColors.INVISIBILITY), - LEAPING(10, ArrowParticleColors.LEAPING), - LONG_LEAPING(11, ArrowParticleColors.LEAPING), - STRONG_LEAPING(12, ArrowParticleColors.LEAPING), - FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE), - LONG_FIRE_RESISTANCE(14, ArrowParticleColors.FIRE_RESISTANCE), - SWIFTNESS(15, ArrowParticleColors.SWIFTNESS), - LONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS), - STRONG_SWIFTNESS(17, ArrowParticleColors.SWIFTNESS), - SLOWNESS(18, ArrowParticleColors.SLOWNESS), - LONG_SLOWNESS(19, ArrowParticleColors.SLOWNESS), - STRONG_SLOWNESS(43, ArrowParticleColors.SLOWNESS), - WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING), - LONG_WATER_BREATHING(21, ArrowParticleColors.WATER_BREATHING), - HEALING(22, ArrowParticleColors.HEALING), - STRONG_HEALING(23, ArrowParticleColors.HEALING), - HARMING(24, ArrowParticleColors.HARMING), - STRONG_HARMING(25, ArrowParticleColors.HARMING), - POISON(26, ArrowParticleColors.POISON), - LONG_POISON(27, ArrowParticleColors.POISON), - STRONG_POISON(28, ArrowParticleColors.POISON), - REGENERATION(29, ArrowParticleColors.REGENERATION), - LONG_REGENERATION(30, ArrowParticleColors.REGENERATION), - STRONG_REGENERATION(31, ArrowParticleColors.REGENERATION), - STRENGTH(32, ArrowParticleColors.STRENGTH), - LONG_STRENGTH(33, ArrowParticleColors.STRENGTH), - STRONG_STRENGTH(34, ArrowParticleColors.STRENGTH), - WEAKNESS(35, ArrowParticleColors.WEAKNESS), - LONG_WEAKNESS(36, ArrowParticleColors.WEAKNESS), - LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock - TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER), - LONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER), - STRONG_TURTLE_MASTER(40, ArrowParticleColors.TURTLE_MASTER), - SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), - LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); - - private static final TippedArrowPotion[] VALUES = values(); - - private final String javaIdentifier; - private final short bedrockId; - /** - * The Java color associated with this ID. - * Used for looking up Java arrow color entity metadata as Bedrock potion IDs, which is what is used for entities in Bedrock - */ - private final int javaColor; - - TippedArrowPotion(int bedrockId, ArrowParticleColors arrowParticleColor) { - this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); - this.bedrockId = (short) bedrockId; - this.javaColor = arrowParticleColor.getColor(); - } - - public static @Nullable TippedArrowPotion of(int id) { - if (id >= 0 && id < VALUES.length) { - return VALUES[id]; - } - return null; - } - - public static @Nullable TippedArrowPotion getByBedrockId(int bedrockId) { - for (TippedArrowPotion potion : VALUES) { - if (potion.bedrockId == bedrockId) { - return potion; - } - } - return null; - } - - /** - * @param color the potion color to look up - * @return the tipped arrow potion that most closely resembles that color. - */ - public static @Nullable TippedArrowPotion getByJavaColor(int color) { - for (TippedArrowPotion potion : VALUES) { - if (potion.javaColor == color) { - return potion; - } - } - return null; - } - - private enum ArrowParticleColors { - NONE(-1), - NIGHT_VISION(2039713), - INVISIBILITY(8356754), - LEAPING(2293580), - FIRE_RESISTANCE(14981690), - SWIFTNESS(8171462), - SLOWNESS(5926017), - TURTLE_MASTER(7691106), - WATER_BREATHING(3035801), - HEALING(16262179), - HARMING(4393481), - POISON(5149489), - REGENERATION(13458603), - STRENGTH(9643043), - WEAKNESS(4738376), - LUCK(3381504), - SLOW_FALLING(16773073); - - @Getter - private final int color; - - ArrowParticleColors(int color) { - this.color = color; - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java index 9d98e9fb3..8b7fa9522 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.inventory.recipe; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; + /** * A more compact version of {@link org.geysermc.mcprotocollib.protocol.data.game.recipe.Recipe}. */ @@ -33,4 +36,7 @@ public interface GeyserRecipe { * Whether the recipe is flexible or not in which items can be placed where. */ boolean isShaped(); + + @Nullable + ItemStack result(); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java index 9d9dbe0db..8289813a4 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java @@ -47,7 +47,7 @@ public final class TrimRecipe { public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates"); public static TrimMaterial readTrimMaterial(GeyserSession session, RegistryEntry entry) { - String key = stripMinecraftNamespace(entry.getId()); + String key = entry.getId().asMinimalString(); // Color is used when hovering over the item // Find the nearest legacy color from the RGB Java gives us to work with @@ -67,7 +67,7 @@ public final class TrimRecipe { } public static TrimPattern readTrimPattern(GeyserSession session, RegistryEntry entry) { - String key = stripMinecraftNamespace(entry.getId()); + String key = entry.getId().asMinimalString(); String itemIdentifier = entry.getData().getString("template_item"); ItemMapping itemMapping = session.getItemMappings().getMapping(itemIdentifier); @@ -78,19 +78,6 @@ public final class TrimRecipe { return new TrimPattern(itemMapping.getBedrockIdentifier(), key); } - // TODO find a good place for a stripNamespace util method - private static String stripMinecraftNamespace(String identifier) { - int i = identifier.indexOf(':'); - if (i >= 0) { - String namespace = identifier.substring(0, i); - // Only strip minecraft namespace - if (namespace.equals("minecraft")) { - return identifier.substring(i + 1); - } - } - return identifier; - } - private TrimRecipe() { //no-op } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index d6a0d922b..c3ac73372 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.inventory.updater; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntMaps; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.kyori.adventure.text.Component; import org.cloudburstmc.nbt.NbtMap; @@ -38,10 +37,9 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; +import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.item.Items; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -307,22 +305,22 @@ public class AnvilInventoryUpdater extends InventoryUpdater { */ private int calcMergeEnchantmentCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material, boolean bedrock) { boolean hasCompatible = false; - Object2IntMap combinedEnchantments = getEnchantments(input); + Object2IntMap combinedEnchantments = getEnchantments(session, input); int cost = 0; - for (Object2IntMap.Entry entry : getEnchantments(material).object2IntEntrySet()) { - JavaEnchantment enchantment = entry.getKey(); - EnchantmentData data = Registries.ENCHANTMENTS.get(enchantment); - if (data == null) { - GeyserImpl.getInstance().getLogger().debug("Java enchantment not in registry: " + enchantment); - continue; - } + for (Object2IntMap.Entry entry : getEnchantments(session, material).object2IntEntrySet()) { + Enchantment enchantment = entry.getKey(); - boolean canApply = isEnchantedBook(input) || data.validItems().contains(input.getJavaId()); - for (JavaEnchantment incompatible : data.incompatibleEnchantments()) { - if (combinedEnchantments.containsKey(incompatible)) { - canApply = false; - if (!bedrock) { - cost++; + boolean canApply = isEnchantedBook(input) || session.getTagCache().is(enchantment.supportedItems(), input); + var exclusiveSet = enchantment.exclusiveSet(); + if (exclusiveSet != null) { + int[] incompatibleEnchantments = session.getTagCache().get(exclusiveSet); + for (int i : incompatibleEnchantments) { + Enchantment incompatible = session.getRegistryCache().enchantments().byId(i); + if (combinedEnchantments.containsKey(incompatible)) { + canApply = false; + if (!bedrock) { + cost++; + } } } } @@ -334,12 +332,12 @@ public class AnvilInventoryUpdater extends InventoryUpdater { newLevel++; } newLevel = Math.max(currentLevel, newLevel); - if (newLevel > data.maxLevel()) { - newLevel = data.maxLevel(); + if (newLevel > enchantment.maxLevel()) { + newLevel = enchantment.maxLevel(); } combinedEnchantments.put(enchantment, newLevel); - int rarityMultiplier = data.rarityMultiplier(); + int rarityMultiplier = enchantment.anvilCost(); if (isEnchantedBook(material) && rarityMultiplier > 1) { rarityMultiplier /= 2; } @@ -347,11 +345,11 @@ public class AnvilInventoryUpdater extends InventoryUpdater { if (newLevel > currentLevel) { hasCompatible = true; } - if (enchantment == JavaEnchantment.IMPALING) { + if (enchantment.bedrockEnchantment() == BedrockEnchantment.IMPALING) { // Multiplier is halved on Bedrock for some reason rarityMultiplier /= 2; - } else if (enchantment == JavaEnchantment.SWEEPING_EDGE) { - // Doesn't exist on Bedrock + } else if (enchantment.bedrockEnchantment() == null) { + // Whatever this is, doesn't exist on Bedrock rarityMultiplier = 0; } cost += rarityMultiplier * (newLevel - currentLevel); @@ -368,7 +366,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { return cost; } - private Object2IntMap getEnchantments(GeyserItemStack itemStack) { + private Object2IntMap getEnchantments(GeyserSession session, GeyserItemStack itemStack) { ItemEnchantments enchantmentComponent; if (isEnchantedBook(itemStack)) { enchantmentComponent = itemStack.getComponent(DataComponentType.STORED_ENCHANTMENTS); @@ -376,9 +374,9 @@ public class AnvilInventoryUpdater extends InventoryUpdater { enchantmentComponent = itemStack.getComponent(DataComponentType.ENCHANTMENTS); } if (enchantmentComponent != null) { - Object2IntMap enchantments = new Object2IntOpenHashMap<>(); + Object2IntMap enchantments = new Object2IntOpenHashMap<>(); for (Map.Entry entry : enchantmentComponent.getEnchantments().entrySet()) { - JavaEnchantment enchantment = JavaEnchantment.of(entry.getKey()); + Enchantment enchantment = session.getRegistryCache().enchantments().byId(entry.getKey()); if (enchantment == null) { GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + entry.getKey()); continue; diff --git a/core/src/main/java/org/geysermc/geyser/item/Items.java b/core/src/main/java/org/geysermc/geyser/item/Items.java index 03f044068..1ddd14982 100644 --- a/core/src/main/java/org/geysermc/geyser/item/Items.java +++ b/core/src/main/java/org/geysermc/geyser/item/Items.java @@ -834,8 +834,9 @@ public final class Items { public static final Item TURTLE_HELMET = register(new ArmorItem("turtle_helmet", ArmorMaterial.TURTLE, builder().stackSize(1).maxDamage(275))); public static final Item TURTLE_SCUTE = register(new Item("turtle_scute", builder())); public static final Item ARMADILLO_SCUTE = register(new Item("armadillo_scute", builder())); - public static final Item WOLF_ARMOR = register(new ArmorItem("wolf_armor", ArmorMaterial.ARMADILLO, builder().stackSize(1).maxDamage(64))); + public static final Item WOLF_ARMOR = register(new WolfArmorItem("wolf_armor", ArmorMaterial.ARMADILLO, builder().stackSize(1).maxDamage(64))); public static final Item FLINT_AND_STEEL = register(new Item("flint_and_steel", builder().stackSize(1).maxDamage(64))); + public static final Item BOWL = register(new Item("bowl", builder())); public static final Item APPLE = register(new Item("apple", builder())); public static final Item BOW = register(new Item("bow", builder().stackSize(1).maxDamage(384))); public static final Item ARROW = register(new ArrowItem("arrow", builder())); @@ -885,7 +886,6 @@ public final class Items { public static final Item NETHERITE_AXE = register(new TieredItem("netherite_axe", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031).attackDamage(10.0))); public static final Item NETHERITE_HOE = register(new TieredItem("netherite_hoe", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031).attackDamage(1.0))); public static final Item STICK = register(new Item("stick", builder())); - public static final Item BOWL = register(new Item("bowl", builder())); public static final Item MUSHROOM_STEW = register(new Item("mushroom_stew", builder().stackSize(1))); public static final Item STRING = register(new BlockItem("string", builder(), Blocks.TRIPWIRE)); public static final Item FEATHER = register(new Item("feather", builder())); @@ -1042,7 +1042,7 @@ public final class Items { public static final Item BLAZE_POWDER = register(new Item("blaze_powder", builder())); public static final Item MAGMA_CREAM = register(new Item("magma_cream", builder())); public static final Item BREWING_STAND = register(new BlockItem(builder(), Blocks.BREWING_STAND)); - public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.LAVA_CAULDRON, Blocks.POWDER_SNOW_CAULDRON, Blocks.WATER_CAULDRON)); + public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.WATER_CAULDRON, Blocks.LAVA_CAULDRON, Blocks.POWDER_SNOW_CAULDRON)); public static final Item ENDER_EYE = register(new Item("ender_eye", builder())); public static final Item GLISTERING_MELON_SLICE = register(new Item("glistering_melon_slice", builder())); public static final Item ARMADILLO_SPAWN_EGG = register(new SpawnEggItem("armadillo_spawn_egg", builder())); @@ -1130,7 +1130,7 @@ public final class Items { public static final Item WIND_CHARGE = register(new Item("wind_charge", builder())); public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1))); public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16))); - public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(250))); + public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(500))); public static final Item ITEM_FRAME = register(new Item("item_frame", builder())); public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder())); public static final Item FLOWER_POT = register(new BlockItem(builder(), Blocks.FLOWER_POT)); @@ -1209,6 +1209,8 @@ public final class Items { public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1))); public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1))); public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1))); + public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1))); + public static final Item MUSIC_DISC_CREATOR_MUSIC_BOX = register(new Item("music_disc_creator_music_box", builder().stackSize(1))); public static final Item MUSIC_DISC_FAR = register(new Item("music_disc_far", builder().stackSize(1))); public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1))); public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1))); @@ -1221,6 +1223,7 @@ public final class Items { public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1))); public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1))); public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1))); + public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", builder().stackSize(1))); public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder())); public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250).attackDamage(9.0))); public static final Item PHANTOM_MEMBRANE = register(new Item("phantom_membrane", builder())); @@ -1365,7 +1368,7 @@ public final class Items { public static final Item TRIAL_KEY = register(new Item("trial_key", builder())); public static final Item OMINOUS_TRIAL_KEY = register(new Item("ominous_trial_key", builder())); public static final Item VAULT = register(new BlockItem(builder(), Blocks.VAULT)); - public static final Item OMINOUS_BOTTLE = register(new Item("ominous_bottle", builder())); + public static final Item OMINOUS_BOTTLE = register(new OminousBottleItem("ominous_bottle", builder())); public static final Item BREEZE_ROD = register(new Item("breeze_rod", builder())); public static final int AIR_ID = AIR.javaId(); diff --git a/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java new file mode 100644 index 000000000..c5c0d2611 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java @@ -0,0 +1,80 @@ +/* + * 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.item.enchantment; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; +import org.geysermc.geyser.session.cache.tags.EnchantmentTag; +import org.geysermc.geyser.session.cache.tags.ItemTag; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @param description only populated if {@link #bedrockEnchantment()} is not null. + * @param anvilCost also as a rarity multiplier + */ +public record Enchantment(String identifier, + Set effects, + ItemTag supportedItems, + int maxLevel, + String description, + int anvilCost, + @Nullable EnchantmentTag exclusiveSet, + @Nullable BedrockEnchantment bedrockEnchantment) { + + // Implementation note: I have a feeling the tags can be a list of items, because in vanilla they're HolderSet classes. + // I'm not sure how that's wired over the network, so we'll put it off. + public static Enchantment read(RegistryEntry entry) { + NbtMap data = entry.getData(); + Set effects = readEnchantmentComponents(data.getCompound("effects")); + String supportedItems = data.getString("supported_items").substring(1); // Remove '#' at beginning that indicates tag + int maxLevel = data.getInt("max_level"); + int anvilCost = data.getInt("anvil_cost"); + String exclusiveSet = data.getString("exclusive_set", null); + EnchantmentTag exclusiveSetTag = exclusiveSet == null ? null : EnchantmentTag.ALL_ENCHANTMENT_TAGS.get(MinecraftKey.key(exclusiveSet.substring(1))); + BedrockEnchantment bedrockEnchantment = BedrockEnchantment.getByJavaIdentifier(entry.getId().asString()); + String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null; + + return new Enchantment(entry.getId().asString(), effects, ItemTag.ALL_ITEM_TAGS.get(MinecraftKey.key(supportedItems)), maxLevel, + description, anvilCost, exclusiveSetTag, bedrockEnchantment); + } + + private static Set readEnchantmentComponents(NbtMap effects) { + Set components = new HashSet<>(); + for (Map.Entry entry : effects.entrySet()) { + switch (entry.getKey()) { + case "minecraft:prevent_armor_change" -> components.add(EnchantmentComponent.PREVENT_ARMOR_CHANGE); + } + } + return Set.copyOf(components); // Also ensures any empty sets are consolidated + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/enchantment/EnchantmentComponent.java b/core/src/main/java/org/geysermc/geyser/item/enchantment/EnchantmentComponent.java new file mode 100644 index 000000000..66d110f98 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/enchantment/EnchantmentComponent.java @@ -0,0 +1,33 @@ +/* + * 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.item.enchantment; + +public class EnchantmentComponent { + /** + * Singleton with no additional data + */ + public static final EnchantmentComponent PREVENT_ARMOR_CHANGE = new EnchantmentComponent(); +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java index 84c3b1a39..4e4f1830e 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java @@ -28,15 +28,13 @@ package org.geysermc.geyser.item.type; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents; -import java.util.Collections; - public class ArrowItem extends Item { public ArrowItem(String javaIdentifier, Builder builder) { super(javaIdentifier, builder); @@ -44,13 +42,18 @@ public class ArrowItem extends Item { @Override public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { - TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); + Potion potion = Potion.getByTippedArrowDamage(itemData.getDamage()); GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); - if (tippedArrowPotion != null) { + if (potion != null) { itemStack = Items.TIPPED_ARROW.newItemStack(itemStack.getAmount(), itemStack.getComponents()); - PotionContents contents = new PotionContents(tippedArrowPotion.ordinal(), -1, Collections.emptyList()); + PotionContents contents = potion.toComponent(); itemStack.getOrCreateComponents().put(DataComponentType.POTION_CONTENTS, contents); } return itemStack; } + + @Override + public boolean ignoreDamage() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java b/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java index b53843882..6ec0da8ed 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.item.type; import it.unimi.dsi.fastutil.Pair; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; @@ -40,8 +41,8 @@ import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.JavaRegistry; import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.Holder; -import org.geysermc.mcprotocollib.protocol.data.game.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -60,9 +61,6 @@ public class BannerItem extends BlockItem { */ private static final List> OMINOUS_BANNER_PATTERN; - // TODO fix - we somehow need to be able to get the sessions banner pattern registry, which we don't have where we need this :/ - private static final int[] ominousBannerPattern = new int[] { 21, 29, 30, 1, 34, 15, 3, 1 }; - static { // Construct what an ominous banner is supposed to look like OMINOUS_BANNER_PATTERN = List.of( @@ -109,7 +107,7 @@ public class BannerItem extends BlockItem { if (color != pair.right()) { return false; } - String id = Identifier.formalize(patternLayer.getString("pattern")); // Ouch + Key id = MinecraftKey.key(patternLayer.getString("pattern")); // Ouch BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(id); if (bannerPattern != pair.left()) { return false; @@ -146,8 +144,8 @@ public class BannerItem extends BlockItem { } else { List patternList = new ArrayList<>(patterns.size()); for (BannerPatternLayer patternLayer : patterns) { - patternLayer.getPattern().ifId(holder -> { - BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(holder.id()); + patternLayer.getPattern().ifId(id -> { + BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(id); if (bannerPattern != null) { NbtMap tag = NbtMap.builder() .putString("Pattern", bannerPattern.getBedrockIdentifier()) @@ -169,7 +167,7 @@ public class BannerItem extends BlockItem { */ private static NbtMap getBedrockBannerPattern(NbtMap pattern) { // ViaVersion 1.20.4 -> 1.20.5 can send without the namespace - BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(Identifier.formalize(pattern.getString("pattern"))); + BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(MinecraftKey.key(pattern.getString("pattern"))); DyeColor dyeColor = DyeColor.getByJavaIdentifier(pattern.getString("color")); if (bannerPattern == null || dyeColor == null) { return null; @@ -215,20 +213,22 @@ public class BannerItem extends BlockItem { } @Override - public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { - super.translateNbtToJava(bedrockTag, components, mapping); + public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { + super.translateNbtToJava(session, bedrockTag, components, mapping); if (bedrockTag.getInt("Type") == 1) { // Ominous banner pattern List patternLayers = new ArrayList<>(); - for (int i = 0; i < ominousBannerPattern.length; i++) { - patternLayers.add(new BannerPatternLayer(Holder.ofId(ominousBannerPattern[i]), OMINOUS_BANNER_PATTERN.get(i).right().ordinal())); + for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) { + var pair = OMINOUS_BANNER_PATTERN.get(i); + patternLayers.add(new BannerPatternLayer(Holder.ofId(session.getRegistryCache().bannerPatterns().byValue(pair.left())), + pair.right().ordinal())); } components.put(DataComponentType.BANNER_PATTERNS, patternLayers); components.put(DataComponentType.HIDE_ADDITIONAL_TOOLTIP, Unit.INSTANCE); components.put(DataComponentType.ITEM_NAME, Component - .translatable("block.minecraft.ominous_banner") // thank god this works + .translatable("block.minecraft.ominous_banner") .style(Style.style(TextColor.color(16755200))) ); } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/DyeableArmorItem.java b/core/src/main/java/org/geysermc/geyser/item/type/DyeableArmorItem.java index b2dbb95e5..8c63eaeb0 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/DyeableArmorItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/DyeableArmorItem.java @@ -29,9 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.ArmorMaterial; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.item.BedrockItemBuilder; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DyedItemColor; public class DyeableArmorItem extends ArmorItem { public DyeableArmorItem(String javaIdentifier, ArmorMaterial material, Builder builder) { @@ -44,9 +42,6 @@ public class DyeableArmorItem extends ArmorItem { // Note that this is handled as of 1.20.5 in the ItemColors class. // But horse leather armor and body leather armor are now both armor items. So it works! - DyedItemColor dyedItemColor = components.get(DataComponentType.DYED_COLOR); - if (dyedItemColor != null) { - builder.putInt("customColor", dyedItemColor.getRgb()); - } + translateDyedColor(components, builder); } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/EnchantedBookItem.java b/core/src/main/java/org/geysermc/geyser/item/type/EnchantedBookItem.java index 98e98b4b8..8b0f3e22e 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/EnchantedBookItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/EnchantedBookItem.java @@ -31,7 +31,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; +import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.item.BedrockItemBuilder; @@ -69,8 +70,8 @@ public class EnchantedBookItem extends Item { } @Override - public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { - super.translateNbtToJava(bedrockTag, components, mapping); + public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { + super.translateNbtToJava(session, bedrockTag, components, mapping); List enchantmentTag = bedrockTag.getList("ench", NbtType.COMPOUND); if (enchantmentTag != null) { @@ -78,11 +79,16 @@ public class EnchantedBookItem extends Item { for (NbtMap bedrockEnchantment : enchantmentTag) { short bedrockId = bedrockEnchantment.getShort("id"); - Enchantment enchantment = Enchantment.getByBedrockId(bedrockId); + BedrockEnchantment enchantment = BedrockEnchantment.getByBedrockId(bedrockId); if (enchantment != null) { - int level = bedrockEnchantment.getShort("lvl", (short) 1); - // TODO - javaEnchantments.put(Enchantment.JavaEnchantment.valueOf(enchantment.name()).ordinal(), level); + List enchantments = session.getRegistryCache().enchantments().values(); + for (int i = 0; i < enchantments.size(); i++) { + if (enchantments.get(i).bedrockEnchantment() == enchantment) { + int level = bedrockEnchantment.getShort("lvl", (short) 1); + javaEnchantments.put(i, level); + break; + } + } } else { GeyserImpl.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java index 70a04b863..e571a796a 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java @@ -51,6 +51,7 @@ public class FilledMapItem extends MapItem { switch (mapColor) { case 3830373 -> builder.damage(3); // Ocean Monument case 5393476 -> builder.damage(4); // Woodland explorer + case 12741452 -> builder.damage(14); // Trial Chamber } } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java index c70467b4c..9c637afde 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java @@ -70,8 +70,8 @@ public class FireworkRocketItem extends Item { } @Override - public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { - super.translateNbtToJava(bedrockTag, components, mapping); + public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { + super.translateNbtToJava(session, bedrockTag, components, mapping); NbtMap fireworksTag = bedrockTag.getCompound("Fireworks"); if (!fireworksTag.isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FireworkStarItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FireworkStarItem.java index 18234975d..5ac0f475c 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FireworkStarItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FireworkStarItem.java @@ -78,8 +78,8 @@ public class FireworkStarItem extends Item { } @Override - public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { - super.translateNbtToJava(bedrockTag, components, mapping); + public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { + super.translateNbtToJava(session, bedrockTag, components, mapping); NbtMap explosion = bedrockTag.getCompound("FireworksItem"); if (!explosion.isEmpty()) { @@ -90,4 +90,9 @@ public class FireworkStarItem extends Item { components.put(DataComponentType.FIREWORK_EXPLOSION, newExplosion); } } + + @Override + public boolean ignoreDamage() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java b/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java index cd21c0b6e..d0e85ec52 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java @@ -62,4 +62,9 @@ public class GoatHornItem extends Item { return itemStack; } + + @Override + public boolean ignoreDamage() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 1ec410eaf..362b760c7 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -33,8 +33,9 @@ import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -44,9 +45,10 @@ import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.item.BedrockItemBuilder; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.mcprotocollib.protocol.data.game.Identifier; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DyedItemColor; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; import java.util.ArrayList; @@ -63,7 +65,7 @@ public class Item { private final int maxDamage; public Item(String javaIdentifier, Builder builder) { - this.javaIdentifier = Identifier.formalize(javaIdentifier).intern(); + this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern(); this.stackSize = builder.stackSize; this.maxDamage = builder.maxDamage; this.attackDamage = builder.attackDamage; @@ -123,7 +125,7 @@ public class Item { */ public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) { List loreComponents = components.get(DataComponentType.LORE); - if (loreComponents != null) { + if (loreComponents != null && components.get(DataComponentType.HIDE_TOOLTIP) == null) { List lore = builder.getOrCreateLore(); for (Component loreComponent : loreComponents) { lore.add(MessageTranslator.convertMessage(loreComponent, session.locale())); @@ -170,7 +172,7 @@ public class Item { * * Therefore, if translation cannot be achieved for a certain item, it is not necessarily bad. */ - public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { + public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) { // TODO see if any items from the creative menu need this // CompoundTag displayTag = tag.get("display"); // if (displayTag != null) { @@ -188,71 +190,25 @@ public class Item { // } // displayTag.put(new ListTag("Lore", lore)); // } -// } - - // TODO no creative item should have enchantments *except* enchanted books -// List enchantmentTag = bedrockTag.getList("ench", NbtType.COMPOUND); -// if (enchantmentTag != null) { -// List enchantments = new ArrayList<>(); -// for (Tag value : enchantmentTag.getValue()) { -// if (!(value instanceof CompoundTag tagValue)) -// continue; -// -// ShortTag bedrockId = tagValue.get("id"); -// if (bedrockId == null) continue; -// -// Enchantment enchantment = Enchantment.getByBedrockId(bedrockId.getValue()); -// if (enchantment != null) { -// CompoundTag javaTag = new CompoundTag(""); -// Map javaValue = javaTag.getValue(); -// javaValue.put("id", new StringTag("id", enchantment.getJavaIdentifier())); -// ShortTag levelTag = tagValue.get("lvl"); -// javaValue.put("lvl", new IntTag("lvl", levelTag != null ? levelTag.getValue() : 1)); -// javaTag.setValue(javaValue); -// -// enchantments.add(javaTag); -// } else { -// GeyserImpl.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); -// } -// } -// if (!enchantments.isEmpty()) { -// if ((this instanceof EnchantedBookItem)) { -// bedrockTag.put(new ListTag("StoredEnchantments", enchantments)); -// components.put(DataComponentType.STORED_ENCHANTMENTS, enchantments); -// } else { -// components.put(DataComponentType.ENCHANTMENTS, enchantments); -// } -// } // } } - /** - * This is a map from Java-only enchantments to their translation keys so that we can - * map these enchantments to Bedrock clients, since they don't actually exist there. - */ - private static final Map ENCHANTMENT_TRANSLATION_KEYS = Map.of( - Enchantment.JavaEnchantment.SWEEPING_EDGE, "enchantment.minecraft.sweeping", - Enchantment.JavaEnchantment.DENSITY, "enchantment.minecraft.density", - Enchantment.JavaEnchantment.BREACH, "enchantment.minecraft.breach", - Enchantment.JavaEnchantment.WIND_BURST, "enchantment.minecraft.wind_burst"); - protected final @Nullable NbtMap remapEnchantment(GeyserSession session, int enchantId, int level, BedrockItemBuilder builder) { - // TODO verify - // TODO streamline Enchantment process - Enchantment.JavaEnchantment enchantment = Enchantment.JavaEnchantment.of(enchantId); - String translationKey = ENCHANTMENT_TRANSLATION_KEYS.get(enchantment); - if (translationKey != null) { - String enchantmentTranslation = MinecraftLocale.getLocaleString(translationKey, session.locale()); - addJavaOnlyEnchantment(session, builder, enchantmentTranslation, level); - return null; - } + Enchantment enchantment = session.getRegistryCache().enchantments().byId(enchantId); if (enchantment == null) { GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment while NBT item translating: " + enchantId); return null; } + BedrockEnchantment bedrockEnchantment = enchantment.bedrockEnchantment(); + if (bedrockEnchantment == null) { + String enchantmentTranslation = MinecraftLocale.getLocaleString(enchantment.description(), session.locale()); + addJavaOnlyEnchantment(session, builder, enchantmentTranslation, level); + return null; + } + return NbtMap.builder() - .putShort("id", (short) Enchantment.valueOf(enchantment.name()).ordinal()) + .putShort("id", (short) bedrockEnchantment.ordinal()) .putShort("lvl", (short) level) .build(); } @@ -260,7 +216,22 @@ public class Item { private void addJavaOnlyEnchantment(GeyserSession session, BedrockItemBuilder builder, String enchantmentName, int level) { String lvlTranslation = MinecraftLocale.getLocaleString("enchantment.level." + level, session.locale()); - builder.getOrCreateLore().add(ChatColor.RESET + ChatColor.GRAY + enchantmentName + " " + lvlTranslation); + builder.getOrCreateLore().add(0, ChatColor.RESET + ChatColor.GRAY + enchantmentName + " " + lvlTranslation); + } + + protected final void translateDyedColor(DataComponents components, BedrockItemBuilder builder) { + DyedItemColor dyedItemColor = components.get(DataComponentType.DYED_COLOR); + if (dyedItemColor != null) { + builder.putInt("customColor", dyedItemColor.getRgb()); + } + } + + /** + * Override if the Bedrock equivalent of an item uses damage for extra data, and should not be tracked + * when translating an item. + */ + public boolean ignoreDamage() { + return false; } /* Translation methods end */ diff --git a/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java b/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java new file mode 100644 index 000000000..815f71419 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java @@ -0,0 +1,72 @@ +/* + * 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.item.type; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +public class OminousBottleItem extends Item { + public OminousBottleItem(String javaIdentifier, Builder builder) { + super(javaIdentifier, builder); + } + + @Override + public ItemData.Builder translateToBedrock(int count, @Nullable DataComponents components, ItemMapping mapping, ItemMappings mappings) { + var builder = super.translateToBedrock(count, components, mapping, mappings); + if (components == null) { + // Level 1 ominous bottle is null components - Java 1.21. + return builder; + } + Integer amplifier = components.get(DataComponentType.OMINOUS_BOTTLE_AMPLIFIER); + if (amplifier != null) { + builder.damage(amplifier); + } + return builder; + } + + @Override + public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + // This item can be pulled from the creative inventory with amplifiers. + GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); + int damage = itemData.getDamage(); + if (damage == 0) { + return itemStack; + } + itemStack.getOrCreateComponents().put(DataComponentType.OMINOUS_BOTTLE_AMPLIFIER, damage); + return itemStack; + } + + @Override + public boolean ignoreDamage() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java index e9889c882..f8fe2b4ee 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java @@ -76,4 +76,9 @@ public class PotionItem extends Item { } return itemStack; } + + @Override + public boolean ignoreDamage() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java b/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java index db33bb584..d9e58eaf9 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java @@ -27,7 +27,7 @@ package org.geysermc.geyser.item.type; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; @@ -44,11 +44,11 @@ public class TippedArrowItem extends ArrowItem { if (components != null) { PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS); if (potionContents != null) { - TippedArrowPotion tippedArrowPotion = TippedArrowPotion.of(potionContents.getPotionId()); - if (tippedArrowPotion != null) { + Potion potion = Potion.getByJavaId(potionContents.getPotionId()); + if (potion != null) { return ItemData.builder() .definition(mapping.getBedrockDefinition()) - .damage(tippedArrowPotion.getBedrockId()) + .damage(potion.tippedArrowId()) .count(count); } GeyserImpl.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionContents.getPotionId()); diff --git a/core/src/main/java/org/geysermc/geyser/item/type/WolfArmorItem.java b/core/src/main/java/org/geysermc/geyser/item/type/WolfArmorItem.java new file mode 100644 index 000000000..bd97a6a7d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/type/WolfArmorItem.java @@ -0,0 +1,46 @@ +/* + * 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.item.type; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.ArmorMaterial; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +public class WolfArmorItem extends ArmorItem { + public WolfArmorItem(String javaIdentifier, ArmorMaterial material, Builder builder) { + super(javaIdentifier, material, builder); + } + + @Override + public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) { + super.translateComponentsToBedrock(session, components, builder); + + // Note that this is handled as of 1.21 in the ItemColors class. + translateDyedColor(components, builder); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java index c55a74cd2..eacf6bd1b 100644 --- a/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java @@ -63,7 +63,8 @@ public enum BedrockMapIcon { ICON_SNOWY_VILLAGE(MapIconType.SNOWY_VILLAGE, 20), ICON_TAIGA_VILLAGE(MapIconType.TAIGA_VILLAGE, 21), ICON_JUNGLE_TEMPLE(MapIconType.JUNGLE_TEMPLE, 22), - ICON_SWAMP_HUT(MapIconType.SWAMP_HUT, 23); + ICON_SWAMP_HUT(MapIconType.SWAMP_HUT, 23), + ICON_TRIAL_CHAMBERS(MapIconType.TRIAL_CHAMBERS, 24); private static final BedrockMapIcon[] VALUES = values(); diff --git a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java new file mode 100644 index 000000000..156a62cd1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java @@ -0,0 +1,50 @@ +/* + * 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.level; + +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; + +public record JukeboxSong(String soundEvent, String description) { + + public static JukeboxSong read(RegistryEntry entry) { + NbtMap data = entry.getData(); + Object soundEventObject = data.get("sound_event"); + String soundEvent; + if (soundEventObject instanceof NbtMap map) { + soundEvent = map.getString("sound_id"); + } else if (soundEventObject instanceof String string) { + soundEvent = string; + } else { + soundEvent = ""; + GeyserImpl.getInstance().getLogger().debug("Sound event for " + entry.getId() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject); + } + String description = MessageTranslator.deserializeDescription(data); + return new JukeboxSong(soundEvent, description); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/level/PaintingType.java b/core/src/main/java/org/geysermc/geyser/level/PaintingType.java index 643fd735d..f829b481c 100644 --- a/core/src/main/java/org/geysermc/geyser/level/PaintingType.java +++ b/core/src/main/java/org/geysermc/geyser/level/PaintingType.java @@ -28,6 +28,9 @@ package org.geysermc.geyser.level; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import net.kyori.adventure.key.Key; + +import java.util.Locale; @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @@ -61,21 +64,40 @@ public enum PaintingType { EARTH("Earth", 2, 2), WIND("Wind", 2, 2), WATER("Water", 2, 2), - FIRE("Fire", 2, 2); + FIRE("Fire", 2, 2), + MEDITATIVE("meditative", 1, 1), + PRAIRIE_RIDE("prairie_ride", 1, 2), + BAROQUE("baroque", 2, 2), + HUMBLE("humble", 2, 2), + UNPACKED("unpacked", 4, 4), + BACKYARD("backyard", 3, 4), + BOUQUET("bouquet", 3, 3), + CAVEBIRD("cavebird", 3, 3), + CHANGING("changing", 4, 2), + COTAN("cotan", 3, 3), + ENDBOSS("endboss", 3, 3), + FERN("fern", 3, 3), + FINDING("finding", 4, 2), + LOWMIST("lowmist", 4, 2), + ORB("orb", 4, 4), + OWLEMONS("owlemons", 3, 3), + PASSAGE("passage", 4, 2), + POND("pond", 3, 4), + SUNFLOWERS("sunflowers", 3, 3), + TIDES("tides", 3, 3); private static final PaintingType[] VALUES = values(); private final String bedrockName; private final int width; private final int height; - public static PaintingType getByName(String javaName) { - for (PaintingType paintingName : VALUES) { - if (paintingName.name().equalsIgnoreCase(javaName)) return paintingName; + public static PaintingType getByName(Key key) { + if (!key.namespace().equals("minecraft")) { + return null; } - return KEBAB; - } - - public static PaintingType getByPaintingType(org.geysermc.mcprotocollib.protocol.data.game.entity.type.PaintingType paintingType) { - return getByName(paintingType.name()); + for (PaintingType paintingName : VALUES) { + if (paintingName.name().toLowerCase(Locale.ROOT).equals(key.value())) return paintingName; + } + return null; } } diff --git a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index 3670b6b73..4a20771f2 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -43,9 +43,11 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemCodecHel import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -209,6 +211,13 @@ public abstract class WorldManager { return CompletableFuture.completedFuture(null); } + /** + * Retrieves decorated pot sherds from the server. Used to ensure the data is not erased on animation sent + * through the BlockEntityDataPacket. + */ + public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer> apply) { + } + protected static final Function, DataComponents> RAW_TRANSFORMER = map -> { try { Map, DataComponent> components = new HashMap<>(); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java b/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java index 857a3fc13..735c1f6c4 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java @@ -33,6 +33,7 @@ import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; import static org.geysermc.geyser.level.block.property.Properties.*; import static org.geysermc.geyser.level.block.type.Block.builder; @@ -89,11 +90,11 @@ public final class Blocks { public static final Block LAVA = register(new Block("lava", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) .intState(LEVEL))); public static final Block SAND = register(new Block("sand", builder().destroyTime(0.5f))); - public static final Block SUSPICIOUS_SAND = register(new Block("suspicious_sand", builder().setBlockEntity().destroyTime(0.25f).pushReaction(PistonBehavior.DESTROY) + public static final Block SUSPICIOUS_SAND = register(new Block("suspicious_sand", builder().setBlockEntity(BlockEntityType.BRUSHABLE_BLOCK).destroyTime(0.25f).pushReaction(PistonBehavior.DESTROY) .intState(DUSTED))); public static final Block RED_SAND = register(new Block("red_sand", builder().destroyTime(0.5f))); public static final Block GRAVEL = register(new Block("gravel", builder().destroyTime(0.6f))); - public static final Block SUSPICIOUS_GRAVEL = register(new Block("suspicious_gravel", builder().setBlockEntity().destroyTime(0.25f).pushReaction(PistonBehavior.DESTROY) + public static final Block SUSPICIOUS_GRAVEL = register(new Block("suspicious_gravel", builder().setBlockEntity(BlockEntityType.BRUSHABLE_BLOCK).destroyTime(0.25f).pushReaction(PistonBehavior.DESTROY) .intState(DUSTED))); public static final Block GOLD_ORE = register(new Block("gold_ore", builder().requiresCorrectToolForDrops().destroyTime(3.0f))); public static final Block DEEPSLATE_GOLD_ORE = register(new Block("deepslate_gold_ore", builder().requiresCorrectToolForDrops().destroyTime(4.5f))); @@ -220,7 +221,7 @@ public final class Blocks { public static final Block LAPIS_ORE = register(new Block("lapis_ore", builder().requiresCorrectToolForDrops().destroyTime(3.0f))); public static final Block DEEPSLATE_LAPIS_ORE = register(new Block("deepslate_lapis_ore", builder().requiresCorrectToolForDrops().destroyTime(4.5f))); public static final Block LAPIS_BLOCK = register(new Block("lapis_block", builder().requiresCorrectToolForDrops().destroyTime(3.0f))); - public static final Block DISPENSER = register(new Block("dispenser", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) + public static final Block DISPENSER = register(new Block("dispenser", builder().setBlockEntity(BlockEntityType.DISPENSER).requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN) .booleanState(TRIGGERED))); public static final Block SANDSTONE = register(new Block("sandstone", builder().requiresCorrectToolForDrops().destroyTime(0.8f))); @@ -230,67 +231,67 @@ public final class Blocks { .enumState(NOTEBLOCK_INSTRUMENT) .intState(NOTE) .booleanState(POWERED))); - public static final Block WHITE_BED = register(new BedBlock("white_bed", 0, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block WHITE_BED = register(new BedBlock("white_bed", 0, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block ORANGE_BED = register(new BedBlock("orange_bed", 1, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block ORANGE_BED = register(new BedBlock("orange_bed", 1, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block MAGENTA_BED = register(new BedBlock("magenta_bed", 2, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block MAGENTA_BED = register(new BedBlock("magenta_bed", 2, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block LIGHT_BLUE_BED = register(new BedBlock("light_blue_bed", 3, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIGHT_BLUE_BED = register(new BedBlock("light_blue_bed", 3, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block YELLOW_BED = register(new BedBlock("yellow_bed", 4, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block YELLOW_BED = register(new BedBlock("yellow_bed", 4, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block LIME_BED = register(new BedBlock("lime_bed", 5, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIME_BED = register(new BedBlock("lime_bed", 5, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block PINK_BED = register(new BedBlock("pink_bed", 6, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block PINK_BED = register(new BedBlock("pink_bed", 6, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block GRAY_BED = register(new BedBlock("gray_bed", 7, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block GRAY_BED = register(new BedBlock("gray_bed", 7, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block LIGHT_GRAY_BED = register(new BedBlock("light_gray_bed", 8, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIGHT_GRAY_BED = register(new BedBlock("light_gray_bed", 8, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block CYAN_BED = register(new BedBlock("cyan_bed", 9, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block CYAN_BED = register(new BedBlock("cyan_bed", 9, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block PURPLE_BED = register(new BedBlock("purple_bed", 10, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block PURPLE_BED = register(new BedBlock("purple_bed", 10, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block BLUE_BED = register(new BedBlock("blue_bed", 11, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block BLUE_BED = register(new BedBlock("blue_bed", 11, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block BROWN_BED = register(new BedBlock("brown_bed", 12, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block BROWN_BED = register(new BedBlock("brown_bed", 12, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block GREEN_BED = register(new BedBlock("green_bed", 13, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block GREEN_BED = register(new BedBlock("green_bed", 13, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block RED_BED = register(new BedBlock("red_bed", 14, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block RED_BED = register(new BedBlock("red_bed", 14, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); - public static final Block BLACK_BED = register(new BedBlock("black_bed", 15, builder().setBlockEntity().destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) + public static final Block BLACK_BED = register(new BedBlock("black_bed", 15, builder().setBlockEntity(BlockEntityType.BED).destroyTime(0.2f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OCCUPIED) .enumState(BED_PART))); @@ -335,7 +336,7 @@ public final class Blocks { public static final Block GREEN_WOOL = register(new Block("green_wool", builder().destroyTime(0.8f))); public static final Block RED_WOOL = register(new Block("red_wool", builder().destroyTime(0.8f))); public static final Block BLACK_WOOL = register(new Block("black_wool", builder().destroyTime(0.8f))); - public static final Block MOVING_PISTON = register(new MovingPistonBlock("moving_piston", builder().setBlockEntity().destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK) + public static final Block MOVING_PISTON = register(new MovingPistonBlock("moving_piston", builder().setBlockEntity(BlockEntityType.PISTON).destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN) .enumState(PISTON_TYPE))); public static final Block DANDELION = register(new Block("dandelion", builder().pushReaction(PistonBehavior.DESTROY))); @@ -360,7 +361,7 @@ public final class Blocks { public static final Block TNT = register(new Block("tnt", builder() .booleanState(UNSTABLE))); public static final Block BOOKSHELF = register(new Block("bookshelf", builder().destroyTime(1.5f))); - public static final Block CHISELED_BOOKSHELF = register(new Block("chiseled_bookshelf", builder().setBlockEntity().destroyTime(1.5f) + public static final Block CHISELED_BOOKSHELF = register(new Block("chiseled_bookshelf", builder().setBlockEntity(BlockEntityType.CHISELED_BOOKSHELF).destroyTime(1.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(CHISELED_BOOKSHELF_SLOT_0_OCCUPIED) .booleanState(CHISELED_BOOKSHELF_SLOT_1_OCCUPIED) @@ -381,13 +382,13 @@ public final class Blocks { .booleanState(UP) .booleanState(WEST))); public static final Block SOUL_FIRE = register(new Block("soul_fire", builder().pushReaction(PistonBehavior.DESTROY))); - public static final Block SPAWNER = register(new SpawnerBlock("spawner", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(5.0f))); + public static final Block SPAWNER = register(new SpawnerBlock("spawner", builder().setBlockEntity(BlockEntityType.MOB_SPAWNER).requiresCorrectToolForDrops().destroyTime(5.0f))); public static final Block OAK_STAIRS = register(new Block("oak_stairs", builder().destroyTime(2.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(HALF) .enumState(STAIRS_SHAPE) .booleanState(WATERLOGGED))); - public static final Block CHEST = register(new ChestBlock("chest", builder().setBlockEntity().destroyTime(2.5f) + public static final Block CHEST = register(new ChestBlock("chest", builder().setBlockEntity(BlockEntityType.CHEST).destroyTime(2.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(CHEST_TYPE, ChestType.VALUES) .booleanState(WATERLOGGED))); @@ -405,34 +406,34 @@ public final class Blocks { .intState(AGE_7))); public static final Block FARMLAND = register(new Block("farmland", builder().destroyTime(0.6f) .intState(MOISTURE))); - public static final Block FURNACE = register(new FurnaceBlock("furnace", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) + public static final Block FURNACE = register(new FurnaceBlock("furnace", builder().setBlockEntity(BlockEntityType.FURNACE).requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(LIT))); - public static final Block OAK_SIGN = register(new Block("oak_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block OAK_SIGN = register(new Block("oak_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block SPRUCE_SIGN = register(new Block("spruce_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block SPRUCE_SIGN = register(new Block("spruce_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block BIRCH_SIGN = register(new Block("birch_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BIRCH_SIGN = register(new Block("birch_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block ACACIA_SIGN = register(new Block("acacia_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block ACACIA_SIGN = register(new Block("acacia_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block CHERRY_SIGN = register(new Block("cherry_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CHERRY_SIGN = register(new Block("cherry_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block JUNGLE_SIGN = register(new Block("jungle_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block JUNGLE_SIGN = register(new Block("jungle_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block DARK_OAK_SIGN = register(new Block("dark_oak_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block DARK_OAK_SIGN = register(new Block("dark_oak_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block MANGROVE_SIGN = register(new Block("mangrove_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block MANGROVE_SIGN = register(new Block("mangrove_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block BAMBOO_SIGN = register(new Block("bamboo_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BAMBOO_SIGN = register(new Block("bamboo_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); public static final Block OAK_DOOR = register(new DoorBlock("oak_door", builder().destroyTime(3.0f).pushReaction(PistonBehavior.DESTROY) @@ -452,108 +453,108 @@ public final class Blocks { .enumState(HALF) .enumState(STAIRS_SHAPE) .booleanState(WATERLOGGED))); - public static final Block OAK_WALL_SIGN = register(new Block("oak_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block OAK_WALL_SIGN = register(new Block("oak_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block SPRUCE_WALL_SIGN = register(new Block("spruce_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block SPRUCE_WALL_SIGN = register(new Block("spruce_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block BIRCH_WALL_SIGN = register(new Block("birch_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BIRCH_WALL_SIGN = register(new Block("birch_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block ACACIA_WALL_SIGN = register(new Block("acacia_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block ACACIA_WALL_SIGN = register(new Block("acacia_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block CHERRY_WALL_SIGN = register(new Block("cherry_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CHERRY_WALL_SIGN = register(new Block("cherry_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block JUNGLE_WALL_SIGN = register(new Block("jungle_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block JUNGLE_WALL_SIGN = register(new Block("jungle_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block DARK_OAK_WALL_SIGN = register(new Block("dark_oak_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block DARK_OAK_WALL_SIGN = register(new Block("dark_oak_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block MANGROVE_WALL_SIGN = register(new Block("mangrove_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block MANGROVE_WALL_SIGN = register(new Block("mangrove_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block BAMBOO_WALL_SIGN = register(new Block("bamboo_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BAMBOO_WALL_SIGN = register(new Block("bamboo_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block OAK_HANGING_SIGN = register(new Block("oak_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block OAK_HANGING_SIGN = register(new Block("oak_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block SPRUCE_HANGING_SIGN = register(new Block("spruce_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block SPRUCE_HANGING_SIGN = register(new Block("spruce_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block BIRCH_HANGING_SIGN = register(new Block("birch_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BIRCH_HANGING_SIGN = register(new Block("birch_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block ACACIA_HANGING_SIGN = register(new Block("acacia_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block ACACIA_HANGING_SIGN = register(new Block("acacia_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block CHERRY_HANGING_SIGN = register(new Block("cherry_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CHERRY_HANGING_SIGN = register(new Block("cherry_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block JUNGLE_HANGING_SIGN = register(new Block("jungle_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block JUNGLE_HANGING_SIGN = register(new Block("jungle_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block DARK_OAK_HANGING_SIGN = register(new Block("dark_oak_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block DARK_OAK_HANGING_SIGN = register(new Block("dark_oak_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block CRIMSON_HANGING_SIGN = register(new Block("crimson_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CRIMSON_HANGING_SIGN = register(new Block("crimson_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block WARPED_HANGING_SIGN = register(new Block("warped_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block WARPED_HANGING_SIGN = register(new Block("warped_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block MANGROVE_HANGING_SIGN = register(new Block("mangrove_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block MANGROVE_HANGING_SIGN = register(new Block("mangrove_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block BAMBOO_HANGING_SIGN = register(new Block("bamboo_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BAMBOO_HANGING_SIGN = register(new Block("bamboo_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .booleanState(ATTACHED) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block OAK_WALL_HANGING_SIGN = register(new Block("oak_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block OAK_WALL_HANGING_SIGN = register(new Block("oak_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block SPRUCE_WALL_HANGING_SIGN = register(new Block("spruce_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block SPRUCE_WALL_HANGING_SIGN = register(new Block("spruce_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block BIRCH_WALL_HANGING_SIGN = register(new Block("birch_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BIRCH_WALL_HANGING_SIGN = register(new Block("birch_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block ACACIA_WALL_HANGING_SIGN = register(new Block("acacia_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block ACACIA_WALL_HANGING_SIGN = register(new Block("acacia_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block CHERRY_WALL_HANGING_SIGN = register(new Block("cherry_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CHERRY_WALL_HANGING_SIGN = register(new Block("cherry_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block JUNGLE_WALL_HANGING_SIGN = register(new Block("jungle_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block JUNGLE_WALL_HANGING_SIGN = register(new Block("jungle_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block DARK_OAK_WALL_HANGING_SIGN = register(new Block("dark_oak_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block DARK_OAK_WALL_HANGING_SIGN = register(new Block("dark_oak_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block MANGROVE_WALL_HANGING_SIGN = register(new Block("mangrove_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block MANGROVE_WALL_HANGING_SIGN = register(new Block("mangrove_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block CRIMSON_WALL_HANGING_SIGN = register(new Block("crimson_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CRIMSON_WALL_HANGING_SIGN = register(new Block("crimson_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block WARPED_WALL_HANGING_SIGN = register(new Block("warped_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block WARPED_WALL_HANGING_SIGN = register(new Block("warped_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block BAMBOO_WALL_HANGING_SIGN = register(new Block("bamboo_wall_hanging_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block BAMBOO_WALL_HANGING_SIGN = register(new Block("bamboo_wall_hanging_sign", builder().setBlockEntity(BlockEntityType.HANGING_SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); public static final Block LEVER = register(new Block("lever", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY) @@ -608,7 +609,7 @@ public final class Blocks { public static final Block CLAY = register(new Block("clay", builder().destroyTime(0.6f))); public static final Block SUGAR_CANE = register(new Block("sugar_cane", builder().pushReaction(PistonBehavior.DESTROY) .intState(AGE_15))); - public static final Block JUKEBOX = register(new Block("jukebox", builder().setBlockEntity().destroyTime(2.0f) + public static final Block JUKEBOX = register(new Block("jukebox", builder().setBlockEntity(BlockEntityType.JUKEBOX).destroyTime(2.0f) .booleanState(HAS_RECORD))); public static final Block OAK_FENCE = register(new Block("oak_fence", builder().destroyTime(2.0f) .booleanState(EAST) @@ -819,8 +820,8 @@ public final class Blocks { .booleanState(WATERLOGGED))); public static final Block NETHER_WART = register(new Block("nether_wart", builder().pushReaction(PistonBehavior.DESTROY) .intState(AGE_3))); - public static final Block ENCHANTING_TABLE = register(new Block("enchanting_table", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(5.0f))); - public static final Block BREWING_STAND = register(new Block("brewing_stand", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(0.5f) + public static final Block ENCHANTING_TABLE = register(new Block("enchanting_table", builder().setBlockEntity(BlockEntityType.ENCHANTING_TABLE).requiresCorrectToolForDrops().destroyTime(5.0f))); + public static final Block BREWING_STAND = register(new Block("brewing_stand", builder().setBlockEntity(BlockEntityType.BREWING_STAND).requiresCorrectToolForDrops().destroyTime(0.5f) .booleanState(HAS_BOTTLE_0) .booleanState(HAS_BOTTLE_1) .booleanState(HAS_BOTTLE_2))); @@ -830,7 +831,7 @@ public final class Blocks { public static final Block LAVA_CAULDRON = register(new CauldronBlock("lava_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f))); public static final Block POWDER_SNOW_CAULDRON = register(new CauldronBlock("powder_snow_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f) .intState(LEVEL_CAULDRON))); - public static final Block END_PORTAL = register(new Block("end_portal", builder().setBlockEntity().destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK))); + public static final Block END_PORTAL = register(new Block("end_portal", builder().setBlockEntity(BlockEntityType.END_PORTAL).destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK))); public static final Block END_PORTAL_FRAME = register(new Block("end_portal_frame", builder().destroyTime(-1.0f) .booleanState(EYE) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); @@ -848,7 +849,7 @@ public final class Blocks { .booleanState(WATERLOGGED))); public static final Block EMERALD_ORE = register(new Block("emerald_ore", builder().requiresCorrectToolForDrops().destroyTime(3.0f))); public static final Block DEEPSLATE_EMERALD_ORE = register(new Block("deepslate_emerald_ore", builder().requiresCorrectToolForDrops().destroyTime(4.5f))); - public static final Block ENDER_CHEST = register(new Block("ender_chest", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(22.5f) + public static final Block ENDER_CHEST = register(new Block("ender_chest", builder().setBlockEntity(BlockEntityType.ENDER_CHEST).requiresCorrectToolForDrops().destroyTime(22.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); public static final Block TRIPWIRE_HOOK = register(new Block("tripwire_hook", builder().pushReaction(PistonBehavior.DESTROY) @@ -879,10 +880,10 @@ public final class Blocks { .enumState(HALF) .enumState(STAIRS_SHAPE) .booleanState(WATERLOGGED))); - public static final Block COMMAND_BLOCK = register(new Block("command_block", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(-1.0f) + public static final Block COMMAND_BLOCK = register(new Block("command_block", builder().setBlockEntity(BlockEntityType.COMMAND_BLOCK).requiresCorrectToolForDrops().destroyTime(-1.0f) .booleanState(CONDITIONAL) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block BEACON = register(new Block("beacon", builder().setBlockEntity().destroyTime(3.0f))); + public static final Block BEACON = register(new Block("beacon", builder().setBlockEntity(BlockEntityType.BEACON).destroyTime(3.0f))); public static final Block COBBLESTONE_WALL = register(new Block("cobblestone_wall", builder().requiresCorrectToolForDrops().destroyTime(2.0f) .enumState(EAST_WALL) .enumState(NORTH_WALL) @@ -965,46 +966,46 @@ public final class Blocks { .enumState(ATTACH_FACE) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block SKELETON_SKULL = register(new SkullBlock("skeleton_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block SKELETON_SKULL = register(new SkullBlock("skeleton_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block SKELETON_WALL_SKULL = register(new WallSkullBlock("skeleton_wall_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block SKELETON_WALL_SKULL = register(new WallSkullBlock("skeleton_wall_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block WITHER_SKELETON_SKULL = register(new SkullBlock("wither_skeleton_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block WITHER_SKELETON_SKULL = register(new SkullBlock("wither_skeleton_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block WITHER_SKELETON_WALL_SKULL = register(new WallSkullBlock("wither_skeleton_wall_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block WITHER_SKELETON_WALL_SKULL = register(new WallSkullBlock("wither_skeleton_wall_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block ZOMBIE_HEAD = register(new SkullBlock("zombie_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block ZOMBIE_HEAD = register(new SkullBlock("zombie_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block ZOMBIE_WALL_HEAD = register(new WallSkullBlock("zombie_wall_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block ZOMBIE_WALL_HEAD = register(new WallSkullBlock("zombie_wall_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block PLAYER_HEAD = register(new SkullBlock("player_head", SkullBlock.Type.PLAYER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PLAYER_HEAD = register(new SkullBlock("player_head", SkullBlock.Type.PLAYER, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block PLAYER_WALL_HEAD = register(new WallSkullBlock("player_wall_head", SkullBlock.Type.PLAYER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PLAYER_WALL_HEAD = register(new WallSkullBlock("player_wall_head", SkullBlock.Type.PLAYER, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block CREEPER_HEAD = register(new SkullBlock("creeper_head", SkullBlock.Type.CREEPER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block CREEPER_HEAD = register(new SkullBlock("creeper_head", SkullBlock.Type.CREEPER, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block CREEPER_WALL_HEAD = register(new WallSkullBlock("creeper_wall_head", SkullBlock.Type.CREEPER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block CREEPER_WALL_HEAD = register(new WallSkullBlock("creeper_wall_head", SkullBlock.Type.CREEPER, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block DRAGON_HEAD = register(new SkullBlock("dragon_head", SkullBlock.Type.DRAGON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block DRAGON_HEAD = register(new SkullBlock("dragon_head", SkullBlock.Type.DRAGON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block DRAGON_WALL_HEAD = register(new WallSkullBlock("dragon_wall_head", SkullBlock.Type.DRAGON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block DRAGON_WALL_HEAD = register(new WallSkullBlock("dragon_wall_head", SkullBlock.Type.DRAGON, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); - public static final Block PIGLIN_HEAD = register(new SkullBlock("piglin_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PIGLIN_HEAD = register(new SkullBlock("piglin_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .booleanState(POWERED) .intState(ROTATION_16))); - public static final Block PIGLIN_WALL_HEAD = register(new WallSkullBlock("piglin_wall_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PIGLIN_WALL_HEAD = register(new WallSkullBlock("piglin_wall_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity(BlockEntityType.SKULL).destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); public static final Block ANVIL = register(new Block("anvil", builder().requiresCorrectToolForDrops().destroyTime(5.0f).pushReaction(PistonBehavior.BLOCK) @@ -1013,7 +1014,7 @@ public final class Blocks { .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); public static final Block DAMAGED_ANVIL = register(new Block("damaged_anvil", builder().requiresCorrectToolForDrops().destroyTime(5.0f).pushReaction(PistonBehavior.BLOCK) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block TRAPPED_CHEST = register(new ChestBlock("trapped_chest", builder().setBlockEntity().destroyTime(2.5f) + public static final Block TRAPPED_CHEST = register(new ChestBlock("trapped_chest", builder().setBlockEntity(BlockEntityType.TRAPPED_CHEST).destroyTime(2.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(CHEST_TYPE, ChestType.VALUES) .booleanState(WATERLOGGED))); @@ -1021,16 +1022,16 @@ public final class Blocks { .intState(POWER))); public static final Block HEAVY_WEIGHTED_PRESSURE_PLATE = register(new Block("heavy_weighted_pressure_plate", builder().requiresCorrectToolForDrops().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY) .intState(POWER))); - public static final Block COMPARATOR = register(new Block("comparator", builder().setBlockEntity().pushReaction(PistonBehavior.DESTROY) + public static final Block COMPARATOR = register(new Block("comparator", builder().setBlockEntity(BlockEntityType.COMPARATOR).pushReaction(PistonBehavior.DESTROY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(MODE_COMPARATOR) .booleanState(POWERED))); - public static final Block DAYLIGHT_DETECTOR = register(new Block("daylight_detector", builder().setBlockEntity().destroyTime(0.2f) + public static final Block DAYLIGHT_DETECTOR = register(new Block("daylight_detector", builder().setBlockEntity(BlockEntityType.DAYLIGHT_DETECTOR).destroyTime(0.2f) .booleanState(INVERTED) .intState(POWER))); public static final Block REDSTONE_BLOCK = register(new Block("redstone_block", builder().requiresCorrectToolForDrops().destroyTime(5.0f))); public static final Block NETHER_QUARTZ_ORE = register(new Block("nether_quartz_ore", builder().requiresCorrectToolForDrops().destroyTime(3.0f))); - public static final Block HOPPER = register(new Block("hopper", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.0f) + public static final Block HOPPER = register(new Block("hopper", builder().setBlockEntity(BlockEntityType.HOPPER).requiresCorrectToolForDrops().destroyTime(3.0f) .booleanState(ENABLED) .enumState(FACING_HOPPER, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); public static final Block QUARTZ_BLOCK = register(new Block("quartz_block", builder().requiresCorrectToolForDrops().destroyTime(0.8f))); @@ -1046,7 +1047,7 @@ public final class Blocks { .booleanState(POWERED) .enumState(RAIL_SHAPE_STRAIGHT) .booleanState(WATERLOGGED))); - public static final Block DROPPER = register(new Block("dropper", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) + public static final Block DROPPER = register(new Block("dropper", builder().setBlockEntity(BlockEntityType.DROPPER).requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN) .booleanState(TRIGGERED))); public static final Block WHITE_TERRACOTTA = register(new Block("white_terracotta", builder().requiresCorrectToolForDrops().destroyTime(1.25f))); @@ -1264,69 +1265,69 @@ public final class Blocks { .enumState(DOUBLE_BLOCK_HALF))); public static final Block LARGE_FERN = register(new Block("large_fern", builder().pushReaction(PistonBehavior.DESTROY) .enumState(DOUBLE_BLOCK_HALF))); - public static final Block WHITE_BANNER = register(new BannerBlock("white_banner", 0, builder().setBlockEntity().destroyTime(1.0f) + public static final Block WHITE_BANNER = register(new BannerBlock("white_banner", 0, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block ORANGE_BANNER = register(new BannerBlock("orange_banner", 1, builder().setBlockEntity().destroyTime(1.0f) + public static final Block ORANGE_BANNER = register(new BannerBlock("orange_banner", 1, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block MAGENTA_BANNER = register(new BannerBlock("magenta_banner", 2, builder().setBlockEntity().destroyTime(1.0f) + public static final Block MAGENTA_BANNER = register(new BannerBlock("magenta_banner", 2, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block LIGHT_BLUE_BANNER = register(new BannerBlock("light_blue_banner", 3, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIGHT_BLUE_BANNER = register(new BannerBlock("light_blue_banner", 3, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block YELLOW_BANNER = register(new BannerBlock("yellow_banner", 4, builder().setBlockEntity().destroyTime(1.0f) + public static final Block YELLOW_BANNER = register(new BannerBlock("yellow_banner", 4, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block LIME_BANNER = register(new BannerBlock("lime_banner", 5, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIME_BANNER = register(new BannerBlock("lime_banner", 5, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block PINK_BANNER = register(new BannerBlock("pink_banner", 6, builder().setBlockEntity().destroyTime(1.0f) + public static final Block PINK_BANNER = register(new BannerBlock("pink_banner", 6, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block GRAY_BANNER = register(new BannerBlock("gray_banner", 7, builder().setBlockEntity().destroyTime(1.0f) + public static final Block GRAY_BANNER = register(new BannerBlock("gray_banner", 7, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block LIGHT_GRAY_BANNER = register(new BannerBlock("light_gray_banner", 8, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIGHT_GRAY_BANNER = register(new BannerBlock("light_gray_banner", 8, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block CYAN_BANNER = register(new BannerBlock("cyan_banner", 9, builder().setBlockEntity().destroyTime(1.0f) + public static final Block CYAN_BANNER = register(new BannerBlock("cyan_banner", 9, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block PURPLE_BANNER = register(new BannerBlock("purple_banner", 10, builder().setBlockEntity().destroyTime(1.0f) + public static final Block PURPLE_BANNER = register(new BannerBlock("purple_banner", 10, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block BLUE_BANNER = register(new BannerBlock("blue_banner", 11, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BLUE_BANNER = register(new BannerBlock("blue_banner", 11, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block BROWN_BANNER = register(new BannerBlock("brown_banner", 12, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BROWN_BANNER = register(new BannerBlock("brown_banner", 12, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block GREEN_BANNER = register(new BannerBlock("green_banner", 13, builder().setBlockEntity().destroyTime(1.0f) + public static final Block GREEN_BANNER = register(new BannerBlock("green_banner", 13, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block RED_BANNER = register(new BannerBlock("red_banner", 14, builder().setBlockEntity().destroyTime(1.0f) + public static final Block RED_BANNER = register(new BannerBlock("red_banner", 14, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block BLACK_BANNER = register(new BannerBlock("black_banner", 15, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BLACK_BANNER = register(new BannerBlock("black_banner", 15, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .intState(ROTATION_16))); - public static final Block WHITE_WALL_BANNER = register(new BannerBlock("white_wall_banner", 0, builder().setBlockEntity().destroyTime(1.0f) + public static final Block WHITE_WALL_BANNER = register(new BannerBlock("white_wall_banner", 0, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block ORANGE_WALL_BANNER = register(new BannerBlock("orange_wall_banner", 1, builder().setBlockEntity().destroyTime(1.0f) + public static final Block ORANGE_WALL_BANNER = register(new BannerBlock("orange_wall_banner", 1, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block MAGENTA_WALL_BANNER = register(new BannerBlock("magenta_wall_banner", 2, builder().setBlockEntity().destroyTime(1.0f) + public static final Block MAGENTA_WALL_BANNER = register(new BannerBlock("magenta_wall_banner", 2, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block LIGHT_BLUE_WALL_BANNER = register(new BannerBlock("light_blue_wall_banner", 3, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIGHT_BLUE_WALL_BANNER = register(new BannerBlock("light_blue_wall_banner", 3, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block YELLOW_WALL_BANNER = register(new BannerBlock("yellow_wall_banner", 4, builder().setBlockEntity().destroyTime(1.0f) + public static final Block YELLOW_WALL_BANNER = register(new BannerBlock("yellow_wall_banner", 4, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block LIME_WALL_BANNER = register(new BannerBlock("lime_wall_banner", 5, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIME_WALL_BANNER = register(new BannerBlock("lime_wall_banner", 5, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block PINK_WALL_BANNER = register(new BannerBlock("pink_wall_banner", 6, builder().setBlockEntity().destroyTime(1.0f) + public static final Block PINK_WALL_BANNER = register(new BannerBlock("pink_wall_banner", 6, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block GRAY_WALL_BANNER = register(new BannerBlock("gray_wall_banner", 7, builder().setBlockEntity().destroyTime(1.0f) + public static final Block GRAY_WALL_BANNER = register(new BannerBlock("gray_wall_banner", 7, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block LIGHT_GRAY_WALL_BANNER = register(new BannerBlock("light_gray_wall_banner", 8, builder().setBlockEntity().destroyTime(1.0f) + public static final Block LIGHT_GRAY_WALL_BANNER = register(new BannerBlock("light_gray_wall_banner", 8, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block CYAN_WALL_BANNER = register(new BannerBlock("cyan_wall_banner", 9, builder().setBlockEntity().destroyTime(1.0f) + public static final Block CYAN_WALL_BANNER = register(new BannerBlock("cyan_wall_banner", 9, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block PURPLE_WALL_BANNER = register(new BannerBlock("purple_wall_banner", 10, builder().setBlockEntity().destroyTime(1.0f) + public static final Block PURPLE_WALL_BANNER = register(new BannerBlock("purple_wall_banner", 10, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block BLUE_WALL_BANNER = register(new BannerBlock("blue_wall_banner", 11, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BLUE_WALL_BANNER = register(new BannerBlock("blue_wall_banner", 11, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block BROWN_WALL_BANNER = register(new BannerBlock("brown_wall_banner", 12, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BROWN_WALL_BANNER = register(new BannerBlock("brown_wall_banner", 12, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block GREEN_WALL_BANNER = register(new BannerBlock("green_wall_banner", 13, builder().setBlockEntity().destroyTime(1.0f) + public static final Block GREEN_WALL_BANNER = register(new BannerBlock("green_wall_banner", 13, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block RED_WALL_BANNER = register(new BannerBlock("red_wall_banner", 14, builder().setBlockEntity().destroyTime(1.0f) + public static final Block RED_WALL_BANNER = register(new BannerBlock("red_wall_banner", 14, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block BLACK_WALL_BANNER = register(new BannerBlock("black_wall_banner", 15, builder().setBlockEntity().destroyTime(1.0f) + public static final Block BLACK_WALL_BANNER = register(new BannerBlock("black_wall_banner", 15, builder().setBlockEntity(BlockEntityType.BANNER).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); public static final Block RED_SANDSTONE = register(new Block("red_sandstone", builder().requiresCorrectToolForDrops().destroyTime(0.8f))); public static final Block CHISELED_RED_SANDSTONE = register(new Block("chiseled_red_sandstone", builder().requiresCorrectToolForDrops().destroyTime(0.8f))); @@ -1578,11 +1579,11 @@ public final class Blocks { public static final Block BEETROOTS = register(new Block("beetroots", builder().pushReaction(PistonBehavior.DESTROY) .intState(AGE_3))); public static final Block DIRT_PATH = register(new Block("dirt_path", builder().destroyTime(0.65f))); - public static final Block END_GATEWAY = register(new Block("end_gateway", builder().setBlockEntity().destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK))); - public static final Block REPEATING_COMMAND_BLOCK = register(new Block("repeating_command_block", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(-1.0f) + public static final Block END_GATEWAY = register(new Block("end_gateway", builder().setBlockEntity(BlockEntityType.END_GATEWAY).destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK))); + public static final Block REPEATING_COMMAND_BLOCK = register(new Block("repeating_command_block", builder().setBlockEntity(BlockEntityType.COMMAND_BLOCK).requiresCorrectToolForDrops().destroyTime(-1.0f) .booleanState(CONDITIONAL) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block CHAIN_COMMAND_BLOCK = register(new Block("chain_command_block", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(-1.0f) + public static final Block CHAIN_COMMAND_BLOCK = register(new Block("chain_command_block", builder().setBlockEntity(BlockEntityType.COMMAND_BLOCK).requiresCorrectToolForDrops().destroyTime(-1.0f) .booleanState(CONDITIONAL) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); public static final Block FROSTED_ICE = register(new Block("frosted_ice", builder().destroyTime(0.5f) @@ -1596,39 +1597,39 @@ public final class Blocks { public static final Block OBSERVER = register(new Block("observer", builder().requiresCorrectToolForDrops().destroyTime(3.0f) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN) .booleanState(POWERED))); - public static final Block SHULKER_BOX = register(new Block("shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block SHULKER_BOX = register(new Block("shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block WHITE_SHULKER_BOX = register(new Block("white_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block WHITE_SHULKER_BOX = register(new Block("white_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block ORANGE_SHULKER_BOX = register(new Block("orange_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block ORANGE_SHULKER_BOX = register(new Block("orange_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block MAGENTA_SHULKER_BOX = register(new Block("magenta_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block MAGENTA_SHULKER_BOX = register(new Block("magenta_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block LIGHT_BLUE_SHULKER_BOX = register(new Block("light_blue_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIGHT_BLUE_SHULKER_BOX = register(new Block("light_blue_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block YELLOW_SHULKER_BOX = register(new Block("yellow_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block YELLOW_SHULKER_BOX = register(new Block("yellow_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block LIME_SHULKER_BOX = register(new Block("lime_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIME_SHULKER_BOX = register(new Block("lime_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block PINK_SHULKER_BOX = register(new Block("pink_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PINK_SHULKER_BOX = register(new Block("pink_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block GRAY_SHULKER_BOX = register(new Block("gray_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block GRAY_SHULKER_BOX = register(new Block("gray_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block LIGHT_GRAY_SHULKER_BOX = register(new Block("light_gray_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block LIGHT_GRAY_SHULKER_BOX = register(new Block("light_gray_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block CYAN_SHULKER_BOX = register(new Block("cyan_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block CYAN_SHULKER_BOX = register(new Block("cyan_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block PURPLE_SHULKER_BOX = register(new Block("purple_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block PURPLE_SHULKER_BOX = register(new Block("purple_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block BLUE_SHULKER_BOX = register(new Block("blue_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block BLUE_SHULKER_BOX = register(new Block("blue_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block BROWN_SHULKER_BOX = register(new Block("brown_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block BROWN_SHULKER_BOX = register(new Block("brown_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block GREEN_SHULKER_BOX = register(new Block("green_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block GREEN_SHULKER_BOX = register(new Block("green_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block RED_SHULKER_BOX = register(new Block("red_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block RED_SHULKER_BOX = register(new Block("red_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); - public static final Block BLACK_SHULKER_BOX = register(new Block("black_shulker_box", builder().setBlockEntity().destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block BLACK_SHULKER_BOX = register(new Block("black_shulker_box", builder().setBlockEntity(BlockEntityType.SHULKER_BOX).destroyTime(2.0f).pushReaction(PistonBehavior.DESTROY) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN))); public static final Block WHITE_GLAZED_TERRACOTTA = register(new Block("white_glazed_terracotta", builder().requiresCorrectToolForDrops().destroyTime(1.4f).pushReaction(PistonBehavior.PUSH_ONLY) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); @@ -1787,7 +1788,7 @@ public final class Blocks { .intState(PICKLES) .booleanState(WATERLOGGED))); public static final Block BLUE_ICE = register(new Block("blue_ice", builder().destroyTime(2.8f))); - public static final Block CONDUIT = register(new Block("conduit", builder().setBlockEntity().destroyTime(3.0f) + public static final Block CONDUIT = register(new Block("conduit", builder().setBlockEntity(BlockEntityType.CONDUIT).destroyTime(3.0f) .booleanState(WATERLOGGED))); public static final Block BAMBOO_SAPLING = register(new Block("bamboo_sapling", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.BAMBOO))); public static final Block BAMBOO = register(new Block("bamboo", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY) @@ -2005,13 +2006,13 @@ public final class Blocks { .booleanState(WATERLOGGED))); public static final Block LOOM = register(new Block("loom", builder().destroyTime(2.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block BARREL = register(new Block("barrel", builder().setBlockEntity().destroyTime(2.5f) + public static final Block BARREL = register(new Block("barrel", builder().setBlockEntity(BlockEntityType.BARREL).destroyTime(2.5f) .enumState(FACING, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN) .booleanState(OPEN))); - public static final Block SMOKER = register(new Block("smoker", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) + public static final Block SMOKER = register(new Block("smoker", builder().setBlockEntity(BlockEntityType.SMOKER).requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(LIT))); - public static final Block BLAST_FURNACE = register(new Block("blast_furnace", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) + public static final Block BLAST_FURNACE = register(new Block("blast_furnace", builder().setBlockEntity(BlockEntityType.BLAST_FURNACE).requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(LIT))); public static final Block CARTOGRAPHY_TABLE = register(new Block("cartography_table", builder().destroyTime(2.5f))); @@ -2019,14 +2020,14 @@ public final class Blocks { public static final Block GRINDSTONE = register(new Block("grindstone", builder().requiresCorrectToolForDrops().destroyTime(2.0f).pushReaction(PistonBehavior.BLOCK) .enumState(ATTACH_FACE) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block LECTERN = register(new LecternBlock("lectern", builder().setBlockEntity().destroyTime(2.5f) + public static final Block LECTERN = register(new LecternBlock("lectern", builder().setBlockEntity(BlockEntityType.LECTERN).destroyTime(2.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(HAS_BOOK) .booleanState(POWERED))); public static final Block SMITHING_TABLE = register(new Block("smithing_table", builder().destroyTime(2.5f))); public static final Block STONECUTTER = register(new Block("stonecutter", builder().requiresCorrectToolForDrops().destroyTime(3.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST))); - public static final Block BELL = register(new Block("bell", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(5.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block BELL = register(new Block("bell", builder().setBlockEntity(BlockEntityType.BELL).requiresCorrectToolForDrops().destroyTime(5.0f).pushReaction(PistonBehavior.DESTROY) .enumState(BELL_ATTACHMENT) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(POWERED))); @@ -2036,12 +2037,12 @@ public final class Blocks { public static final Block SOUL_LANTERN = register(new Block("soul_lantern", builder().requiresCorrectToolForDrops().destroyTime(3.5f).pushReaction(PistonBehavior.DESTROY) .booleanState(HANGING) .booleanState(WATERLOGGED))); - public static final Block CAMPFIRE = register(new Block("campfire", builder().setBlockEntity().destroyTime(2.0f) + public static final Block CAMPFIRE = register(new Block("campfire", builder().setBlockEntity(BlockEntityType.CAMPFIRE).destroyTime(2.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(LIT) .booleanState(SIGNAL_FIRE) .booleanState(WATERLOGGED))); - public static final Block SOUL_CAMPFIRE = register(new Block("soul_campfire", builder().setBlockEntity().destroyTime(2.0f) + public static final Block SOUL_CAMPFIRE = register(new Block("soul_campfire", builder().setBlockEntity(BlockEntityType.CAMPFIRE).destroyTime(2.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(LIT) .booleanState(SIGNAL_FIRE) @@ -2155,30 +2156,30 @@ public final class Blocks { .enumState(DOOR_HINGE) .booleanState(OPEN) .booleanState(POWERED))); - public static final Block CRIMSON_SIGN = register(new Block("crimson_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CRIMSON_SIGN = register(new Block("crimson_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block WARPED_SIGN = register(new Block("warped_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block WARPED_SIGN = register(new Block("warped_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .intState(ROTATION_16) .booleanState(WATERLOGGED))); - public static final Block CRIMSON_WALL_SIGN = register(new Block("crimson_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block CRIMSON_WALL_SIGN = register(new Block("crimson_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block WARPED_WALL_SIGN = register(new Block("warped_wall_sign", builder().setBlockEntity().destroyTime(1.0f) + public static final Block WARPED_WALL_SIGN = register(new Block("warped_wall_sign", builder().setBlockEntity(BlockEntityType.SIGN).destroyTime(1.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block STRUCTURE_BLOCK = register(new Block("structure_block", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(-1.0f) + public static final Block STRUCTURE_BLOCK = register(new Block("structure_block", builder().setBlockEntity(BlockEntityType.STRUCTURE_BLOCK).requiresCorrectToolForDrops().destroyTime(-1.0f) .enumState(STRUCTUREBLOCK_MODE))); - public static final Block JIGSAW = register(new Block("jigsaw", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(-1.0f) + public static final Block JIGSAW = register(new Block("jigsaw", builder().setBlockEntity(BlockEntityType.JIGSAW).requiresCorrectToolForDrops().destroyTime(-1.0f) .enumState(ORIENTATION, FrontAndTop.VALUES))); public static final Block COMPOSTER = register(new Block("composter", builder().destroyTime(0.6f) .intState(LEVEL_COMPOSTER))); public static final Block TARGET = register(new Block("target", builder().destroyTime(0.5f) .intState(POWER))); - public static final Block BEE_NEST = register(new Block("bee_nest", builder().setBlockEntity().destroyTime(0.3f) + public static final Block BEE_NEST = register(new Block("bee_nest", builder().setBlockEntity(BlockEntityType.BEEHIVE).destroyTime(0.3f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .intState(LEVEL_HONEY))); - public static final Block BEEHIVE = register(new Block("beehive", builder().setBlockEntity().destroyTime(0.6f) + public static final Block BEEHIVE = register(new Block("beehive", builder().setBlockEntity(BlockEntityType.BEEHIVE).destroyTime(0.6f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .intState(LEVEL_HONEY))); public static final Block HONEY_BLOCK = register(new HoneyBlock("honey_block", builder())); @@ -2422,11 +2423,11 @@ public final class Blocks { public static final Block CALCITE = register(new Block("calcite", builder().requiresCorrectToolForDrops().destroyTime(0.75f))); public static final Block TINTED_GLASS = register(new Block("tinted_glass", builder().destroyTime(0.3f))); public static final Block POWDER_SNOW = register(new Block("powder_snow", builder().destroyTime(0.25f))); - public static final Block SCULK_SENSOR = register(new Block("sculk_sensor", builder().setBlockEntity().destroyTime(1.5f) + public static final Block SCULK_SENSOR = register(new Block("sculk_sensor", builder().setBlockEntity(BlockEntityType.SCULK_SENSOR).destroyTime(1.5f) .intState(POWER) .enumState(SCULK_SENSOR_PHASE) .booleanState(WATERLOGGED))); - public static final Block CALIBRATED_SCULK_SENSOR = register(new Block("calibrated_sculk_sensor", builder().setBlockEntity().destroyTime(1.5f) + public static final Block CALIBRATED_SCULK_SENSOR = register(new Block("calibrated_sculk_sensor", builder().setBlockEntity(BlockEntityType.CALIBRATED_SCULK_SENSOR).destroyTime(1.5f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .intState(POWER) .enumState(SCULK_SENSOR_PHASE) @@ -2440,9 +2441,9 @@ public final class Blocks { .booleanState(UP) .booleanState(WATERLOGGED) .booleanState(WEST))); - public static final Block SCULK_CATALYST = register(new Block("sculk_catalyst", builder().setBlockEntity().destroyTime(3.0f) + public static final Block SCULK_CATALYST = register(new Block("sculk_catalyst", builder().setBlockEntity(BlockEntityType.SCULK_CATALYST).destroyTime(3.0f) .booleanState(BLOOM))); - public static final Block SCULK_SHRIEKER = register(new Block("sculk_shrieker", builder().setBlockEntity().destroyTime(3.0f) + public static final Block SCULK_SHRIEKER = register(new Block("sculk_shrieker", builder().setBlockEntity(BlockEntityType.SCULK_SHRIEKER).destroyTime(3.0f) .booleanState(CAN_SUMMON) .booleanState(SHRIEKING) .booleanState(WATERLOGGED))); @@ -2794,18 +2795,18 @@ public final class Blocks { .enumState(AXIS, Axis.VALUES))); public static final Block FROGSPAWN = register(new Block("frogspawn", builder().pushReaction(PistonBehavior.DESTROY))); public static final Block REINFORCED_DEEPSLATE = register(new Block("reinforced_deepslate", builder().destroyTime(55.0f))); - public static final Block DECORATED_POT = register(new Block("decorated_pot", builder().setBlockEntity().pushReaction(PistonBehavior.DESTROY) + public static final Block DECORATED_POT = register(new Block("decorated_pot", builder().setBlockEntity(BlockEntityType.DECORATED_POT).pushReaction(PistonBehavior.DESTROY) .booleanState(CRACKED) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(WATERLOGGED))); - public static final Block CRAFTER = register(new Block("crafter", builder().setBlockEntity().destroyTime(1.5f) + public static final Block CRAFTER = register(new Block("crafter", builder().setBlockEntity(BlockEntityType.CRAFTER).destroyTime(1.5f) .booleanState(CRAFTING) .enumState(ORIENTATION, FrontAndTop.VALUES) .booleanState(TRIGGERED))); - public static final Block TRIAL_SPAWNER = register(new Block("trial_spawner", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(50.0f) + public static final Block TRIAL_SPAWNER = register(new Block("trial_spawner", builder().setBlockEntity(BlockEntityType.TRIAL_SPAWNER).destroyTime(50.0f) .booleanState(OMINOUS) .enumState(TRIAL_SPAWNER_STATE))); - public static final Block VAULT = register(new Block("vault", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(50.0f) + public static final Block VAULT = register(new Block("vault", builder().setBlockEntity(BlockEntityType.VAULT).destroyTime(50.0f) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .booleanState(OMINOUS) .enumState(VAULT_STATE))); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java index 8028a4355..782f664fc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java @@ -16,7 +16,6 @@ public class GeyserJavaBlockState implements JavaBlockState { boolean canBreakWithHand; String pickItem; String pistonBehavior; - boolean hasBlockEntity; private GeyserJavaBlockState(Builder builder) { this.identifier = builder.identifier; @@ -28,7 +27,6 @@ public class GeyserJavaBlockState implements JavaBlockState { this.canBreakWithHand = builder.canBreakWithHand; this.pickItem = builder.pickItem; this.pistonBehavior = builder.pistonBehavior; - this.hasBlockEntity = builder.hasBlockEntity; } @Override @@ -76,9 +74,10 @@ public class GeyserJavaBlockState implements JavaBlockState { return pistonBehavior; } + @SuppressWarnings("removal") @Override public boolean hasBlockEntity() { - return hasBlockEntity; + return false; } public static class Builder implements JavaBlockState.Builder { @@ -91,7 +90,6 @@ public class GeyserJavaBlockState implements JavaBlockState { private boolean canBreakWithHand; private String pickItem; private String pistonBehavior; - private boolean hasBlockEntity; @Override public Builder identifier(@NonNull String identifier) { @@ -147,9 +145,13 @@ public class GeyserJavaBlockState implements JavaBlockState { return this; } + @SuppressWarnings("removal") @Override public Builder hasBlockEntity(boolean hasBlockEntity) { - this.hasBlockEntity = hasBlockEntity; + // keep the current behavior + if (this.pistonBehavior == null && hasBlockEntity) { + this.pistonBehavior = "BLOCK"; + } return this; } diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java index ee99b652a..a005fc103 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java @@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; @@ -41,6 +42,7 @@ import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; import org.intellij.lang.annotations.Subst; import java.util.*; @@ -55,7 +57,7 @@ public class Block { * Can you harvest this with your hand. */ private final boolean requiresCorrectToolForDrops; - private final boolean hasBlockEntity; + private final @Nullable BlockEntityType blockEntityType; private final float destroyTime; private final @NonNull PistonBehavior pushReaction; /** @@ -75,7 +77,7 @@ public class Block { public Block(@Subst("empty") String javaIdentifier, Builder builder) { this.javaIdentifier = Key.key(javaIdentifier); this.requiresCorrectToolForDrops = builder.requiresCorrectToolForDrops; - this.hasBlockEntity = builder.hasBlockEntity; + this.blockEntityType = builder.blockEntityType; this.destroyTime = builder.destroyTime; this.pushReaction = builder.pushReaction; this.pickItem = builder.pickItem; @@ -181,7 +183,12 @@ public class Block { } public boolean hasBlockEntity() { - return hasBlockEntity; + return blockEntityType != null; + } + + @Nullable + public BlockEntityType blockEntityType() { + return blockEntityType; } public float destroyTime() { @@ -227,7 +234,7 @@ public class Block { public static final class Builder { private final Map, List>> states = new LinkedHashMap<>(); private boolean requiresCorrectToolForDrops = false; - private boolean hasBlockEntity = false; + private BlockEntityType blockEntityType = null; private PistonBehavior pushReaction = PistonBehavior.NORMAL; private float destroyTime; private Supplier pickItem; @@ -271,8 +278,8 @@ public class Block { return this; } - public Builder setBlockEntity() { - this.hasBlockEntity = true; + public Builder setBlockEntity(BlockEntityType blockEntityType) { + this.blockEntityType = blockEntityType; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/BlockState.java b/core/src/main/java/org/geysermc/geyser/level/block/type/BlockState.java index 36c31f32e..2513e3ceb 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/BlockState.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/type/BlockState.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.level.block.type; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.level.block.property.Property; import org.geysermc.geyser.registry.BlockRegistries; @@ -65,12 +66,13 @@ public final class BlockState { return (T) get(property); } - public boolean getValue(Property property, boolean def) { + public > T getValue(Property property, T def) { var value = get(property); if (value == null) { return def; } - return (Boolean) value; + //noinspection unchecked + return (T) value; } @Nullable @@ -183,7 +185,14 @@ public final class BlockState { return builder.toString(); } + /** + * Null-safe method that looks up a Java block state ID in the BLOCK_STATES registry, and defaults to air if not found. + * + * @param javaId the Java block state ID to look up. + * @return the corresponding block state, or air if the given ID wasn't registered and returned null. + */ + @NonNull public static BlockState of(int javaId) { - return BlockRegistries.BLOCK_STATES.get(javaId); + return BlockRegistries.BLOCK_STATES.getOrDefault(javaId, BlockRegistries.BLOCK_STATES.get(Block.JAVA_AIR_ID)); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 1c58288c7..de995301a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -48,7 +48,7 @@ public final class GameProtocol { * release of the game that Geyser supports. */ public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() - .minecraftVersion("1.21.0") + .minecraftVersion("1.21.1") .build()); /** @@ -67,7 +67,7 @@ public final class GameProtocol { .minecraftVersion("1.20.80/1.20.81") .build())); SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(DEFAULT_BEDROCK_CODEC.toBuilder() - .minecraftVersion("1.21.0") + .minecraftVersion("1.21.0/1.20.1") .build())); } @@ -106,7 +106,7 @@ public final class GameProtocol { * @return the supported Minecraft: Java Edition version names */ public static List getJavaVersions() { - return List.of("1.20.5", DEFAULT_JAVA_CODEC.getMinecraftVersion()); + return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion()); } /** diff --git a/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java deleted file mode 100644 index 981ed0f8c..000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.registry; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.registry.loader.RegistryLoader; - -import java.util.function.Supplier; - -/** - * A mapped registry with an integer as the key. This class is designed to minimize the need for boxing/unboxing keys. - * - * @param the value - */ -public class IntMappedRegistry extends AbstractMappedRegistry> { - protected IntMappedRegistry(I input, RegistryLoader> registryLoader) { - super(input, registryLoader); - } - - /** - * Returns the value registered by the given integer. - * - * @param i the integer - * @return the value registered by the given integer. - */ - public V get(int i) { - return this.mappings.get(i); - } - - @Nullable - @Override - @Deprecated - public V get(Integer key) { - return super.get(key); - } - - /** - * Returns the value registered by the given key or the default value - * specified if null. - * - * @param i the key - * @param defaultValue the default value - * @return the value registered by the given key or the default value - * specified if null. - */ - public V getOrDefault(int i, V defaultValue) { - return this.mappings.getOrDefault(i, defaultValue); - } - - @Override - @Deprecated - public V getOrDefault(Integer key, V defaultValue) { - return super.getOrDefault(key, defaultValue); - } - - /** - * Registers a new value into this registry with the given key. - * - * @param i the key - * @param value the value - * @return a new value into this registry with the given key. - */ - public V register(int i, V value) { - return this.mappings.put(i, value); - } - - @Override - @Deprecated - public V register(Integer key, V value) { - return super.register(key, value); - } - - /** - * Creates a new integer mapped registry with the given {@link RegistryLoader}. The - * input type is not specified here, meaning the loader return type is either - * predefined, or the registry is populated at a later point. - * - * @param registryLoader the registry loader - * @param the input - * @param the map value - * @return a new registry with the given RegistryLoader - */ - public static IntMappedRegistry create(RegistryLoader> registryLoader) { - return new IntMappedRegistry<>(null, registryLoader); - } - - /** - * Creates a new integer mapped registry with the given {@link RegistryLoader} and input. - * - * @param registryLoader the registry loader - * @param the input - * @param the map value - * @return a new registry with the given RegistryLoader supplier - */ - public static IntMappedRegistry create(I input, Supplier>> registryLoader) { - return new IntMappedRegistry<>(input, registryLoader.get()); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index b509d6ac0..30d3c0763 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -25,33 +25,23 @@ package org.geysermc.geyser.registry; -import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; -import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; -import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent; -import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; -import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType; -import org.geysermc.mcprotocollib.network.packet.Packet; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.pack.ResourcePack; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.loader.*; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; -import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; +import org.geysermc.geyser.registry.loader.RecipeRegistryLoader; import org.geysermc.geyser.registry.provider.ProviderSupplier; -import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.registry.type.SoundMapping; @@ -59,6 +49,12 @@ import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; import org.geysermc.geyser.translator.level.event.LevelEventTranslator; import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; import org.geysermc.geyser.translator.sound.SoundTranslator; +import org.geysermc.mcprotocollib.network.packet.Packet; +import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; +import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent; +import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; +import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType; import java.util.*; @@ -97,16 +93,6 @@ public final class Registries { */ public static final SimpleMappedRegistry BLOCK_ENTITIES = SimpleMappedRegistry.create("org.geysermc.geyser.translator.level.block.entity.BlockEntity", BlockEntityRegistryLoader::new); - /** - * A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link RecipeData}. - */ - public static final VersionedRegistry>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); - - /** - * A registry holding data of all the known enchantments. - */ - public static final SimpleMappedRegistry ENCHANTMENTS; - /** * A map containing all entity types and their respective Geyser definitions */ @@ -154,13 +140,7 @@ public final class Registries { /** * A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value. */ - public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); - - /** - * A mapped registry holding the available records, with the ID of the record being the key, and the {@link org.cloudburstmc.protocol.bedrock.data.SoundEvent} - * as the value. - */ - public static final IntMappedRegistry RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final SimpleMappedRegistry> RECIPES = SimpleMappedRegistry.create("mappings/recipes.nbt", RecipeRegistryLoader::new); /** * A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys. @@ -189,11 +169,9 @@ public final class Registries { static { PacketRegistryPopulator.populate(); ItemRegistryPopulator.populate(); - RecipeRegistryPopulator.populate(); // Create registries that require other registries to load first POTION_MIXES = VersionedRegistry.create(PotionMixRegistryLoader::new); - ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new); // Remove unneeded client generation data from NbtMapBuilder NbtMapBuilder biomesNbt = NbtMap.builder(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java deleted file mode 100644 index c1a50de96..000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.registry.loader; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; -import org.geysermc.geyser.item.type.Item; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.EnchantmentData; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.Map; - -public class EnchantmentRegistryLoader implements RegistryLoader> { - @Override - public Map load(String input) { - JsonObject enchantmentsJson; - try (InputStream enchantmentsStream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) { - enchantmentsJson = new JsonParser().parse(new InputStreamReader(enchantmentsStream)).getAsJsonObject(); - } catch (Exception e) { - throw new AssertionError("Unable to load enchantment data", e); - } - - Map enchantments = new EnumMap<>(JavaEnchantment.class); - for (Map.Entry entry : enchantmentsJson.entrySet()) { - JavaEnchantment key = JavaEnchantment.getByJavaIdentifier(entry.getKey()); - JsonObject node = entry.getValue().getAsJsonObject(); - int rarityMultiplier = node.get("anvil_cost").getAsInt(); - int maxLevel = node.get("max_level").getAsInt(); - - EnumSet incompatibleEnchantments = EnumSet.noneOf(JavaEnchantment.class); - JsonArray incompatibleEnchantmentsJson = node.getAsJsonArray("incompatible_enchantments"); - if (incompatibleEnchantmentsJson != null) { - for (JsonElement incompatibleNode : incompatibleEnchantmentsJson) { - incompatibleEnchantments.add(JavaEnchantment.getByJavaIdentifier(incompatibleNode.getAsString())); - } - } - - IntSet validItems = new IntOpenHashSet(); - for (JsonElement itemNode : node.getAsJsonArray("valid_items")) { - String javaIdentifier = itemNode.getAsString(); - Item item = Registries.JAVA_ITEM_IDENTIFIERS.get(javaIdentifier); - if (item != null) { - validItems.add(item.javaId()); - } else { - throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier); - } - } - - EnchantmentData enchantmentData = new EnchantmentData(rarityMultiplier, maxLevel, incompatibleEnchantments, validItems); - enchantments.put(key, enchantmentData); - } - return enchantments; - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java index 0eec7cdb6..eae4e2bea 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.registry.loader; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData; import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.item.Items; @@ -75,6 +74,11 @@ public class PotionMixRegistryLoader implements RegistryLoader inputs = List.of( getNonNull(mappings, Items.POTION), diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/RecipeRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/RecipeRegistryLoader.java new file mode 100644 index 000000000..f1d0c456f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/RecipeRegistryLoader.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019-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.registry.loader; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.cloudburstmc.nbt.NBTInputStream; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec; +import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient; +import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +/** + * Populates the recipe registry with some recipes that Java does not send, to ensure they show up as intended + * in the recipe book. + */ +public final class RecipeRegistryLoader implements RegistryLoader>> { + + @Override + public Map> load(String input) { + Map> deserializedRecipes = new Object2ObjectOpenHashMap<>(); + + List recipes; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.nbt")) { + try (NBTInputStream nbtStream = new NBTInputStream(new DataInputStream(stream))) { + recipes = ((NbtMap) nbtStream.readTag()).getList("recipes", NbtType.COMPOUND); + } + } catch (Exception e) { + throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + } + + MinecraftCodecHelper helper = MinecraftCodec.CODEC.getHelperFactory().get(); + for (NbtMap recipeCollection : recipes) { + var pair = getRecipes(recipeCollection, helper); + deserializedRecipes.put(pair.key(), pair.value()); + } + return deserializedRecipes; + } + + private static Pair> getRecipes(NbtMap recipes, MinecraftCodecHelper helper) { + List typedRecipes = recipes.getList("recipes", NbtType.COMPOUND); + RecipeType recipeType = RecipeType.from(recipes.getInt("recipe_type", -1)); + if (recipeType == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) { + return Pair.of(recipeType, getShapedRecipes(typedRecipes, helper)); + } else { + return Pair.of(recipeType, getShapelessRecipes(typedRecipes, helper)); + } + } + + private static List getShapelessRecipes(List recipes, MinecraftCodecHelper helper) { + List deserializedRecipes = new ObjectArrayList<>(recipes.size()); + for (NbtMap recipe : recipes) { + ItemStack output = toItemStack(recipe.getCompound("output"), helper); + List rawInputs = recipe.getList("inputs", NbtType.COMPOUND); + Ingredient[] javaInputs = new Ingredient[rawInputs.size()]; + for (int i = 0; i < rawInputs.size(); i++) { + javaInputs[i] = new Ingredient(new ItemStack[] {toItemStack(rawInputs.get(i), helper)}); + } + deserializedRecipes.add(new GeyserShapelessRecipe(javaInputs, output)); + } + return deserializedRecipes; + } + + private static List getShapedRecipes(List recipes, MinecraftCodecHelper helper) { + List deserializedRecipes = new ObjectArrayList<>(recipes.size()); + for (NbtMap recipe : recipes) { + ItemStack output = toItemStack(recipe.getCompound("output"), helper); + List shape = recipe.getList("shape", NbtType.INT_ARRAY); + + // In the recipes mapping, each recipe is mapped by a number + List letterToRecipe = new ArrayList<>(); + for (NbtMap rawInput : recipe.getList("inputs", NbtType.COMPOUND)) { + letterToRecipe.add(toItemStack(rawInput, helper)); + } + + Ingredient[] inputs = new Ingredient[shape.size() * shape.get(0).length]; + int i = 0; + // Create a linear array of items from the "cube" of the shape + for (int j = 0; i < shape.size() * shape.get(0).length; j++) { + for (int index : shape.get(j)) { + ItemStack stack = letterToRecipe.get(index); + inputs[i++] = new Ingredient(new ItemStack[] {stack}); + } + } + deserializedRecipes.add(new GeyserShapedRecipe(shape.size(), shape.get(0).length, inputs, output)); + } + return deserializedRecipes; + } + + /** + * Converts our serialized NBT into an ItemStack. + * id is the Java item ID as an integer, components is an optional String of the data components serialized + * as bytes in Base64 (so MCProtocolLib can parse the data). + */ + private static ItemStack toItemStack(NbtMap nbt, MinecraftCodecHelper helper) { + int id = nbt.getInt("id"); + int count = nbt.getInt("count", 1); + String componentsRaw = nbt.getString("components", null); + if (componentsRaw != null) { + byte[] bytes = Base64.getDecoder().decode(componentsRaw); + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + DataComponents components = helper.readDataComponentPatch(buf); + return new ItemStack(id, count, components); + } + return new ItemStack(id, count); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index d32d5bc09..b5e25a4ba 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.registry.mappings.versions; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; -import org.geysermc.mcprotocollib.protocol.data.game.Identifier; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,14 +34,9 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockPermutation; import org.geysermc.geyser.api.block.custom.CustomBlockState; -import org.geysermc.geyser.api.block.custom.component.BoxComponent; -import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; -import org.geysermc.geyser.api.block.custom.component.GeometryComponent; -import org.geysermc.geyser.api.block.custom.component.MaterialInstance; -import org.geysermc.geyser.api.block.custom.component.PlacementConditions; +import org.geysermc.geyser.api.block.custom.component.*; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face; -import org.geysermc.geyser.api.block.custom.component.TransformationComponent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.util.CreativeCategory; @@ -60,16 +54,10 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockStateMapping; import org.geysermc.geyser.translator.collision.BlockCollision; import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.MathUtils; +import org.geysermc.geyser.util.MinecraftKey; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -131,7 +119,7 @@ public class MappingsReader_v1 extends MappingsReader { blocksNode.fields().forEachRemaining(entry -> { if (entry.getValue().isObject()) { try { - String identifier = Identifier.formalize(entry.getKey()); + String identifier = MinecraftKey.key(entry.getKey()).asString(); CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue()); consumer.accept(identifier, customBlockMapping); } catch (Exception e) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 272522429..d7dc989da 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -435,9 +435,6 @@ public final class BlockRegistryPopulator { if (!javaBlockState.canBreakWithHand()) { builder.requiresCorrectToolForDrops(); } - if (javaBlockState.hasBlockEntity()) { - builder.setBlockEntity(); - } String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier()); String pickItem = javaBlockState.pickItem(); Block block = new Block(cleanJavaIdentifier, builder) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java index 0b0731707..58886ca57 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.registry.populator; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; +import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.type.GeyserMappingItem; @@ -41,10 +42,21 @@ public class Conversion685_671 { private static final List OMINOUS_BLOCKS = List.of("minecraft:trial_spawner", "minecraft:vault"); private static final List NEW_BLOCKS = Stream.of(NEW_CORAL_BLOCKS, NEW_DOUBLE_PLANTS, NEW_STONE_BLOCK_SLABS, NEW_TALLGRASSES).flatMap(List::stream).toList(); private static final List MODIFIED_BLOCKS = Stream.of(NEW_BLOCKS, OMINOUS_BLOCKS).flatMap(List::stream).toList(); + private static final List NEW_MUSIC_DISCS = List.of(Items.MUSIC_DISC_CREATOR, Items.MUSIC_DISC_CREATOR_MUSIC_BOX, Items.MUSIC_DISC_PRECIPICE); - static GeyserMappingItem remapItem(@SuppressWarnings("unused") Item item, GeyserMappingItem mapping) { + static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) { String identifer = mapping.getBedrockIdentifier(); + if (NEW_MUSIC_DISCS.contains(item)) { + return mapping.withBedrockIdentifier("minecraft:music_disc_otherside"); + } + if (item == Items.OMINOUS_TRIAL_KEY) { + return mapping.withBedrockIdentifier("minecraft:trial_key"); + } + if (item == Items.OMINOUS_BOTTLE) { + return mapping.withBedrockIdentifier("minecraft:glass_bottle"); + } + if (!NEW_BLOCKS.contains(identifer)) { return mapping; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 8f515c1cb..e19066462 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -41,7 +41,6 @@ import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtUtils; import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671; import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685; -import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; @@ -458,10 +457,6 @@ public class ItemRegistryPopulator { if (javaItem.javaIdentifier().contains("bucket") && !javaItem.javaIdentifier().contains("milk")) { buckets.add(definition); - } else if (javaItem.javaIdentifier().startsWith("minecraft:music_disc_")) { - // The Java record level event uses the item ID as the "key" to play the record - Registries.RECORDS.register(javaItem.javaId(), SoundEvent.valueOf("RECORD_" + - mapping.getBedrockIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH))); } mappings.add(mapping); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java deleted file mode 100644 index 4c6d53518..000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.registry.populator; - -import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; -import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient; -import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtUtils; -import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.MultiRecipeData; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData; -import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData; -import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.recipe.GeyserRecipe; -import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; -import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.registry.type.ItemMappings; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.translator.item.ItemTranslator; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID; - -/** - * Populates the recipe registry. - */ -public class RecipeRegistryPopulator { - - public static void populate() { - JsonNode items; - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.json")) { - items = GeyserImpl.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - - int currentRecipeId = LAST_RECIPE_NET_ID; - for (Int2ObjectMap.Entry version : Registries.ITEMS.get().int2ObjectEntrySet()) { - // Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions - LAST_RECIPE_NET_ID = currentRecipeId; - Map> craftingData = new EnumMap<>(RecipeType.class); - Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); - - craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING, - Collections.singletonList(MultiRecipeData.of(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID))); - craftingData.put(RecipeType.CRAFTING_SPECIAL_REPAIRITEM, - Collections.singletonList(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000001"), ++LAST_RECIPE_NET_ID))); - craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPEXTENDING, - Collections.singletonList(MultiRecipeData.of(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), ++LAST_RECIPE_NET_ID))); - craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPCLONING, - Collections.singletonList(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), ++LAST_RECIPE_NET_ID))); - - // https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php - - for (JsonNode entry : items.get("leather_armor")) { - // This won't be perfect, as we can't possibly send every leather input for every kind of color - // But it does display the correct output from a base leather armor, and besides visuals everything works fine - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_ARMORDYE, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - for (JsonNode entry : items.get("firework_rockets")) { - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_ROCKET, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - for (JsonNode entry : items.get("firework_stars")) { - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_STAR, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - for (JsonNode entry : items.get("shulker_boxes")) { - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SHULKERBOXCOLORING, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - for (JsonNode entry : items.get("suspicious_stew")) { - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SUSPICIOUSSTEW, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - for (JsonNode entry : items.get("tipped_arrows")) { - craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_TIPPEDARROW, - c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); - } - - Registries.CRAFTING_DATA.register(version.getIntKey(), craftingData); - Registries.RECIPES.register(version.getIntKey(), recipes); - } - } - - /** - * Computes a Bedrock crafting recipe from the given JSON data. - * @param node the JSON data to compute - * @param recipes a list of all the recipes - * @return the {@link RecipeData} to send to the Bedrock client. - */ - private static RecipeData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { - int netId = ++LAST_RECIPE_NET_ID; - int type = node.get("bedrockRecipeType").asInt(); - JsonNode outputNode = node.get("output"); - ItemMapping outputEntry = mappings.getMapping(outputNode.get("identifier").asText()); - ItemData output = getBedrockItemFromIdentifierJson(outputEntry, outputNode); - UUID uuid = UUID.randomUUID(); - if (type == 1) { - // Shaped recipe - List shape = new ArrayList<>(); - // Get the shape of the recipe - for (JsonNode chars : node.get("shape")) { - shape.add(chars.asText()); - } - - // In recipes.json each recipe is mapped by a letter - Map letterToRecipe = new HashMap<>(); - Iterator> iterator = node.get("inputs").fields(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - JsonNode inputNode = entry.getValue(); - ItemMapping inputEntry = mappings.getMapping(inputNode.get("identifier").asText()); - letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode)); - } - - List inputs = new ArrayList<>(shape.size() * shape.get(0).length()); - int i = 0; - // Create a linear array of items from the "cube" of the shape - for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { - for (char c : shape.get(j).toCharArray()) { - ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); - inputs.add(data); - i++; - } - } - - /* Convert into a Java recipe class for autocrafting */ - List ingredients = new ArrayList<>(); - for (ItemData input : inputs) { - ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); - } - GeyserRecipe recipe = new GeyserShapedRecipe(shape.get(0).length(), shape.size(), - ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - recipes.put(netId, recipe); - /* Convert end */ - - return ShapedRecipeData.shaped(uuid.toString(), shape.get(0).length(), shape.size(), - inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID); - } - List inputs = new ObjectArrayList<>(); - for (JsonNode entry : node.get("inputs")) { - ItemMapping inputEntry = mappings.getMapping(entry.get("identifier").asText()); - inputs.add(getBedrockItemFromIdentifierJson(inputEntry, entry)); - } - - /* Convert into a Java Recipe class for autocrafting */ - List ingredients = new ArrayList<>(); - for (ItemData input : inputs) { - ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); - } - GeyserRecipe recipe = new GeyserShapelessRecipe(ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - recipes.put(netId, recipe); - /* Convert end */ - - if (type == 5) { - // Shulker box - return ShapelessRecipeData.shulkerBox(uuid.toString(), - inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId); - } - return ShapelessRecipeData.shapeless(uuid.toString(), - inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID); - } - - private static ItemData getBedrockItemFromIdentifierJson(ItemMapping mapping, JsonNode itemNode) { - int count = 1; - short damage = 0; - NbtMap tag = null; - JsonNode damageNode = itemNode.get("bedrockDamage"); - if (damageNode != null) { - damage = damageNode.numberValue().shortValue(); - } - JsonNode countNode = itemNode.get("count"); - if (countNode != null) { - count = countNode.asInt(); - } - JsonNode nbtNode = itemNode.get("bedrockNbt"); - if (nbtNode != null) { - byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - try { - tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return ItemData.builder() - .definition(mapping.getBedrockDefinition()) - .damage(damage) - .count(count) - .blockDefinition(mapping.getBedrockBlockDefinition()) - .tag(tag) - .build(); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java index 94c863660..189474238 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.registry.type; -import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import lombok.Builder; @@ -41,7 +40,7 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; -import org.geysermc.geyser.item.type.PotionItem; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.util.List; import java.util.Map; @@ -148,10 +147,8 @@ public class ItemMappings implements DefinitionRegistry { } } else { if (!(mapping.getBedrockData() == data.getDamage() || - // Make exceptions for potions, tipped arrows, firework stars, goat horns, and suspicious stews, whose damage values can vary - (mapping.getJavaItem() instanceof PotionItem || mapping.getJavaItem() == Items.ARROW - || mapping.getJavaItem() == Items.FIREWORK_STAR || mapping.getJavaItem() == Items.GOAT_HORN - || mapping.getJavaItem() == Items.SUSPICIOUS_STEW))) { + // Make exceptions for items whose damage values can vary + (mapping.getJavaItem().ignoreDamage() || mapping.getJavaItem() == Items.SUSPICIOUS_STEW))) { continue; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index fc000b95f..7f015a05e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -40,6 +40,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; @@ -290,7 +291,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Keeps track of the world name for respawning. */ @Setter - private String worldName = null; + private Key worldName = null; /** * As of Java 1.19.3, the client only uses these for commands. */ @@ -356,8 +357,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Stores all Java recipes by recipe identifier, and matches them to all possible Bedrock recipe identifiers. * They are not 1:1, since Bedrock can have multiple recipes for the same Java recipe. */ - @Setter - private Map> javaToBedrockRecipeIds; + private final Map> javaToBedrockRecipeIds; @Setter private Int2ObjectMap craftingRecipes; @@ -996,7 +996,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public void disconnected(DisconnectedEvent event) { loggingIn = false; - loggedIn = false; String disconnectMessage; Throwable cause = event.getCause(); @@ -1036,13 +1035,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } else { GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause); } - // GeyserSession is disconnected via session.disconnect() called indirectly be the server - // This only needs to be "initiated" here when there is an exception, hence the cause clause - GeyserSession.this.disconnect(disconnectMessage); if (geyser.getConfig().isDebugMode()) { cause.printStackTrace(); } } + if ((!GeyserSession.this.closed && GeyserSession.this.loggedIn) || cause != null) { + // GeyserSession is disconnected via session.disconnect() called indirectly be the server + // This needs to be "initiated" here when there is an exception, but also when the Netty connection + // is closed without a disconnect packet - in this case, closed will still be false, but loggedIn + // will also be true as GeyserSession#disconnect will not have been called. + GeyserSession.this.disconnect(disconnectMessage); + } + + loggedIn = false; } @Override @@ -1307,22 +1312,28 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.cameraData.handleGameModeChange(currentlySpectator, newGamemode); } + /** + * Convenience method to reduce amount of duplicate code. Sends ServerboundUseItemPacket. + */ + public void useItem(Hand hand) { + sendDownstreamGamePacket(new ServerboundUseItemPacket( + hand, worldCache.nextPredictionSequence(), playerEntity.getYaw(), playerEntity.getPitch())); + } + /** * Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display * blocking and sends a packet to the Java server. */ private boolean attemptToBlock() { - ServerboundUseItemPacket useItemPacket; if (playerInventory.getItemInHand().asItem() == Items.SHIELD) { - useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND, worldCache.nextPredictionSequence()); + useItem(Hand.MAIN_HAND); } else if (playerInventory.getOffhand().asItem() == Items.SHIELD) { - useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND, worldCache.nextPredictionSequence()); + useItem(Hand.OFF_HAND); } else { // No blocking return false; } - sendDownstreamGamePacket(useItemPacket); playerEntity.setFlag(EntityFlag.BLOCKING, true); // Metadata should be updated later return true; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java index f66daf027..ee8ebb13f 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.session.cache; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos; @@ -64,7 +65,7 @@ public final class LodestoneCache { int x = position.getX(); int y = position.getY(); int z = position.getZ(); - String dim = position.getDimension(); + Key dim = position.getDimension(); for (LodestonePos pos : this.activeLodestones.values()) { if (pos.equals(x, y, z, dim)) { @@ -98,7 +99,7 @@ public final class LodestoneCache { int x = position.getX(); int y = position.getY(); int z = position.getZ(); - String dim = position.getDimension(); + Key dim = position.getDimension(); for (LodestonePos pos : this.activeLodestones.values()) { if (pos.equals(x, y, z, dim)) { @@ -138,8 +139,8 @@ public final class LodestoneCache { this.lodestones.clear(); } - public record LodestonePos(int id, int x, int y, int z, String dimension) { - boolean equals(int x, int y, int z, String dimension) { + public record LodestonePos(int id, int x, int y, int z, Key dimension) { + boolean equals(int x, int y, int z, Key dimension) { return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java index fa4503635..3121af369 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java @@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; +import net.kyori.adventure.key.Key; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; @@ -38,14 +39,19 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; import org.geysermc.geyser.inventory.item.BannerPattern; import org.geysermc.geyser.inventory.recipe.TrimRecipe; +import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.level.JavaDimension; +import org.geysermc.geyser.level.JukeboxSong; +import org.geysermc.geyser.level.PaintingType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.JavaRegistry; import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; +import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; import org.geysermc.mcprotocollib.protocol.packet.configuration.clientbound.ClientboundRegistryDataPacket; import java.util.ArrayList; @@ -66,28 +72,31 @@ import java.util.function.ToIntFunction; @Accessors(fluent = true) @Getter public final class RegistryCache { - private static final Map> DEFAULTS; - private static final Map>> REGISTRIES = new HashMap<>(); + private static final Map> DEFAULTS; + private static final Map>> REGISTRIES = new HashMap<>(); static { register("chat_type", cache -> cache.chatTypes, ($, entry) -> TextDecoration.readChatType(entry)); register("dimension_type", cache -> cache.dimensions, ($, entry) -> JavaDimension.read(entry)); + register("enchantment", cache -> cache.enchantments, ($, entry) -> Enchantment.read(entry)); + register("jukebox_song", cache -> cache.jukeboxSongs, ($, entry) -> JukeboxSong.read(entry)); + register("painting_variant", cache -> cache.paintings, ($, entry) -> PaintingType.getByName(entry.getId())); register("trim_material", cache -> cache.trimMaterials, TrimRecipe::readTrimMaterial); register("trim_pattern", cache -> cache.trimPatterns, TrimRecipe::readTrimPattern); register("worldgen/biome", (cache, array) -> cache.biomeTranslations = array, BiomeTranslator::loadServerBiome); register("banner_pattern", cache -> cache.bannerPatterns, ($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId())); - register("wolf_variant", cache -> cache.wolfVariants, ($, entry) -> WolfEntity.WolfVariant.getByJavaIdentifier(entry.getId())); + register("wolf_variant", cache -> cache.wolfVariants, ($, entry) -> WolfEntity.BuiltInWolfVariant.getByJavaIdentifier(entry.getId().asString())); // Load from MCProtocolLib's classloader NbtMap tag = MinecraftProtocol.loadNetworkCodec(); - Map> defaults = new HashMap<>(); + Map> defaults = new HashMap<>(); // Don't create a keySet - no need to create the cached object in HashMap if we don't use it again REGISTRIES.forEach((key, $) -> { - List rawValues = tag.getCompound(key) + List rawValues = tag.getCompound(key.asString()) .getList("value", NbtType.COMPOUND); - Map values = new HashMap<>(); + Map values = new HashMap<>(); for (NbtMap value : rawValues) { - String name = value.getString("name"); + Key name = MinecraftKey.key(value.getString("name")); values.put(name, value.getCompound("element")); } // Can make these maps immutable and as efficient as possible after initialization @@ -104,16 +113,19 @@ public final class RegistryCache { * Java -> Bedrock biome network IDs. */ private int[] biomeTranslations; - private final JavaRegistry chatTypes = new SimpleJavaRegistry<>(); + private final JavaRegistry chatTypes = new SimpleJavaRegistry<>(); /** * All dimensions that the client could possibly connect to. */ private final JavaRegistry dimensions = new SimpleJavaRegistry<>(); + private final JavaRegistry enchantments = new SimpleJavaRegistry<>(); + private final JavaRegistry jukeboxSongs = new SimpleJavaRegistry<>(); + private final JavaRegistry paintings = new SimpleJavaRegistry<>(); private final JavaRegistry trimMaterials = new SimpleJavaRegistry<>(); private final JavaRegistry trimPatterns = new SimpleJavaRegistry<>(); private final JavaRegistry bannerPatterns = new SimpleJavaRegistry<>(); - private final JavaRegistry wolfVariants = new SimpleJavaRegistry<>(); + private final JavaRegistry wolfVariants = new SimpleJavaRegistry<>(); public RegistryCache(GeyserSession session) { this.session = session; @@ -138,9 +150,9 @@ public final class RegistryCache { * @param the class that represents these entries. */ private static void register(String registry, Function> localCacheFunction, BiFunction reader) { - String key = "minecraft:" + registry; + Key key = MinecraftKey.key(registry); REGISTRIES.put(key, (registryCache, entries) -> { - Map localRegistry = null; + Map localRegistry = null; JavaRegistry localCache = localCacheFunction.apply(registryCache); // Clear each local cache every time a new registry entry is given to us // (e.g. proxy server switches) @@ -166,7 +178,7 @@ public final class RegistryCache { * @param localCacheFunction the int array to set the final values to. */ private static void register(String registry, BiConsumer localCacheFunction, ToIntFunction reader) { - REGISTRIES.put("minecraft:" + registry, (registryCache, entries) -> { + REGISTRIES.put(MinecraftKey.key(registry), (registryCache, entries) -> { Int2IntMap temp = new Int2IntOpenHashMap(); int greatestId = 0; for (int i = 0; i < entries.size(); i++) { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index 95f5c1cc3..c8bfc7eed 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -25,85 +25,94 @@ package org.geysermc.geyser.session.cache; -import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntArrays; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.BlockTag; +import org.geysermc.geyser.session.cache.tags.EnchantmentTag; import org.geysermc.geyser.session.cache.tags.ItemTag; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.geyser.util.Ordered; import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundUpdateTagsPacket; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.EnumMap; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; +import static org.geysermc.geyser.session.cache.tags.BlockTag.ALL_BLOCK_TAGS; +import static org.geysermc.geyser.session.cache.tags.EnchantmentTag.ALL_ENCHANTMENT_TAGS; +import static org.geysermc.geyser.session.cache.tags.ItemTag.ALL_ITEM_TAGS; + /** * Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here * will remain empty, matching Java Edition behavior. - * - * This system is designed for easy extensibility - just add an enum to {@link BlockTag} or {@link ItemTag}. */ @ParametersAreNonnullByDefault public final class TagCache { - // Put these here so the enums can load without a static map - public static final Map ALL_BLOCK_TAGS = new HashMap<>(); - public static final Map ALL_ITEM_TAGS = new HashMap<>(); - - private final Map blocks = new EnumMap<>(BlockTag.class); - private final Map items = new EnumMap<>(ItemTag.class); + private final int[][] blocks = new int[ALL_BLOCK_TAGS.size()][]; + private final int[][] items = new int[ALL_ITEM_TAGS.size()][]; + private final int[][] enchantments = new int[ALL_ENCHANTMENT_TAGS.size()][]; public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) { - Map blockTags = packet.getTags().get("minecraft:block"); - this.blocks.clear(); - ALL_BLOCK_TAGS.forEach((location, tag) -> { - int[] values = blockTags.get(location); - if (values != null) { - this.blocks.put(tag, IntList.of(values)); - } else { - session.getGeyser().getLogger().debug("Block tag not found from server: " + location); - } - }); + Map blockTags = packet.getTags().get(MinecraftKey.key("block")); + loadTags("Block", blockTags, ALL_BLOCK_TAGS, this.blocks); // Hack btw GeyserLogger logger = session.getGeyser().getLogger(); - int[] convertableToMud = blockTags.get("minecraft:convertable_to_mud"); + int[] convertableToMud = blockTags.get(MinecraftKey.key("convertable_to_mud")); boolean emulatePost1_18Logic = convertableToMud != null && convertableToMud.length != 0; session.setEmulatePost1_18Logic(emulatePost1_18Logic); if (logger.isDebug()) { logger.debug("Emulating post 1.18 block predication logic for " + session.bedrockUsername() + "? " + emulatePost1_18Logic); } - Map itemTags = packet.getTags().get("minecraft:item"); - this.items.clear(); - ALL_ITEM_TAGS.forEach((location, tag) -> { - int[] values = itemTags.get(location); - if (values != null) { - this.items.put(tag, IntList.of(values)); - } else { - session.getGeyser().getLogger().debug("Item tag not found from server: " + location); - } - }); + Map itemTags = packet.getTags().get(MinecraftKey.key("item")); + loadTags("Item", itemTags, ALL_ITEM_TAGS, this.items); // Hack btw - boolean emulatePost1_13Logic = itemTags.get("minecraft:signs").length > 1; + boolean emulatePost1_13Logic = itemTags.get(MinecraftKey.key("signs")).length > 1; session.setEmulatePost1_13Logic(emulatePost1_13Logic); if (logger.isDebug()) { logger.debug("Emulating post 1.13 villager logic for " + session.bedrockUsername() + "? " + emulatePost1_13Logic); } + + Map enchantmentTags = packet.getTags().get(MinecraftKey.key("enchantment")); + loadTags("Enchantment", enchantmentTags, ALL_ENCHANTMENT_TAGS, this.enchantments); + } + + private void loadTags(String type, @Nullable Map packetTags, Map allTags, int[][] localValues) { + if (packetTags == null) { + Arrays.fill(localValues, IntArrays.EMPTY_ARRAY); + GeyserImpl.getInstance().getLogger().debug("Not loading " + type + " tags; they do not exist here."); + return; + } + allTags.forEach((location, tag) -> { + int[] values = packetTags.get(location); + if (values != null) { + if (values.length != 0) { + localValues[tag.ordinal()] = values; + } else { + localValues[tag.ordinal()] = IntArrays.EMPTY_ARRAY; + } + } else { + localValues[tag.ordinal()] = IntArrays.EMPTY_ARRAY; + GeyserImpl.getInstance().getLogger().debug(type + " tag not found from server: " + location); + } + }); } /** * @return true if the block tag is present and contains this block mapping's Java ID. */ public boolean is(BlockTag tag, Block block) { - IntList values = this.blocks.get(tag); - if (values != null) { - return values.contains(block.javaId()); - } - return false; + int[] values = this.blocks[tag.ordinal()]; + return contains(values, block.javaId()); } /** @@ -117,9 +126,19 @@ public final class TagCache { * @return true if the item tag is present and contains this item's Java ID. */ public boolean is(ItemTag tag, Item item) { - IntList values = this.items.get(tag); - if (values != null) { - return values.contains(item.javaId()); + int[] values = this.items[tag.ordinal()]; + return contains(values, item.javaId()); + } + + public int[] get(EnchantmentTag tag) { + return this.enchantments[tag.ordinal()]; + } + + private static boolean contains(int[] array, int i) { + for (int item : array) { + if (item == i) { + return true; + } } return false; } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 44ec7a6b9..8eb715560 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -28,8 +28,10 @@ package org.geysermc.geyser.session.cache; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMaps; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; +import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; import org.geysermc.geyser.scoreboard.Scoreboard; @@ -39,6 +41,7 @@ import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; import java.util.Iterator; +import java.util.Map; public final class WorldCache { private final GeyserSession session; @@ -61,6 +64,8 @@ public final class WorldCache { private int currentSequence; private final Object2IntMap unverifiedPredictions = new Object2IntOpenHashMap<>(1); + private final Map activeRecords = new Object2ObjectOpenHashMap<>(1); // Assume the average player won't be listening to many records + @Getter @Setter private boolean editingSignOnFront; @@ -185,4 +190,15 @@ public final class WorldCache { } } } + + public void addActiveRecord(Vector3i pos, String bedrockPlaySound) { + this.activeRecords.put(pos, bedrockPlaySound); + } + + // Implementation note: positions aren't removed unless the server calls, but this seems to match 1.21 Java + // client behavior. + @Nullable + public String removeActiveRecord(Vector3i pos) { + return this.activeRecords.remove(pos); + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java index 9839a1568..7b79a40be 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java @@ -57,4 +57,9 @@ public class SimpleJavaRegistry implements JavaRegistry { public List values() { return this.values; } + + @Override + public String toString() { + return this.values.toString(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/tags/BlockTag.java b/core/src/main/java/org/geysermc/geyser/session/cache/tags/BlockTag.java index 7017ad55c..1dca7843a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/tags/BlockTag.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/tags/BlockTag.java @@ -25,24 +25,214 @@ package org.geysermc.geyser.session.cache.tags; -import org.geysermc.geyser.session.cache.TagCache; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.util.Ordered; -public enum BlockTag { - LEAVES("leaves"), - WOOL("wool"), - AXE_EFFECTIVE("mineable/axe"), - HOE_EFFECTIVE("mineable/hoe"), - PICKAXE_EFFECTIVE("mineable/pickaxe"), - SHOVEL_EFFECTIVE("mineable/shovel"), - NEEDS_STONE_TOOL("needs_stone_tool"), - NEEDS_IRON_TOOL("needs_iron_tool"), - NEEDS_DIAMOND_TOOL("needs_diamond_tool"); +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unused") +public final class BlockTag implements Ordered { + public static final Map ALL_BLOCK_TAGS = new HashMap<>(); + + public static final BlockTag WOOL = new BlockTag("wool"); + public static final BlockTag PLANKS = new BlockTag("planks"); + public static final BlockTag STONE_BRICKS = new BlockTag("stone_bricks"); + public static final BlockTag WOODEN_BUTTONS = new BlockTag("wooden_buttons"); + public static final BlockTag STONE_BUTTONS = new BlockTag("stone_buttons"); + public static final BlockTag BUTTONS = new BlockTag("buttons"); + public static final BlockTag WOOL_CARPETS = new BlockTag("wool_carpets"); + public static final BlockTag WOODEN_DOORS = new BlockTag("wooden_doors"); + public static final BlockTag MOB_INTERACTABLE_DOORS = new BlockTag("mob_interactable_doors"); + public static final BlockTag WOODEN_STAIRS = new BlockTag("wooden_stairs"); + public static final BlockTag WOODEN_SLABS = new BlockTag("wooden_slabs"); + public static final BlockTag WOODEN_FENCES = new BlockTag("wooden_fences"); + public static final BlockTag PRESSURE_PLATES = new BlockTag("pressure_plates"); + public static final BlockTag WOODEN_PRESSURE_PLATES = new BlockTag("wooden_pressure_plates"); + public static final BlockTag STONE_PRESSURE_PLATES = new BlockTag("stone_pressure_plates"); + public static final BlockTag WOODEN_TRAPDOORS = new BlockTag("wooden_trapdoors"); + public static final BlockTag DOORS = new BlockTag("doors"); + public static final BlockTag SAPLINGS = new BlockTag("saplings"); + public static final BlockTag LOGS_THAT_BURN = new BlockTag("logs_that_burn"); + public static final BlockTag OVERWORLD_NATURAL_LOGS = new BlockTag("overworld_natural_logs"); + public static final BlockTag LOGS = new BlockTag("logs"); + public static final BlockTag DARK_OAK_LOGS = new BlockTag("dark_oak_logs"); + public static final BlockTag OAK_LOGS = new BlockTag("oak_logs"); + public static final BlockTag BIRCH_LOGS = new BlockTag("birch_logs"); + public static final BlockTag ACACIA_LOGS = new BlockTag("acacia_logs"); + public static final BlockTag CHERRY_LOGS = new BlockTag("cherry_logs"); + public static final BlockTag JUNGLE_LOGS = new BlockTag("jungle_logs"); + public static final BlockTag SPRUCE_LOGS = new BlockTag("spruce_logs"); + public static final BlockTag MANGROVE_LOGS = new BlockTag("mangrove_logs"); + public static final BlockTag CRIMSON_STEMS = new BlockTag("crimson_stems"); + public static final BlockTag WARPED_STEMS = new BlockTag("warped_stems"); + public static final BlockTag BAMBOO_BLOCKS = new BlockTag("bamboo_blocks"); + public static final BlockTag WART_BLOCKS = new BlockTag("wart_blocks"); + public static final BlockTag BANNERS = new BlockTag("banners"); + public static final BlockTag SAND = new BlockTag("sand"); + public static final BlockTag SMELTS_TO_GLASS = new BlockTag("smelts_to_glass"); + public static final BlockTag STAIRS = new BlockTag("stairs"); + public static final BlockTag SLABS = new BlockTag("slabs"); + public static final BlockTag WALLS = new BlockTag("walls"); + public static final BlockTag ANVIL = new BlockTag("anvil"); + public static final BlockTag RAILS = new BlockTag("rails"); + public static final BlockTag LEAVES = new BlockTag("leaves"); + public static final BlockTag TRAPDOORS = new BlockTag("trapdoors"); + public static final BlockTag SMALL_FLOWERS = new BlockTag("small_flowers"); + public static final BlockTag BEDS = new BlockTag("beds"); + public static final BlockTag FENCES = new BlockTag("fences"); + public static final BlockTag TALL_FLOWERS = new BlockTag("tall_flowers"); + public static final BlockTag FLOWERS = new BlockTag("flowers"); + public static final BlockTag PIGLIN_REPELLENTS = new BlockTag("piglin_repellents"); + public static final BlockTag GOLD_ORES = new BlockTag("gold_ores"); + public static final BlockTag IRON_ORES = new BlockTag("iron_ores"); + public static final BlockTag DIAMOND_ORES = new BlockTag("diamond_ores"); + public static final BlockTag REDSTONE_ORES = new BlockTag("redstone_ores"); + public static final BlockTag LAPIS_ORES = new BlockTag("lapis_ores"); + public static final BlockTag COAL_ORES = new BlockTag("coal_ores"); + public static final BlockTag EMERALD_ORES = new BlockTag("emerald_ores"); + public static final BlockTag COPPER_ORES = new BlockTag("copper_ores"); + public static final BlockTag CANDLES = new BlockTag("candles"); + public static final BlockTag DIRT = new BlockTag("dirt"); + public static final BlockTag TERRACOTTA = new BlockTag("terracotta"); + public static final BlockTag BADLANDS_TERRACOTTA = new BlockTag("badlands_terracotta"); + public static final BlockTag CONCRETE_POWDER = new BlockTag("concrete_powder"); + public static final BlockTag COMPLETES_FIND_TREE_TUTORIAL = new BlockTag("completes_find_tree_tutorial"); + public static final BlockTag FLOWER_POTS = new BlockTag("flower_pots"); + public static final BlockTag ENDERMAN_HOLDABLE = new BlockTag("enderman_holdable"); + public static final BlockTag ICE = new BlockTag("ice"); + public static final BlockTag VALID_SPAWN = new BlockTag("valid_spawn"); + public static final BlockTag IMPERMEABLE = new BlockTag("impermeable"); + public static final BlockTag UNDERWATER_BONEMEALS = new BlockTag("underwater_bonemeals"); + public static final BlockTag CORAL_BLOCKS = new BlockTag("coral_blocks"); + public static final BlockTag WALL_CORALS = new BlockTag("wall_corals"); + public static final BlockTag CORAL_PLANTS = new BlockTag("coral_plants"); + public static final BlockTag CORALS = new BlockTag("corals"); + public static final BlockTag BAMBOO_PLANTABLE_ON = new BlockTag("bamboo_plantable_on"); + public static final BlockTag STANDING_SIGNS = new BlockTag("standing_signs"); + public static final BlockTag WALL_SIGNS = new BlockTag("wall_signs"); + public static final BlockTag SIGNS = new BlockTag("signs"); + public static final BlockTag CEILING_HANGING_SIGNS = new BlockTag("ceiling_hanging_signs"); + public static final BlockTag WALL_HANGING_SIGNS = new BlockTag("wall_hanging_signs"); + public static final BlockTag ALL_HANGING_SIGNS = new BlockTag("all_hanging_signs"); + public static final BlockTag ALL_SIGNS = new BlockTag("all_signs"); + public static final BlockTag DRAGON_IMMUNE = new BlockTag("dragon_immune"); + public static final BlockTag DRAGON_TRANSPARENT = new BlockTag("dragon_transparent"); + public static final BlockTag WITHER_IMMUNE = new BlockTag("wither_immune"); + public static final BlockTag WITHER_SUMMON_BASE_BLOCKS = new BlockTag("wither_summon_base_blocks"); + public static final BlockTag BEEHIVES = new BlockTag("beehives"); + public static final BlockTag CROPS = new BlockTag("crops"); + public static final BlockTag BEE_GROWABLES = new BlockTag("bee_growables"); + public static final BlockTag PORTALS = new BlockTag("portals"); + public static final BlockTag FIRE = new BlockTag("fire"); + public static final BlockTag NYLIUM = new BlockTag("nylium"); + public static final BlockTag BEACON_BASE_BLOCKS = new BlockTag("beacon_base_blocks"); + public static final BlockTag SOUL_SPEED_BLOCKS = new BlockTag("soul_speed_blocks"); + public static final BlockTag WALL_POST_OVERRIDE = new BlockTag("wall_post_override"); + public static final BlockTag CLIMBABLE = new BlockTag("climbable"); + public static final BlockTag FALL_DAMAGE_RESETTING = new BlockTag("fall_damage_resetting"); + public static final BlockTag SHULKER_BOXES = new BlockTag("shulker_boxes"); + public static final BlockTag HOGLIN_REPELLENTS = new BlockTag("hoglin_repellents"); + public static final BlockTag SOUL_FIRE_BASE_BLOCKS = new BlockTag("soul_fire_base_blocks"); + public static final BlockTag STRIDER_WARM_BLOCKS = new BlockTag("strider_warm_blocks"); + public static final BlockTag CAMPFIRES = new BlockTag("campfires"); + public static final BlockTag GUARDED_BY_PIGLINS = new BlockTag("guarded_by_piglins"); + public static final BlockTag PREVENT_MOB_SPAWNING_INSIDE = new BlockTag("prevent_mob_spawning_inside"); + public static final BlockTag FENCE_GATES = new BlockTag("fence_gates"); + public static final BlockTag UNSTABLE_BOTTOM_CENTER = new BlockTag("unstable_bottom_center"); + public static final BlockTag MUSHROOM_GROW_BLOCK = new BlockTag("mushroom_grow_block"); + public static final BlockTag INFINIBURN_OVERWORLD = new BlockTag("infiniburn_overworld"); + public static final BlockTag INFINIBURN_NETHER = new BlockTag("infiniburn_nether"); + public static final BlockTag INFINIBURN_END = new BlockTag("infiniburn_end"); + public static final BlockTag BASE_STONE_OVERWORLD = new BlockTag("base_stone_overworld"); + public static final BlockTag STONE_ORE_REPLACEABLES = new BlockTag("stone_ore_replaceables"); + public static final BlockTag DEEPSLATE_ORE_REPLACEABLES = new BlockTag("deepslate_ore_replaceables"); + public static final BlockTag BASE_STONE_NETHER = new BlockTag("base_stone_nether"); + public static final BlockTag OVERWORLD_CARVER_REPLACEABLES = new BlockTag("overworld_carver_replaceables"); + public static final BlockTag NETHER_CARVER_REPLACEABLES = new BlockTag("nether_carver_replaceables"); + public static final BlockTag CANDLE_CAKES = new BlockTag("candle_cakes"); + public static final BlockTag CAULDRONS = new BlockTag("cauldrons"); + public static final BlockTag CRYSTAL_SOUND_BLOCKS = new BlockTag("crystal_sound_blocks"); + public static final BlockTag INSIDE_STEP_SOUND_BLOCKS = new BlockTag("inside_step_sound_blocks"); + public static final BlockTag COMBINATION_STEP_SOUND_BLOCKS = new BlockTag("combination_step_sound_blocks"); + public static final BlockTag CAMEL_SAND_STEP_SOUND_BLOCKS = new BlockTag("camel_sand_step_sound_blocks"); + public static final BlockTag OCCLUDES_VIBRATION_SIGNALS = new BlockTag("occludes_vibration_signals"); + public static final BlockTag DAMPENS_VIBRATIONS = new BlockTag("dampens_vibrations"); + public static final BlockTag DRIPSTONE_REPLACEABLE_BLOCKS = new BlockTag("dripstone_replaceable_blocks"); + public static final BlockTag CAVE_VINES = new BlockTag("cave_vines"); + public static final BlockTag MOSS_REPLACEABLE = new BlockTag("moss_replaceable"); + public static final BlockTag LUSH_GROUND_REPLACEABLE = new BlockTag("lush_ground_replaceable"); + public static final BlockTag AZALEA_ROOT_REPLACEABLE = new BlockTag("azalea_root_replaceable"); + public static final BlockTag SMALL_DRIPLEAF_PLACEABLE = new BlockTag("small_dripleaf_placeable"); + public static final BlockTag BIG_DRIPLEAF_PLACEABLE = new BlockTag("big_dripleaf_placeable"); + public static final BlockTag SNOW = new BlockTag("snow"); + public static final BlockTag MINEABLE_AXE = new BlockTag("mineable/axe"); + public static final BlockTag MINEABLE_HOE = new BlockTag("mineable/hoe"); + public static final BlockTag MINEABLE_PICKAXE = new BlockTag("mineable/pickaxe"); + public static final BlockTag MINEABLE_SHOVEL = new BlockTag("mineable/shovel"); + public static final BlockTag SWORD_EFFICIENT = new BlockTag("sword_efficient"); + public static final BlockTag NEEDS_DIAMOND_TOOL = new BlockTag("needs_diamond_tool"); + public static final BlockTag NEEDS_IRON_TOOL = new BlockTag("needs_iron_tool"); + public static final BlockTag NEEDS_STONE_TOOL = new BlockTag("needs_stone_tool"); + public static final BlockTag INCORRECT_FOR_NETHERITE_TOOL = new BlockTag("incorrect_for_netherite_tool"); + public static final BlockTag INCORRECT_FOR_DIAMOND_TOOL = new BlockTag("incorrect_for_diamond_tool"); + public static final BlockTag INCORRECT_FOR_IRON_TOOL = new BlockTag("incorrect_for_iron_tool"); + public static final BlockTag INCORRECT_FOR_STONE_TOOL = new BlockTag("incorrect_for_stone_tool"); + public static final BlockTag INCORRECT_FOR_GOLD_TOOL = new BlockTag("incorrect_for_gold_tool"); + public static final BlockTag INCORRECT_FOR_WOODEN_TOOL = new BlockTag("incorrect_for_wooden_tool"); + public static final BlockTag FEATURES_CANNOT_REPLACE = new BlockTag("features_cannot_replace"); + public static final BlockTag LAVA_POOL_STONE_CANNOT_REPLACE = new BlockTag("lava_pool_stone_cannot_replace"); + public static final BlockTag GEODE_INVALID_BLOCKS = new BlockTag("geode_invalid_blocks"); + public static final BlockTag FROG_PREFER_JUMP_TO = new BlockTag("frog_prefer_jump_to"); + public static final BlockTag SCULK_REPLACEABLE = new BlockTag("sculk_replaceable"); + public static final BlockTag SCULK_REPLACEABLE_WORLD_GEN = new BlockTag("sculk_replaceable_world_gen"); + public static final BlockTag ANCIENT_CITY_REPLACEABLE = new BlockTag("ancient_city_replaceable"); + public static final BlockTag VIBRATION_RESONATORS = new BlockTag("vibration_resonators"); + public static final BlockTag ANIMALS_SPAWNABLE_ON = new BlockTag("animals_spawnable_on"); + public static final BlockTag ARMADILLO_SPAWNABLE_ON = new BlockTag("armadillo_spawnable_on"); + public static final BlockTag AXOLOTLS_SPAWNABLE_ON = new BlockTag("axolotls_spawnable_on"); + public static final BlockTag GOATS_SPAWNABLE_ON = new BlockTag("goats_spawnable_on"); + public static final BlockTag MOOSHROOMS_SPAWNABLE_ON = new BlockTag("mooshrooms_spawnable_on"); + public static final BlockTag PARROTS_SPAWNABLE_ON = new BlockTag("parrots_spawnable_on"); + public static final BlockTag POLAR_BEARS_SPAWNABLE_ON_ALTERNATE = new BlockTag("polar_bears_spawnable_on_alternate"); + public static final BlockTag RABBITS_SPAWNABLE_ON = new BlockTag("rabbits_spawnable_on"); + public static final BlockTag FOXES_SPAWNABLE_ON = new BlockTag("foxes_spawnable_on"); + public static final BlockTag WOLVES_SPAWNABLE_ON = new BlockTag("wolves_spawnable_on"); + public static final BlockTag FROGS_SPAWNABLE_ON = new BlockTag("frogs_spawnable_on"); + public static final BlockTag AZALEA_GROWS_ON = new BlockTag("azalea_grows_on"); + public static final BlockTag CONVERTABLE_TO_MUD = new BlockTag("convertable_to_mud"); + public static final BlockTag MANGROVE_LOGS_CAN_GROW_THROUGH = new BlockTag("mangrove_logs_can_grow_through"); + public static final BlockTag MANGROVE_ROOTS_CAN_GROW_THROUGH = new BlockTag("mangrove_roots_can_grow_through"); + public static final BlockTag DEAD_BUSH_MAY_PLACE_ON = new BlockTag("dead_bush_may_place_on"); + public static final BlockTag SNAPS_GOAT_HORN = new BlockTag("snaps_goat_horn"); + public static final BlockTag REPLACEABLE_BY_TREES = new BlockTag("replaceable_by_trees"); + public static final BlockTag SNOW_LAYER_CANNOT_SURVIVE_ON = new BlockTag("snow_layer_cannot_survive_on"); + public static final BlockTag SNOW_LAYER_CAN_SURVIVE_ON = new BlockTag("snow_layer_can_survive_on"); + public static final BlockTag INVALID_SPAWN_INSIDE = new BlockTag("invalid_spawn_inside"); + public static final BlockTag SNIFFER_DIGGABLE_BLOCK = new BlockTag("sniffer_diggable_block"); + public static final BlockTag SNIFFER_EGG_HATCH_BOOST = new BlockTag("sniffer_egg_hatch_boost"); + public static final BlockTag TRAIL_RUINS_REPLACEABLE = new BlockTag("trail_ruins_replaceable"); + public static final BlockTag REPLACEABLE = new BlockTag("replaceable"); + public static final BlockTag ENCHANTMENT_POWER_PROVIDER = new BlockTag("enchantment_power_provider"); + public static final BlockTag ENCHANTMENT_POWER_TRANSMITTER = new BlockTag("enchantment_power_transmitter"); + public static final BlockTag MAINTAINS_FARMLAND = new BlockTag("maintains_farmland"); + public static final BlockTag BLOCKS_WIND_CHARGE_EXPLOSIONS = new BlockTag("blocks_wind_charge_explosions"); + public static final BlockTag DOES_NOT_BLOCK_HOPPERS = new BlockTag("does_not_block_hoppers"); + public static final BlockTag AIR = new BlockTag("air"); + + private final int id; - BlockTag(String identifier) { + private BlockTag(String identifier) { + this.id = ALL_BLOCK_TAGS.size(); register(identifier, this); } + @Override + public int ordinal() { + return id; + } + private static void register(String name, BlockTag tag) { - TagCache.ALL_BLOCK_TAGS.put("minecraft:" + name, tag); + ALL_BLOCK_TAGS.put(Key.key(name), tag); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/tags/EnchantmentTag.java b/core/src/main/java/org/geysermc/geyser/session/cache/tags/EnchantmentTag.java new file mode 100644 index 000000000..0af690abd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/tags/EnchantmentTag.java @@ -0,0 +1,91 @@ +/* + * 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.session.cache.tags; + +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.geyser.util.Ordered; + +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unused") +public final class EnchantmentTag implements Ordered { + public static final Map ALL_ENCHANTMENT_TAGS = new HashMap<>(); + + public static final EnchantmentTag TOOLTIP_ORDER = new EnchantmentTag("tooltip_order"); + public static final EnchantmentTag EXCLUSIVE_SET_ARMOR = new EnchantmentTag("exclusive_set/armor"); + public static final EnchantmentTag EXCLUSIVE_SET_BOOTS = new EnchantmentTag("exclusive_set/boots"); + public static final EnchantmentTag EXCLUSIVE_SET_BOW = new EnchantmentTag("exclusive_set/bow"); + public static final EnchantmentTag EXCLUSIVE_SET_CROSSBOW = new EnchantmentTag("exclusive_set/crossbow"); + public static final EnchantmentTag EXCLUSIVE_SET_DAMAGE = new EnchantmentTag("exclusive_set/damage"); + public static final EnchantmentTag EXCLUSIVE_SET_MINING = new EnchantmentTag("exclusive_set/mining"); + public static final EnchantmentTag EXCLUSIVE_SET_RIPTIDE = new EnchantmentTag("exclusive_set/riptide"); + public static final EnchantmentTag TRADEABLE = new EnchantmentTag("tradeable"); + public static final EnchantmentTag DOUBLE_TRADE_PRICE = new EnchantmentTag("double_trade_price"); + public static final EnchantmentTag IN_ENCHANTING_TABLE = new EnchantmentTag("in_enchanting_table"); + public static final EnchantmentTag ON_MOB_SPAWN_EQUIPMENT = new EnchantmentTag("on_mob_spawn_equipment"); + public static final EnchantmentTag ON_TRADED_EQUIPMENT = new EnchantmentTag("on_traded_equipment"); + public static final EnchantmentTag ON_RANDOM_LOOT = new EnchantmentTag("on_random_loot"); + public static final EnchantmentTag CURSE = new EnchantmentTag("curse"); + public static final EnchantmentTag SMELTS_LOOT = new EnchantmentTag("smelts_loot"); + public static final EnchantmentTag PREVENTS_BEE_SPAWNS_WHEN_MINING = new EnchantmentTag("prevents_bee_spawns_when_mining"); + public static final EnchantmentTag PREVENTS_DECORATED_POT_SHATTERING = new EnchantmentTag("prevents_decorated_pot_shattering"); + public static final EnchantmentTag PREVENTS_ICE_MELTING = new EnchantmentTag("prevents_ice_melting"); + public static final EnchantmentTag PREVENTS_INFESTED_SPAWNS = new EnchantmentTag("prevents_infested_spawns"); + public static final EnchantmentTag TREASURE = new EnchantmentTag("treasure"); + public static final EnchantmentTag NON_TREASURE = new EnchantmentTag("non_treasure"); + public static final EnchantmentTag TRADES_DESERT_COMMON = new EnchantmentTag("trades/desert_common"); + public static final EnchantmentTag TRADES_JUNGLE_COMMON = new EnchantmentTag("trades/jungle_common"); + public static final EnchantmentTag TRADES_PLAINS_COMMON = new EnchantmentTag("trades/plains_common"); + public static final EnchantmentTag TRADES_SAVANNA_COMMON = new EnchantmentTag("trades/savanna_common"); + public static final EnchantmentTag TRADES_SNOW_COMMON = new EnchantmentTag("trades/snow_common"); + public static final EnchantmentTag TRADES_SWAMP_COMMON = new EnchantmentTag("trades/swamp_common"); + public static final EnchantmentTag TRADES_TAIGA_COMMON = new EnchantmentTag("trades/taiga_common"); + public static final EnchantmentTag TRADES_DESERT_SPECIAL = new EnchantmentTag("trades/desert_special"); + public static final EnchantmentTag TRADES_JUNGLE_SPECIAL = new EnchantmentTag("trades/jungle_special"); + public static final EnchantmentTag TRADES_PLAINS_SPECIAL = new EnchantmentTag("trades/plains_special"); + public static final EnchantmentTag TRADES_SAVANNA_SPECIAL = new EnchantmentTag("trades/savanna_special"); + public static final EnchantmentTag TRADES_SNOW_SPECIAL = new EnchantmentTag("trades/snow_special"); + public static final EnchantmentTag TRADES_SWAMP_SPECIAL = new EnchantmentTag("trades/swamp_special"); + public static final EnchantmentTag TRADES_TAIGA_SPECIAL = new EnchantmentTag("trades/taiga_special"); + + private final int id; + + private EnchantmentTag(String identifier) { + this.id = ALL_ENCHANTMENT_TAGS.size(); + register(identifier, this); + } + + @Override + public int ordinal() { + return id; + } + + private static void register(String name, EnchantmentTag tag) { + ALL_ENCHANTMENT_TAGS.put(MinecraftKey.key(name), tag); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/tags/ItemTag.java b/core/src/main/java/org/geysermc/geyser/session/cache/tags/ItemTag.java index f064d0763..a2e861dd6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/tags/ItemTag.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/tags/ItemTag.java @@ -25,44 +25,178 @@ package org.geysermc.geyser.session.cache.tags; -import org.geysermc.geyser.session.cache.TagCache; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.geyser.util.Ordered; -public enum ItemTag { - AXOLOTL_FOOD("axolotl_food"), - CREEPER_IGNITERS("creeper_igniters"), - FISHES("fishes"), - FOX_FOOD("fox_food"), - PIGLIN_LOVED("piglin_loved"), - SMALL_FLOWERS("small_flowers"), - SNIFFER_FOOD("sniffer_food"), - PIGLIN_FOOD("piglin_food"), - COW_FOOD("cow_food"), - GOAT_FOOD("goat_food"), - SHEEP_FOOD("sheep_food"), - WOLF_FOOD("wolf_food"), - CAT_FOOD("cat_food"), - HORSE_FOOD("horse_food"), - CAMEL_FOOD("camel_food"), - ARMADILLO_FOOD("armadillo_food"), - BEE_FOOD("bee_food"), - CHICKEN_FOOD("chicken_food"), - FROG_FOOD("frog_food"), - HOGLIN_FOOD("hoglin_food"), - LLAMA_FOOD("llama_food"), - OCELOT_FOOD("ocelot_food"), - PANDA_FOOD("panda_food"), - PIG_FOOD("pig_food"), - RABBIT_FOOD("rabbit_food"), - STRIDER_FOOD("strider_food"), - TURTLE_FOOD("turtle_food"), - PARROT_FOOD("parrot_food"), - PARROT_POISONOUS_FOOD("parrot_poisonous_food"); +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unused") +public final class ItemTag implements Ordered { + public static final Map ALL_ITEM_TAGS = new HashMap<>(); + + public static final ItemTag WOOL = new ItemTag("wool"); + public static final ItemTag PLANKS = new ItemTag("planks"); + public static final ItemTag STONE_BRICKS = new ItemTag("stone_bricks"); + public static final ItemTag WOODEN_BUTTONS = new ItemTag("wooden_buttons"); + public static final ItemTag STONE_BUTTONS = new ItemTag("stone_buttons"); + public static final ItemTag BUTTONS = new ItemTag("buttons"); + public static final ItemTag WOOL_CARPETS = new ItemTag("wool_carpets"); + public static final ItemTag WOODEN_DOORS = new ItemTag("wooden_doors"); + public static final ItemTag WOODEN_STAIRS = new ItemTag("wooden_stairs"); + public static final ItemTag WOODEN_SLABS = new ItemTag("wooden_slabs"); + public static final ItemTag WOODEN_FENCES = new ItemTag("wooden_fences"); + public static final ItemTag FENCE_GATES = new ItemTag("fence_gates"); + public static final ItemTag WOODEN_PRESSURE_PLATES = new ItemTag("wooden_pressure_plates"); + public static final ItemTag WOODEN_TRAPDOORS = new ItemTag("wooden_trapdoors"); + public static final ItemTag DOORS = new ItemTag("doors"); + public static final ItemTag SAPLINGS = new ItemTag("saplings"); + public static final ItemTag LOGS_THAT_BURN = new ItemTag("logs_that_burn"); + public static final ItemTag LOGS = new ItemTag("logs"); + public static final ItemTag DARK_OAK_LOGS = new ItemTag("dark_oak_logs"); + public static final ItemTag OAK_LOGS = new ItemTag("oak_logs"); + public static final ItemTag BIRCH_LOGS = new ItemTag("birch_logs"); + public static final ItemTag ACACIA_LOGS = new ItemTag("acacia_logs"); + public static final ItemTag CHERRY_LOGS = new ItemTag("cherry_logs"); + public static final ItemTag JUNGLE_LOGS = new ItemTag("jungle_logs"); + public static final ItemTag SPRUCE_LOGS = new ItemTag("spruce_logs"); + public static final ItemTag MANGROVE_LOGS = new ItemTag("mangrove_logs"); + public static final ItemTag CRIMSON_STEMS = new ItemTag("crimson_stems"); + public static final ItemTag WARPED_STEMS = new ItemTag("warped_stems"); + public static final ItemTag BAMBOO_BLOCKS = new ItemTag("bamboo_blocks"); + public static final ItemTag WART_BLOCKS = new ItemTag("wart_blocks"); + public static final ItemTag BANNERS = new ItemTag("banners"); + public static final ItemTag SAND = new ItemTag("sand"); + public static final ItemTag SMELTS_TO_GLASS = new ItemTag("smelts_to_glass"); + public static final ItemTag STAIRS = new ItemTag("stairs"); + public static final ItemTag SLABS = new ItemTag("slabs"); + public static final ItemTag WALLS = new ItemTag("walls"); + public static final ItemTag ANVIL = new ItemTag("anvil"); + public static final ItemTag RAILS = new ItemTag("rails"); + public static final ItemTag LEAVES = new ItemTag("leaves"); + public static final ItemTag TRAPDOORS = new ItemTag("trapdoors"); + public static final ItemTag SMALL_FLOWERS = new ItemTag("small_flowers"); + public static final ItemTag BEDS = new ItemTag("beds"); + public static final ItemTag FENCES = new ItemTag("fences"); + public static final ItemTag TALL_FLOWERS = new ItemTag("tall_flowers"); + public static final ItemTag FLOWERS = new ItemTag("flowers"); + public static final ItemTag PIGLIN_REPELLENTS = new ItemTag("piglin_repellents"); + public static final ItemTag PIGLIN_LOVED = new ItemTag("piglin_loved"); + public static final ItemTag IGNORED_BY_PIGLIN_BABIES = new ItemTag("ignored_by_piglin_babies"); + public static final ItemTag MEAT = new ItemTag("meat"); + public static final ItemTag SNIFFER_FOOD = new ItemTag("sniffer_food"); + public static final ItemTag PIGLIN_FOOD = new ItemTag("piglin_food"); + public static final ItemTag FOX_FOOD = new ItemTag("fox_food"); + public static final ItemTag COW_FOOD = new ItemTag("cow_food"); + public static final ItemTag GOAT_FOOD = new ItemTag("goat_food"); + public static final ItemTag SHEEP_FOOD = new ItemTag("sheep_food"); + public static final ItemTag WOLF_FOOD = new ItemTag("wolf_food"); + public static final ItemTag CAT_FOOD = new ItemTag("cat_food"); + public static final ItemTag HORSE_FOOD = new ItemTag("horse_food"); + public static final ItemTag HORSE_TEMPT_ITEMS = new ItemTag("horse_tempt_items"); + public static final ItemTag CAMEL_FOOD = new ItemTag("camel_food"); + public static final ItemTag ARMADILLO_FOOD = new ItemTag("armadillo_food"); + public static final ItemTag BEE_FOOD = new ItemTag("bee_food"); + public static final ItemTag CHICKEN_FOOD = new ItemTag("chicken_food"); + public static final ItemTag FROG_FOOD = new ItemTag("frog_food"); + public static final ItemTag HOGLIN_FOOD = new ItemTag("hoglin_food"); + public static final ItemTag LLAMA_FOOD = new ItemTag("llama_food"); + public static final ItemTag LLAMA_TEMPT_ITEMS = new ItemTag("llama_tempt_items"); + public static final ItemTag OCELOT_FOOD = new ItemTag("ocelot_food"); + public static final ItemTag PANDA_FOOD = new ItemTag("panda_food"); + public static final ItemTag PIG_FOOD = new ItemTag("pig_food"); + public static final ItemTag RABBIT_FOOD = new ItemTag("rabbit_food"); + public static final ItemTag STRIDER_FOOD = new ItemTag("strider_food"); + public static final ItemTag STRIDER_TEMPT_ITEMS = new ItemTag("strider_tempt_items"); + public static final ItemTag TURTLE_FOOD = new ItemTag("turtle_food"); + public static final ItemTag PARROT_FOOD = new ItemTag("parrot_food"); + public static final ItemTag PARROT_POISONOUS_FOOD = new ItemTag("parrot_poisonous_food"); + public static final ItemTag AXOLOTL_FOOD = new ItemTag("axolotl_food"); + public static final ItemTag GOLD_ORES = new ItemTag("gold_ores"); + public static final ItemTag IRON_ORES = new ItemTag("iron_ores"); + public static final ItemTag DIAMOND_ORES = new ItemTag("diamond_ores"); + public static final ItemTag REDSTONE_ORES = new ItemTag("redstone_ores"); + public static final ItemTag LAPIS_ORES = new ItemTag("lapis_ores"); + public static final ItemTag COAL_ORES = new ItemTag("coal_ores"); + public static final ItemTag EMERALD_ORES = new ItemTag("emerald_ores"); + public static final ItemTag COPPER_ORES = new ItemTag("copper_ores"); + public static final ItemTag NON_FLAMMABLE_WOOD = new ItemTag("non_flammable_wood"); + public static final ItemTag SOUL_FIRE_BASE_BLOCKS = new ItemTag("soul_fire_base_blocks"); + public static final ItemTag CANDLES = new ItemTag("candles"); + public static final ItemTag DIRT = new ItemTag("dirt"); + public static final ItemTag TERRACOTTA = new ItemTag("terracotta"); + public static final ItemTag COMPLETES_FIND_TREE_TUTORIAL = new ItemTag("completes_find_tree_tutorial"); + public static final ItemTag BOATS = new ItemTag("boats"); + public static final ItemTag CHEST_BOATS = new ItemTag("chest_boats"); + public static final ItemTag FISHES = new ItemTag("fishes"); + public static final ItemTag SIGNS = new ItemTag("signs"); + public static final ItemTag CREEPER_DROP_MUSIC_DISCS = new ItemTag("creeper_drop_music_discs"); + public static final ItemTag COALS = new ItemTag("coals"); + public static final ItemTag ARROWS = new ItemTag("arrows"); + public static final ItemTag LECTERN_BOOKS = new ItemTag("lectern_books"); + public static final ItemTag BOOKSHELF_BOOKS = new ItemTag("bookshelf_books"); + public static final ItemTag BEACON_PAYMENT_ITEMS = new ItemTag("beacon_payment_items"); + public static final ItemTag STONE_TOOL_MATERIALS = new ItemTag("stone_tool_materials"); + public static final ItemTag STONE_CRAFTING_MATERIALS = new ItemTag("stone_crafting_materials"); + public static final ItemTag FREEZE_IMMUNE_WEARABLES = new ItemTag("freeze_immune_wearables"); + public static final ItemTag DAMPENS_VIBRATIONS = new ItemTag("dampens_vibrations"); + public static final ItemTag CLUSTER_MAX_HARVESTABLES = new ItemTag("cluster_max_harvestables"); + public static final ItemTag COMPASSES = new ItemTag("compasses"); + public static final ItemTag HANGING_SIGNS = new ItemTag("hanging_signs"); + public static final ItemTag CREEPER_IGNITERS = new ItemTag("creeper_igniters"); + public static final ItemTag NOTEBLOCK_TOP_INSTRUMENTS = new ItemTag("noteblock_top_instruments"); + public static final ItemTag FOOT_ARMOR = new ItemTag("foot_armor"); + public static final ItemTag LEG_ARMOR = new ItemTag("leg_armor"); + public static final ItemTag CHEST_ARMOR = new ItemTag("chest_armor"); + public static final ItemTag HEAD_ARMOR = new ItemTag("head_armor"); + public static final ItemTag SKULLS = new ItemTag("skulls"); + public static final ItemTag TRIMMABLE_ARMOR = new ItemTag("trimmable_armor"); + public static final ItemTag TRIM_MATERIALS = new ItemTag("trim_materials"); + public static final ItemTag TRIM_TEMPLATES = new ItemTag("trim_templates"); + public static final ItemTag DECORATED_POT_SHERDS = new ItemTag("decorated_pot_sherds"); + public static final ItemTag DECORATED_POT_INGREDIENTS = new ItemTag("decorated_pot_ingredients"); + public static final ItemTag SWORDS = new ItemTag("swords"); + public static final ItemTag AXES = new ItemTag("axes"); + public static final ItemTag HOES = new ItemTag("hoes"); + public static final ItemTag PICKAXES = new ItemTag("pickaxes"); + public static final ItemTag SHOVELS = new ItemTag("shovels"); + public static final ItemTag BREAKS_DECORATED_POTS = new ItemTag("breaks_decorated_pots"); + public static final ItemTag VILLAGER_PLANTABLE_SEEDS = new ItemTag("villager_plantable_seeds"); + public static final ItemTag DYEABLE = new ItemTag("dyeable"); + public static final ItemTag ENCHANTABLE_FOOT_ARMOR = new ItemTag("enchantable/foot_armor"); + public static final ItemTag ENCHANTABLE_LEG_ARMOR = new ItemTag("enchantable/leg_armor"); + public static final ItemTag ENCHANTABLE_CHEST_ARMOR = new ItemTag("enchantable/chest_armor"); + public static final ItemTag ENCHANTABLE_HEAD_ARMOR = new ItemTag("enchantable/head_armor"); + public static final ItemTag ENCHANTABLE_ARMOR = new ItemTag("enchantable/armor"); + public static final ItemTag ENCHANTABLE_SWORD = new ItemTag("enchantable/sword"); + public static final ItemTag ENCHANTABLE_FIRE_ASPECT = new ItemTag("enchantable/fire_aspect"); + public static final ItemTag ENCHANTABLE_SHARP_WEAPON = new ItemTag("enchantable/sharp_weapon"); + public static final ItemTag ENCHANTABLE_WEAPON = new ItemTag("enchantable/weapon"); + public static final ItemTag ENCHANTABLE_MINING = new ItemTag("enchantable/mining"); + public static final ItemTag ENCHANTABLE_MINING_LOOT = new ItemTag("enchantable/mining_loot"); + public static final ItemTag ENCHANTABLE_FISHING = new ItemTag("enchantable/fishing"); + public static final ItemTag ENCHANTABLE_TRIDENT = new ItemTag("enchantable/trident"); + public static final ItemTag ENCHANTABLE_DURABILITY = new ItemTag("enchantable/durability"); + public static final ItemTag ENCHANTABLE_BOW = new ItemTag("enchantable/bow"); + public static final ItemTag ENCHANTABLE_EQUIPPABLE = new ItemTag("enchantable/equippable"); + public static final ItemTag ENCHANTABLE_CROSSBOW = new ItemTag("enchantable/crossbow"); + public static final ItemTag ENCHANTABLE_VANISHING = new ItemTag("enchantable/vanishing"); + public static final ItemTag ENCHANTABLE_MACE = new ItemTag("enchantable/mace"); + + private final int id; - ItemTag(String identifier) { + private ItemTag(String identifier) { + this.id = ALL_ITEM_TAGS.size(); register(identifier, this); } + @Override + public int ordinal() { + return id; + } + private static void register(String name, ItemTag tag) { - TagCache.ALL_ITEM_TAGS.put("minecraft:" + name, tag); + ALL_ITEM_TAGS.put(MinecraftKey.key(name), tag); } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 7b126c136..4c3db7504 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -309,6 +309,15 @@ public class SkinManager { return null; } + if (DEFAULT_FLOODGATE_STEVE.equals(skinUrl)) { + // https://github.com/GeyserMC/Floodgate/commit/00b8b1b6364116ff4bc9b00e2015ce35bae8abb1 ensures that + // Bedrock players on online-mode servers will always have a textures property. However, this skin is + // also sent our way, and isn't overwritten. It's very likely that this skin is *only* a placeholder, + // and no one should ever be using it outside of Floodgate, and therefore no one wants to see this + // specific Steve skin. + return null; + } + boolean isAlex = skinTexture.has("metadata"); String capeUrl = null; @@ -322,5 +331,7 @@ public class SkinManager { return new GameProfileData(skinUrl, capeUrl, isAlex); } + + private static final String DEFAULT_FLOODGATE_STEVE = "https://textures.minecraft.net/texture/31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb"; } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java index cfe950409..28fd6f9a4 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -259,6 +259,13 @@ public class GeyserLocale { // Invalid locale return locale; } + + // See https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes - covers the special case that is norwegian + String lowerCaseLocale = locale.toLowerCase(Locale.ROOT); + if (lowerCaseLocale.equals("nn_no") || lowerCaseLocale.equals("no_no")) { + locale = "nb_NO"; + } + String language = locale.substring(0, 2); String country = locale.substring(3); return language.toLowerCase(Locale.ENGLISH) + "_" + country.toUpperCase(Locale.ENGLISH); diff --git a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java index b2222d3b9..ab9e2b5ed 100644 --- a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java +++ b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java @@ -30,21 +30,49 @@ import net.kyori.adventure.text.format.Style; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; +import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; +import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; +import java.util.*; -public final class TextDecoration { - private final String translationKey; - private final Style style; - private final Set parameters; +public record TextDecoration(String translationKey, List parameters, Style deserializedStyle) implements ChatTypeDecoration { - public TextDecoration(NbtMap tag) { - translationKey = tag.getString("translation_key"); + @Override + public NbtMap style() { + // Should not ever be called. + throw new UnsupportedOperationException(); + } - NbtMap styleTag = tag.getCompound("style"); + public static ChatType readChatType(RegistryEntry entry) { + // Note: The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla. + // (This note has been passed around through several classes and iterations. It stays as a warning + // to anyone that dares to try and hardcode registry IDs.) + NbtMap tag = entry.getData(); + NbtMap chat = tag.getCompound("chat", null); + if (chat != null) { + String translationKey = chat.getString("translation_key"); + + NbtMap styleTag = chat.getCompound("style"); + Style style = deserializeStyle(styleTag); + + List parameters = new ArrayList<>(); + List 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(null, null); + } + + public static Style getStyle(ChatTypeDecoration decoration) { + if (decoration instanceof TextDecoration textDecoration) { + return textDecoration.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); @@ -57,50 +85,6 @@ public final class TextDecoration { builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); } } - style = builder.build(); - - this.parameters = EnumSet.noneOf(Parameter.class); - List parameters = tag.getList("parameters", NbtType.STRING); - for (String parameter : parameters) { - this.parameters.add(Parameter.valueOf(parameter.toUpperCase(Locale.ROOT))); - } - } - - public String translationKey() { - return translationKey; - } - - public Style style() { - return style; - } - - public Set parameters() { - return parameters; - } - - @Override - public String toString() { - return "TextDecoration{" + - "translationKey='" + translationKey + '\'' + - ", style=" + style + - ", parameters=" + parameters + - '}'; - } - - public static TextDecoration readChatType(RegistryEntry entry) { - // Note: The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla. - NbtMap tag = entry.getData(); - NbtMap chat = tag.getCompound("chat", null); - TextDecoration textDecoration = null; - if (chat != null) { - textDecoration = new TextDecoration(chat); - } - return textDecoration; - } - - public enum Parameter { - CONTENT, - SENDER, - TARGET + return builder.build(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java index e1407346a..b51d86d13 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java @@ -36,15 +36,14 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse; import org.cloudburstmc.protocol.bedrock.packet.PlayerEnchantOptionsPacket; import org.geysermc.geyser.inventory.*; -import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; import java.util.Arrays; -import java.util.Locale; public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { public EnchantingInventoryTranslator() { @@ -73,16 +72,16 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla // The Bedrock index might need changed, so let's look it up and see. int bedrockIndex = value; if (bedrockIndex != -1) { - Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + Enchantment.JavaEnchantment.of(bedrockIndex).name().toLowerCase(Locale.ROOT)); - if (enchantment != null) { + Enchantment enchantment = session.getRegistryCache().enchantments().byId(value); + if (enchantment != null && enchantment.bedrockEnchantment() != null) { // Convert the Java enchantment index to Bedrock's - bedrockIndex = enchantment.ordinal(); + bedrockIndex = enchantment.bedrockEnchantment().ordinal(); } else { // There is no Bedrock enchantment equivalent bedrockIndex = -1; } } - enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(value, bedrockIndex); + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(bedrockIndex); break; case 7: case 8: diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 5e4ffcafd..4c426b410 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -86,7 +86,7 @@ public abstract class InventoryTranslator { put(ContainerType.BEACON, new BeaconInventoryTranslator()); put(ContainerType.BREWING_STAND, new BrewingInventoryTranslator()); put(ContainerType.CARTOGRAPHY, new CartographyInventoryTranslator()); - //put(ContainerType.CRAFTER_3x3, new CrafterInventoryTranslator()); todo Output slot is currently broken + put(ContainerType.CRAFTER_3x3, new CrafterInventoryTranslator()); put(ContainerType.CRAFTING, new CraftingInventoryTranslator()); put(ContainerType.ENCHANTMENT, new EnchantingInventoryTranslator()); put(ContainerType.HOPPER, new HopperInventoryTranslator()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java index 18d6a22eb..bc6ff2adf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -423,7 +423,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } // Reference the creative items list we send to the client to know what it's asking of us ItemData creativeItem = creativeItems[creativeId]; - javaCreativeItem = ItemTranslator.translateToJava(creativeItem, session.getItemMappings()); + javaCreativeItem = ItemTranslator.translateToJava(session, creativeItem); break; } case CRAFT_RESULTS_DEPRECATED: { diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 251aacba8..85b4c6264 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -47,7 +47,6 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; @@ -66,6 +65,12 @@ public final class ItemTranslator { * The order of these slots is their display order on Java Edition clients */ private static final EnumMap SLOT_NAMES; + private static final ItemAttributeModifiers.EquipmentSlotGroup[] ARMOR_SLOT_NAMES = new ItemAttributeModifiers.EquipmentSlotGroup[] { + ItemAttributeModifiers.EquipmentSlotGroup.HEAD, + ItemAttributeModifiers.EquipmentSlotGroup.CHEST, + ItemAttributeModifiers.EquipmentSlotGroup.LEGS, + ItemAttributeModifiers.EquipmentSlotGroup.FEET + }; private static final DecimalFormat ATTRIBUTE_FORMAT = new DecimalFormat("0.#####"); static { @@ -83,25 +88,21 @@ public final class ItemTranslator { private ItemTranslator() { } - /** - * @param mappings item mappings to use while translating. This can't just be a Geyser session as this method is used - * when loading recipes. - */ - public static ItemStack translateToJava(ItemData data, ItemMappings mappings) { + public static ItemStack translateToJava(GeyserSession session, ItemData data) { if (data == null) { return new ItemStack(Items.AIR_ID); } - ItemMapping bedrockItem = mappings.getMapping(data); + ItemMapping bedrockItem = session.getItemMappings().getMapping(data); Item javaItem = bedrockItem.getJavaItem(); - GeyserItemStack itemStack = javaItem.translateToJava(data, bedrockItem, mappings); + GeyserItemStack itemStack = javaItem.translateToJava(data, bedrockItem, session.getItemMappings()); NbtMap nbt = data.getTag(); if (nbt != null && !nbt.isEmpty()) { // translateToJava may have added components DataComponents components = itemStack.getComponents() == null ? new DataComponents(new HashMap<>()) : itemStack.getComponents(); - javaItem.translateNbtToJava(nbt, components, bedrockItem); + javaItem.translateNbtToJava(session, nbt, components, bedrockItem); if (!components.getDataComponents().isEmpty()) { itemStack.setComponents(components); } @@ -134,11 +135,13 @@ public final class ItemTranslator { .build(); } - private static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, Item javaItem, ItemMapping bedrockItem, int count, @Nullable DataComponents components) { + public static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, Item javaItem, ItemMapping bedrockItem, int count, @Nullable DataComponents components) { BedrockItemBuilder nbtBuilder = new BedrockItemBuilder(); + boolean hideTooltips = false; if (components != null) { javaItem.translateComponentsToBedrock(session, components, nbtBuilder); + if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true; } String customName = getCustomName(session, components, bedrockItem); @@ -148,13 +151,13 @@ public final class ItemTranslator { if (components != null) { ItemAttributeModifiers attributeModifiers = components.get(DataComponentType.ATTRIBUTE_MODIFIERS); - if (attributeModifiers != null && attributeModifiers.isShowInTooltip()) { + if (attributeModifiers != null && attributeModifiers.isShowInTooltip() && !hideTooltips) { // only add if attribute modifiers do not indicate to hide them addAttributeLore(attributeModifiers, nbtBuilder, session.locale()); } } - if (session.isAdvancedTooltips()) { + if (session.isAdvancedTooltips() && !hideTooltips) { addAdvancedTooltips(components, nbtBuilder, javaItem, session.locale()); } @@ -212,7 +215,12 @@ public final class ItemTranslator { } ItemAttributeModifiers.EquipmentSlotGroup slotGroup = entry.getSlot(); - if (slotGroup == ItemAttributeModifiers.EquipmentSlotGroup.ANY) { + if (slotGroup == ItemAttributeModifiers.EquipmentSlotGroup.ARMOR) { + // modifier applies to all armor slots + for (ItemAttributeModifiers.EquipmentSlotGroup slot : ARMOR_SLOT_NAMES) { + slotsToModifiers.computeIfAbsent(slot, s -> new ArrayList<>()).add(loreEntry); + } + } else if (slotGroup == ItemAttributeModifiers.EquipmentSlotGroup.ANY) { // modifier applies to all slots implicitly for (var slot : SLOT_NAMES.keySet()) { slotsToModifiers.computeIfAbsent(slot, s -> new ArrayList<>()).add(loreEntry); @@ -252,7 +260,7 @@ public final class ItemTranslator { return null; } - String name = modifier.getName().replace("minecraft:", ""); + String name = modifier.getId().asMinimalString(); // the namespace does not need to be present, but if it is, the java client ignores it as of pre-1.20.5 ModifierOperation operation = modifier.getOperation(); @@ -410,13 +418,15 @@ public final class ItemTranslator { if (components != null) { // ItemStack#getHoverName as of 1.20.5 Component customName = components.get(DataComponentType.CUSTOM_NAME); - if (customName == null) { - customName = components.get(DataComponentType.ITEM_NAME); - } if (customName != null) { - // Get the translated name and prefix it with a reset char return MessageTranslator.convertMessage(customName, session.locale()); } + customName = components.get(DataComponentType.ITEM_NAME); + if (customName != null) { + // Get the translated name and prefix it with a reset char to prevent italics - matches Java Edition + // behavior as of 1.21 + return ChatColor.RESET + ChatColor.ESCAPE + translationColor + MessageTranslator.convertMessage(customName, session.locale()); + } } if (mapping.hasTranslation()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java index d70f53e2c..166089b6b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java @@ -45,7 +45,7 @@ import org.geysermc.geyser.session.GeyserSession; public class BiomeTranslator { public static int loadServerBiome(RegistryEntry entry) { - String javaIdentifier = entry.getId(); + String javaIdentifier = entry.getId().asString(); return Registries.BIOME_IDENTIFIERS.get().getOrDefault(javaIdentifier, 0); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BrushableBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BrushableBlockEntityTranslator.java index f0d632041..de0ce62b3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BrushableBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BrushableBlockEntityTranslator.java @@ -37,7 +37,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType @BlockEntity(type = BlockEntityType.BRUSHABLE_BLOCK) public class BrushableBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, @Nullable NbtMap javaNbt, BlockState blockState) { if (javaNbt == null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DecoratedPotBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DecoratedPotBlockEntityTranslator.java index 33c884c55..c22d906cf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DecoratedPotBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DecoratedPotBlockEntityTranslator.java @@ -34,7 +34,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType @BlockEntity(type = BlockEntityType.DECORATED_POT) public class DecoratedPotBlockEntityTranslator extends BlockEntityTranslator { - @Override public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { if (javaNbt == null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java index 6fea10e2e..9cbd5f433 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.level.block.entity; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; -import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.property.ChestType; import org.geysermc.geyser.level.block.property.Properties; import org.geysermc.geyser.level.block.type.BlockState; @@ -40,12 +39,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType */ @BlockEntity(type = { BlockEntityType.CHEST, BlockEntityType.TRAPPED_CHEST }) public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { - if (!(blockState.is(Blocks.CHEST) || blockState.is(Blocks.TRAPPED_CHEST))) { - return; - } if (blockState.getValue(Properties.CHEST_TYPE) != ChestType.SINGLE) { int x = (int) bedrockNbt.get("x"); int z = (int) bedrockNbt.get("z"); diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index cdbb20c44..c2d457202 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -50,7 +50,6 @@ import java.util.concurrent.ExecutionException; @BlockEntity(type = BlockEntityType.SKULL) public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { Integer rotation = blockState.getValue(Properties.ROTATION_16); diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java index 4b6a630ba..14f706f27 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java @@ -39,7 +39,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType @BlockEntity(type = BlockEntityType.MOB_SPAWNER) public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { - @Override public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, @Nullable NbtMap javaNbt, BlockState blockState) { if (javaNbt == null) { @@ -108,7 +107,7 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { bedrockNbt.put("isMovable", (byte) 1); } - static void translateSpawnData(@NonNull NbtMapBuilder builder, @Nullable NbtMap spawnData) { + private static void translateSpawnData(@NonNull NbtMapBuilder builder, @Nullable NbtMap spawnData) { if (spawnData == null) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java index 79fb3da6b..dcf4f85ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java @@ -39,7 +39,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType @BlockEntity(type = BlockEntityType.STRUCTURE_BLOCK) public class StructureBlockBlockEntityTranslator extends BlockEntityTranslator { - @Override public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, @Nullable NbtMap javaNbt, BlockState blockState) { if (javaNbt == null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/TrialSpawnerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/TrialSpawnerBlockEntityTranslator.java index a4c158a15..25925a89b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/TrialSpawnerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/TrialSpawnerBlockEntityTranslator.java @@ -27,23 +27,31 @@ package org.geysermc.geyser.translator.level.block.entity; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; +import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; @BlockEntity(type = BlockEntityType.TRIAL_SPAWNER) public class TrialSpawnerBlockEntityTranslator extends BlockEntityTranslator { - + // Note that it would appear block entity updates don't include the NBT, but we do need it on chunk load. @Override public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { if (javaNbt == null) { return; } - // trial spawners have "spawn_data" instead of "SpawnData" - SpawnerBlockEntityTranslator.translateSpawnData(bedrockNbt, javaNbt.getCompound("spawn_data", null)); - - // Because trial spawners don't exist on bedrock yet - bedrockNbt.put("id", "MobSpawner"); + NbtMap entityData = javaNbt.getCompound("spawn_data").getCompound("entity"); + if (entityData.isEmpty()) { + return; + } + NbtMapBuilder spawnData = NbtMap.builder(); + EntityDefinition definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityData.getString("id")); + if (definition != null) { + spawnData.putString("TypeId", definition.identifier()); + } + spawnData.putInt("Weight", entityData.getInt("Size", 1)); // ??? presumably since these are the only other two extra attributes + bedrockNbt.putCompound("spawn_data", spawnData.build()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/VaultBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/VaultBlockEntityTranslator.java new file mode 100644 index 000000000..81be5b33a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/VaultBlockEntityTranslator.java @@ -0,0 +1,150 @@ +/* + * 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.level.block.entity; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.nbt.NbtType; +import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; +import org.cloudburstmc.protocol.common.util.TriConsumer; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.inventory.item.Potion; +import org.geysermc.geyser.item.enchantment.Enchantment; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.geyser.translator.item.ItemTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@BlockEntity(type = BlockEntityType.VAULT) +public class VaultBlockEntityTranslator extends BlockEntityTranslator { + // Bedrock 1.21 does not send the position nor ID in the tag. + @Override + public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, @Nullable NbtMap javaNbt, BlockState blockState) { + NbtMapBuilder builder = NbtMap.builder(); + if (javaNbt != null) { + translateTag(session, builder, javaNbt, blockState); + } + return builder.build(); + } + + @Override + public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { + NbtMap sharedData = javaNbt.getCompound("shared_data"); + + NbtMap item = sharedData.getCompound("display_item"); + ItemMapping mapping = session.getItemMappings().getMapping(item.getString("id")); + if (mapping == null) { + bedrockNbt.putCompound("display_item", NbtMap.builder() + .putByte("Count", (byte) 0) + .putShort("Damage", (short) 0) + .putString("Name", "") + .putByte("WasPickedUp", (byte) 0).build()); + } else { + int count = item.getInt("count"); + NbtMap componentsTag = item.getCompound("components"); + NbtMapBuilder itemAsNbt; + if (!componentsTag.isEmpty()) { + DataComponents components = new DataComponents(new HashMap<>()); + for (Map.Entry entry : componentsTag.entrySet()) { + var consumer = DATA_COMPONENT_DECODERS.get(entry.getKey()); + if (consumer != null) { + consumer.accept(session, (NbtMap) entry.getValue(), components); + } + } + ItemData bedrockItem = ItemTranslator.translateToBedrock(session, mapping.getJavaItem(), mapping, count, components).build(); + itemAsNbt = BedrockItemBuilder.createItemNbt(mapping, bedrockItem.getCount(), bedrockItem.getDamage()); + if (bedrockItem.getTag() != null) { + itemAsNbt.putCompound("tag", bedrockItem.getTag()); + } + } else { + itemAsNbt = BedrockItemBuilder.createItemNbt(mapping, count, mapping.getBedrockData()); + } + bedrockNbt.putCompound("display_item", itemAsNbt.build()); + } + + List connectedPlayers = sharedData.getList("connected_players", NbtType.INT_ARRAY); + LongList bedrockPlayers = new LongArrayList(connectedPlayers.size()); + for (int[] player : connectedPlayers) { + UUID uuid = uuidFromIntArray(player); + if (uuid.equals(session.getPlayerEntity().getUuid())) { + bedrockPlayers.add(session.getPlayerEntity().getGeyserId()); + } else { + PlayerEntity playerEntity = session.getEntityCache().getPlayerEntity(uuid); + if (playerEntity != null) { + bedrockPlayers.add(playerEntity.getGeyserId()); + } + } + } + bedrockNbt.putList("connected_players", NbtType.LONG, bedrockPlayers); + + // Fill this in, since as of Java 1.21, Bedrock always seems to include it, but Java assumes the default + // if it is not sent over the network + bedrockNbt.putFloat("connected_particle_range", (float) sharedData.getDouble("connected_particles_range", 4.5d)); + } + + // From ViaVersion! thank u!! + private static UUID uuidFromIntArray(int[] parts) { + return new UUID((long) parts[0] << 32 | (parts[1] & 0xFFFFFFFFL), (long) parts[2] << 32 | (parts[3] & 0xFFFFFFFFL)); + } + + // This might be easier to maintain in the long run so items don't have two translate methods. + // Also, it's not out of the question that block entities get the data component treatment, likely rendering this useless. + // The goal is to just translate the basics so clients know what potion is roughly present, and that any enchantment even exists. + private static final Map> DATA_COMPONENT_DECODERS = Map.of( + "minecraft:potion_contents", (session, tag, components) -> { + String potionId = tag.getString("potion"); + Potion potion = Potion.getByJavaIdentifier(potionId); + components.put(DataComponentType.POTION_CONTENTS, potion.toComponent()); + }, + "minecraft:enchantments", (session, tag, components) -> { // Enchanted books already have glint. Translating them doesn't matter. + NbtMap levels = tag.getCompound("levels"); + List enchantmentRegistry = session.getRegistryCache().enchantments().values(); + Int2ObjectMap enchantments = new Int2ObjectOpenHashMap<>(levels.size()); + for (Map.Entry entry : levels.entrySet()) { + for (int i = 0; i < enchantmentRegistry.size(); i++) { + if (enchantmentRegistry.get(i).identifier().equals(entry.getKey())) { + enchantments.put(i, (Integer) entry.getValue()); + } + } + } + components.put(DataComponentType.ENCHANTMENTS, new ItemEnchantments(enchantments, true)); + }); +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index b5e923ee6..a7e4bc656 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetJigsawBlockPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundSignUpdatePacket; import org.cloudburstmc.math.vector.Vector3i; @@ -119,7 +120,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator legacySlots = packet.getLegacySlots(); if (packet.getActions().size() == 1 && !legacySlots.isEmpty()) { @@ -639,8 +638,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator session.sendDownstreamGamePacket(new ServerboundUseItemPacket(Hand.MAIN_HAND, session.getWorldCache().nextPredictionSequence())), + session.scheduleInEventLoop(() -> session.useItem(Hand.MAIN_HAND), 50, TimeUnit.MILLISECONDS); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java index d1246a0b4..da4a5059b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -58,7 +58,7 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran // Build the NBT data for the update NbtMapBuilder builder = NbtMap.builder(); - builder.putInt("dim", DimensionUtils.javaToBedrock(pos.dimension())); + builder.putInt("dim", DimensionUtils.javaToBedrock(pos.dimension().asString())); builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId())); builder.putByte("version", (byte) 1); // Not sure what this is for diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 959797d41..6834d3190 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -160,7 +160,7 @@ public class BedrockActionTranslator extends PacketTranslator { @@ -93,29 +92,42 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= packet.getPosition().getY()) { + if (entity.getPosition().getY() >= packet.getPosition().getY() && !isBelowVoid) { int floorY = position.getFloorY(); - // The void floor is offset about 40 blocks below the bottom of the world - BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); - int voidFloorLocation = bedrockDimension.minY() - 40; - teleportThroughVoidFloor = floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation; - if (teleportThroughVoidFloor) { - // https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground. - onGround = false; - } + int voidFloorLocation = entity.voidFloorPosition(); + teleportThroughVoidFloor = floorY <= (voidFloorLocation + 1) && floorY >= voidFloorLocation; } else { teleportThroughVoidFloor = false; } + if (teleportThroughVoidFloor || isBelowVoid) { + // https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground. + onGround = false; + } + + if (isBelowVoid) { + int floorY = position.getFloorY(); + int voidFloorLocation = entity.voidFloorPosition(); + mustResyncPosition = floorY < voidFloorLocation && floorY >= voidFloorLocation - 1; + } else { + mustResyncPosition = false; + } + + double yPosition = position.getY(); + if (entity.isVoidPositionDesynched()) { // not using the cached variable on purpose + yPosition += 4; // We are de-synched since we had to teleport below the void floor. + } + Packet movePacket; if (rotationChanged) { // Send rotation updates as well movePacket = new ServerboundMovePlayerPosRotPacket( onGround, - position.getX(), position.getY(), position.getZ(), + position.getX(), yPosition, position.getZ(), yaw, pitch ); entity.setYaw(yaw); @@ -123,7 +135,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator ALL_BLOCK_NAMES = Suppliers.memoize(() -> BlockRegistries.JAVA_BLOCKS.get().stream().map(block -> block.javaIdentifier().toString()).toArray(String[]::new)); private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers(); - private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.values().stream().map(AttributeType::getIdentifier).toList().toArray(new String[0]); + private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.values().stream().map(type -> type.getIdentifier().asString()).toList().toArray(new String[0]); private static final String[] ENUM_BOOLEAN = {"true", "false"}; private static final String[] VALID_COLORS; private static final String[] VALID_SCOREBOARD_SLOTS; @@ -264,10 +265,10 @@ public class JavaCommandsTranslator extends PacketTranslator ATTRIBUTES; - case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; + case "minecraft:enchantment" -> context.getEnchantments(); case "minecraft:entity_type" -> context.getEntityTypes(); case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS; case "minecraft:worldgen/biome" -> tags ? context.getBiomesWithTags() : context.getBiomes(); @@ -292,6 +293,7 @@ public class JavaCommandsTranslator extends PacketTranslator { @@ -98,7 +101,7 @@ public class JavaLoginTranslator extends PacketTranslator> recipeTypes = Registries.CRAFTING_DATA.forVersion(session.getUpstream().getProtocolVersion()); - // Get the last known network ID (first used for the pregenerated recipes) and increment from there. - int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1; boolean sendTrimRecipes = false; Map> recipeIDs = session.getJavaToBedrockRecipeIds(); - Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); + recipeIDs.clear(); + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(); Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); + RecipeContext context = new RecipeContext(session, craftingDataPacket, recipeMap); + for (Recipe recipe : packet.getRecipes()) { switch (recipe.getType()) { case CRAFTING_SHAPELESS -> { ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData(); - ItemData output = ItemTranslator.translateToBedrock(session, shapelessRecipeData.getResult()); - if (!output.isValid()) { - // Likely modded item that Bedrock will complain about if it persists - continue; + List bedrockRecipeIDs = context.translateShapelessRecipe(new GeyserShapelessRecipe(shapelessRecipeData)); + if (bedrockRecipeIDs != null) { + context.addRecipeIdentifier(session, recipe.getIdentifier().asString(), bedrockRecipeIDs); } - // Strip NBT - tools won't appear in the recipe book otherwise - output = output.toBuilder().tag(null).build(); - ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); - if (inputCombinations == null) { - continue; - } - - List bedrockRecipeIDs = new ArrayList<>(); - for (ItemDescriptorWithCount[] inputs : inputCombinations) { - UUID uuid = UUID.randomUUID(); - bedrockRecipeIDs.add(uuid.toString()); - craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(), - Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID)); - recipeMap.put(netId++, new GeyserShapelessRecipe(shapelessRecipeData)); - } - addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs); } case CRAFTING_SHAPED -> { ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData(); - ItemData output = ItemTranslator.translateToBedrock(session, shapedRecipeData.getResult()); - if (!output.isValid()) { - // Likely modded item that Bedrock will complain about if it persists - continue; + List bedrockRecipeIDs = context.translateShapedRecipe(new GeyserShapedRecipe(shapedRecipeData)); + if (bedrockRecipeIDs != null) { + context.addRecipeIdentifier(session, recipe.getIdentifier().asString(), bedrockRecipeIDs); } - // See above - output = output.toBuilder().tag(null).build(); - ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); - if (inputCombinations == null) { - continue; - } - - List bedrockRecipeIDs = new ArrayList<>(); - for (ItemDescriptorWithCount[] inputs : inputCombinations) { - UUID uuid = UUID.randomUUID(); - bedrockRecipeIDs.add(uuid.toString()); - craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(), - shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs), - Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID)); - recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData)); - } - addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs); } case STONECUTTING -> { StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData(); @@ -195,10 +160,10 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator(Collections.singletonList(id))); } @@ -212,13 +177,48 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { // Paper 1.20 seems to send only one recipe, which seems to be hardcoded to include all recipes. // We can send the equivalent Bedrock MultiRecipe! :) - craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("685a742a-c42e-4a4e-88ea-5eb83fc98e5b"), netId++)); + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("685a742a-c42e-4a4e-88ea-5eb83fc98e5b"), context.getAndIncrementNetId())); + } + case CRAFTING_SPECIAL_BOOKCLONING -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), context.getAndIncrementNetId())); + } + case CRAFTING_SPECIAL_REPAIRITEM -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000001"), context.getAndIncrementNetId())); + } + case CRAFTING_SPECIAL_MAPEXTENDING -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), context.getAndIncrementNetId())); + } + case CRAFTING_SPECIAL_MAPCLONING -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId())); } default -> { - List craftingData = recipeTypes.get(recipe.getType()); - if (craftingData != null) { - addSpecialRecipesIdentifiers(session, recipe, craftingData); - craftingDataPacket.getCraftingData().addAll(craftingData); + List recipes = Registries.RECIPES.get(recipe.getType()); + if (recipes != null) { + List bedrockRecipeIds = new ArrayList<>(); + if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) { + // Only shaped recipe at this moment + for (GeyserRecipe builtInRecipe : recipes) { + var recipeIds = context.translateShapedRecipe((GeyserShapedRecipe) builtInRecipe); + if (recipeIds != null) { + bedrockRecipeIds.addAll(recipeIds); + } + } + } else if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_SHULKERBOXCOLORING) { + for (GeyserRecipe builtInRecipe : recipes) { + var recipeIds = context.translateShulkerBoxRecipe((GeyserShapelessRecipe) builtInRecipe); + if (recipeIds != null) { + bedrockRecipeIds.addAll(recipeIds); + } + } + } else { + for (GeyserRecipe builtInRecipe : recipes) { + var recipeIds = context.translateShapelessRecipe((GeyserShapelessRecipe) builtInRecipe); + if (recipeIds != null) { + bedrockRecipeIds.addAll(recipeIds); + } + } + } + context.addSpecialRecipesIdentifiers(recipe, bedrockRecipeIds); } } } @@ -250,17 +250,17 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator craftingData) { - String javaRecipeID = recipe.getIdentifier(); - - switch (recipe.getType()) { - case CRAFTING_SPECIAL_BOOKCLONING, CRAFTING_SPECIAL_REPAIRITEM, CRAFTING_SPECIAL_MAPEXTENDING, CRAFTING_SPECIAL_MAPCLONING: - // We do not want to (un)lock these, since BDS does not do it for MultiRecipes - return; - case CRAFTING_SPECIAL_SHULKERBOXCOLORING: - // BDS (un)locks the dyeing with the shulker box recipe, Java never - we want BDS behavior for ease of use - javaRecipeID = "minecraft:shulker_box"; - break; - case CRAFTING_SPECIAL_TIPPEDARROW: - // similar as above - javaRecipeID = "minecraft:arrow"; - break; - } - List bedrockRecipeIDs = new ArrayList<>(); - - // defined in the recipes.json mappings file: Only tipped arrows use shaped recipes, we need the cast for the identifier - if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) { - for (RecipeData data : craftingData) { - bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData) data).getId()); - } - } else { - for (RecipeData data : craftingData) { - bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData) data).getId()); - } - } - addRecipeIdentifier(session, javaRecipeID, bedrockRecipeIDs); } //TODO: rewrite @@ -323,7 +291,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator, IntSet> squashedOptions = new HashMap<>(); for (int i = 0; i < ingredients.length; i++) { @@ -407,17 +375,6 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator bedrockIdentifiers) { - session.getJavaToBedrockRecipeIds().computeIfAbsent(javaIdentifier, k -> new ArrayList<>()).addAll(bedrockIdentifiers); - } - - @EqualsAndHashCode - @AllArgsConstructor - private static class GroupedItem { - ItemDefinition id; - int count; - } - private List getSmithingTransformRecipes(GeyserSession session) { List recipes = new ArrayList<>(); ItemMapping template = session.getItemMappings().getStoredItems().upgradeTemplate(); @@ -442,4 +399,120 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator recipeMap; + // Get the last known network ID (first used for some pregenerated recipes) and increment from there. + private int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1; + + private RecipeContext(GeyserSession session, CraftingDataPacket packet, Int2ObjectMap recipeMap) { + this.session = session; + this.packet = packet; + this.recipeMap = recipeMap; + } + + List translateShulkerBoxRecipe(GeyserShapelessRecipe recipe) { + ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + if (!output.isValid()) { + // Likely modded item that Bedrock will complain about if it persists + return null; + } + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); + if (inputCombinations == null) { + return null; + } + + List bedrockRecipeIDs = new ArrayList<>(); + for (ItemDescriptorWithCount[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + bedrockRecipeIDs.add(uuid.toString()); + packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shulkerBox(uuid.toString(), + Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId)); + recipeMap.put(netId++, recipe); + } + return bedrockRecipeIDs; + } + + List translateShapelessRecipe(GeyserShapelessRecipe recipe) { + ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + if (!output.isValid()) { + // Likely modded item that Bedrock will complain about if it persists + return null; + } + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); + if (inputCombinations == null) { + return null; + } + + List bedrockRecipeIDs = new ArrayList<>(); + for (ItemDescriptorWithCount[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + bedrockRecipeIDs.add(uuid.toString()); + packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(), + Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID)); + recipeMap.put(netId++, recipe); + } + return bedrockRecipeIDs; + } + + List translateShapedRecipe(GeyserShapedRecipe recipe) { + ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + if (!output.isValid()) { + // Likely modded item that Bedrock will complain about if it persists + return null; + } + // See above + output = output.toBuilder().tag(null).build(); + ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); + if (inputCombinations == null) { + return null; + } + + List bedrockRecipeIDs = new ArrayList<>(); + for (ItemDescriptorWithCount[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + bedrockRecipeIDs.add(uuid.toString()); + packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(), + recipe.width(), recipe.height(), Arrays.asList(inputs), + Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID)); + recipeMap.put(netId++, recipe); + } + return bedrockRecipeIDs; + } + + void addSpecialRecipesIdentifiers(Recipe recipe, List identifiers) { + String javaRecipeID = switch (recipe.getType()) { + case CRAFTING_SPECIAL_SHULKERBOXCOLORING -> + // BDS (un)locks the dyeing with the shulker box recipe, Java never - we want BDS behavior for ease of use + "minecraft:shulker_box"; + case CRAFTING_SPECIAL_TIPPEDARROW -> + // similar as above + "minecraft:arrow"; + default -> recipe.getIdentifier().asString(); + }; + + addRecipeIdentifier(session, javaRecipeID, identifiers); + } + + void addRecipeIdentifier(GeyserSession session, String javaIdentifier, List bedrockIdentifiers) { + session.getJavaToBedrockRecipeIds().computeIfAbsent(javaIdentifier, k -> new ArrayList<>()).addAll(bedrockIdentifiers); + } + + int getAndIncrementNetId() { + return this.netId++; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java index d595e928f..15d47a285 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java @@ -25,15 +25,15 @@ package org.geysermc.geyser.translator.protocol.java.entity; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityLinkPacket; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.MobEntity; +import org.geysermc.geyser.entity.type.Leashable; 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.entity.ClientboundSetEntityLinkPacket; /** * Called when a leash is attached, removed or updated from an entity @@ -44,16 +44,16 @@ public class JavaSetEntityLinkTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockDestructionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockDestructionTranslator.java index 700f7e701..674a1c583 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockDestructionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockDestructionTranslator.java @@ -27,9 +27,7 @@ package org.geysermc.geyser.translator.protocol.java.level; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.BlockState; -import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -43,7 +41,7 @@ public class JavaBlockDestructionTranslator extends PacketTranslator { + BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); + blockEntityPacket.setBlockPosition(position); + + NbtMapBuilder builder = BlockEntityTranslator.getConstantBedrockTag("DecoratedPot", position); + builder.putList("sherds", NbtType.STRING, sherds); + builder.putByte("animation", switch (potValue.getWobbleStyle()) { + case POSITIVE -> (byte) 2; + case NEGATIVE -> (byte) 1; + }); + + blockEntityPacket.setData(builder.build()); + session.sendUpstreamPacket(blockEntityPacket); + }); + } else if (session.getGeyser().getLogger().isDebug()) { + session.getGeyser().getLogger().debug("Unhandled block event packet: " + packet); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java index 496f5982d..f840b8143 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java @@ -25,22 +25,24 @@ package org.geysermc.geyser.translator.protocol.java.level; -import org.geysermc.geyser.level.block.type.Block; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundExplodePacket; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; -import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket; -import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; -import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.SoundUtils; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.ExplosionInteraction; +import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundExplodePacket; + +import java.util.concurrent.ThreadLocalRandom; @Translator(packet = ClientboundExplodePacket.class) public class JavaExplodeTranslator extends PacketTranslator { @@ -56,27 +58,39 @@ public class JavaExplodeTranslator extends PacketTranslator> 4) - yOffset]; - BlockState blockState = BlockRegistries.BLOCK_STATES.get(section.get(x, y & 0xF, z)); + BlockState blockState = BlockState.of(section.get(x, y & 0xF, z)); // Note that, since 1.20.5, tags can be null, but Bedrock still needs a default tag to render the item // Also, some properties - like banner base colors - are part of the tag and is processed here. BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(type); - bedrockBlockEntities.add(blockEntityTranslator.getBlockEntityTag(session, type, x + chunkBlockX, y, z + chunkBlockZ, tag, blockState)); - // Check for custom skulls - if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.containsKey("profile")) { - BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, tag, Vector3i.from(x + chunkBlockX, y, z + chunkBlockZ), blockState); - if (blockDefinition != null) { - int bedrockSectionY = (y >> 4) - (bedrockDimension.minY() >> 4); - int subChunkIndex = (y >> 4) + (bedrockDimension.minY() >> 4); - if (0 <= bedrockSectionY && bedrockSectionY < maxBedrockSectionY) { - // Custom skull is in a section accepted by Bedrock - GeyserChunkSection bedrockSection = sections[bedrockSectionY]; - IntList palette = bedrockSection.getBlockStorageArray()[0].getPalette(); - if (palette instanceof IntImmutableList || palette instanceof IntLists.Singleton) { - // TODO there has to be a better way to expand the palette .-. - bedrockSection = bedrockSection.copy(subChunkIndex); - sections[bedrockSectionY] = bedrockSection; + // The Java server can send block entity data for blocks that aren't actually those blocks. + // A Java client ignores these + if (type == blockState.block().blockEntityType()) { + bedrockBlockEntities.add(blockEntityTranslator.getBlockEntityTag(session, type, x + chunkBlockX, y, z + chunkBlockZ, tag, blockState)); + + // Check for custom skulls + if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.containsKey("profile")) { + BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, tag, Vector3i.from(x + chunkBlockX, y, z + chunkBlockZ), blockState); + if (blockDefinition != null) { + int bedrockSectionY = (y >> 4) - (bedrockDimension.minY() >> 4); + int subChunkIndex = (y >> 4) + (bedrockDimension.minY() >> 4); + if (0 <= bedrockSectionY && bedrockSectionY < maxBedrockSectionY) { + // Custom skull is in a section accepted by Bedrock + GeyserChunkSection bedrockSection = sections[bedrockSectionY]; + IntList palette = bedrockSection.getBlockStorageArray()[0].getPalette(); + if (palette instanceof IntImmutableList || palette instanceof IntLists.Singleton) { + // TODO there has to be a better way to expand the palette .-. + bedrockSection = bedrockSection.copy(subChunkIndex); + sections[bedrockSectionY] = bedrockSection; + } + bedrockSection.setFullBlock(x, y & 0xF, z, 0, blockDefinition.getRuntimeId()); } - bedrockSection.setFullBlock(x, y & 0xF, z, 0, blockDefinition.getRuntimeId()); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index cb8a8e60f..d4e317af4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java @@ -25,28 +25,27 @@ package org.geysermc.geyser.translator.protocol.java.level; -import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; -import org.geysermc.mcprotocollib.protocol.data.game.level.event.*; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.protocol.bedrock.data.ParticleType; import org.cloudburstmc.protocol.bedrock.data.SoundEvent; -import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket; -import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; -import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; -import org.cloudburstmc.protocol.bedrock.packet.TextPacket; +import org.cloudburstmc.protocol.bedrock.packet.*; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.level.JukeboxSong; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.SoundMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.level.event.LevelEventTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.SoundUtils; +import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; +import org.geysermc.mcprotocollib.protocol.data.game.level.event.*; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; import java.util.Collections; -import java.util.Locale; import java.util.Set; @Translator(packet = ClientboundLevelEventPacket.class) @@ -58,25 +57,49 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE -> { effectPacket.setType(ParticleType.BRUSH_DUST); session.playSoundEvent(SoundEvent.BRUSH_COMPLETED, pos); // todo 1.20.2 verify this } - case COMPOSTER -> { + case COMPOSTER_FILL -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH); ComposterEventData composterEventData = (ComposterEventData) packet.getData(); @@ -124,7 +146,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case LAVA_FIZZ -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EVAPORATE); effectPacket.setPosition(pos.add(-0.5f, 0.7f, -0.5f)); @@ -137,7 +159,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case REDSTONE_TORCH_BURNOUT -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EVAPORATE); effectPacket.setPosition(pos.add(-0.5f, 0, -0.5f)); @@ -150,7 +172,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case END_PORTAL_FRAME_FILL -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EVAPORATE); effectPacket.setPosition(pos.add(-0.5f, 0.3125f, -0.5f)); @@ -163,8 +185,8 @@ public class JavaLevelEventTranslator extends PacketTranslator { - if (levelEvent == LevelEventType.SMOKE) { + case PARTICLES_SHOOT_SMOKE, PARTICLES_SHOOT_WHITE_SMOKE -> { + if (levelEvent == LevelEventType.PARTICLES_SHOOT_SMOKE) { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_SHOOT); } else { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_SHOOT_WHITE_SMOKE); @@ -203,13 +225,13 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case PARTICLES_DESTROY_BLOCK -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_DESTROY_BLOCK); BreakBlockEventData breakBlockEventData = (BreakBlockEventData) packet.getData(); effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakBlockEventData.getBlockState())); } - case BREAK_SPLASH_POTION, BREAK_SPLASH_POTION2 -> { + case PARTICLES_SPELL_POTION_SPLASH, PARTICLES_INSTANT_POTION_SPLASH -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_POTION_SPLASH); effectPacket.setPosition(pos.add(0, -0.5f, 0)); @@ -225,16 +247,16 @@ public class JavaLevelEventTranslator extends PacketTranslator effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EYE_OF_ENDER_DEATH); - case MOB_SPAWN -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java - case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> { - effectPacket.setType(levelEvent == LevelEventType.BONEMEAL_GROW ? org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_TURTLE_EGG : org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH); + case PARTICLES_EYE_OF_ENDER_DEATH -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EYE_OF_ENDER_DEATH); + case PARTICLES_MOBBLOCK_SPAWN -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java + case PARTICLES_AND_SOUND_PLANT_GROWTH -> { + effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH); BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData(); effectPacket.setData(growEventData.getParticleCount()); } - case EGG_CRACK -> effectPacket.setType(ParticleType.VILLAGER_HAPPY); // both the lil green sparkle - case ENDERDRAGON_FIREBALL_EXPLODE -> { + case PARTICLES_EGG_CRACK -> effectPacket.setType(ParticleType.VILLAGER_HAPPY); // both the lil green sparkle + case PARTICLES_DRAGON_FIREBALL_SPLASH -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EYE_OF_ENDER_DEATH); // TODO DragonFireballEventData fireballEventData = (DragonFireballEventData) packet.getData(); @@ -249,15 +271,15 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case PARTICLES_DRAGON_BLOCK_BREAK -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_GENERIC_SPAWN); effectPacket.setData(61); } - case EVAPORATE -> { + 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)); } - case END_GATEWAY_SPAWN -> { + case ANIMATION_END_GATEWAY_SPAWN -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EXPLOSION); LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket(); @@ -270,11 +292,11 @@ public class JavaLevelEventTranslator extends PacketTranslator effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_DRIPSTONE_DRIP); - case ELECTRIC_SPARK -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_ELECTRIC_SPARK); // Matches with a Bedrock server but doesn't seem to match up with Java - case WAX_ON -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_WAX_ON); - case WAX_OFF -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_WAX_OFF); - case SCRAPE -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_SCRAPE); - case SCULK_BLOCK_CHARGE -> { + case PARTICLES_ELECTRIC_SPARK -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_ELECTRIC_SPARK); // Matches with a Bedrock server but doesn't seem to match up with Java + case PARTICLES_AND_SOUND_WAX_ON -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_WAX_ON); + case PARTICLES_WAX_OFF -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_WAX_OFF); + case PARTICLES_SCRAPE -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_SCRAPE); + case PARTICLES_SCULK_CHARGE -> { SculkBlockChargeEventData eventData = (SculkBlockChargeEventData) packet.getData(); LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket(); // TODO add SCULK_BLOCK_CHARGE sound @@ -302,7 +324,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { + case PARTICLES_SCULK_SHRIEK -> { LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket(); levelEventPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_SCULK_SHRIEK); levelEventPacket.setTag( @@ -324,15 +346,24 @@ public class JavaLevelEventTranslator extends PacketTranslator { - LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket(); - levelSoundEvent.setIdentifier(""); - levelSoundEvent.setSound(SoundEvent.STOP_RECORD); - levelSoundEvent.setPosition(pos); - levelSoundEvent.setRelativeVolumeDisabled(false); - levelSoundEvent.setExtraData(-1); - levelSoundEvent.setBabySound(false); - session.sendUpstreamPacket(levelSoundEvent); + case SOUND_STOP_JUKEBOX_SONG -> { + String bedrockSound = session.getWorldCache().removeActiveRecord(origin); + if (bedrockSound == null) { + // Vanilla record + LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket(); + levelSoundEvent.setIdentifier(""); + levelSoundEvent.setSound(SoundEvent.STOP_RECORD); + levelSoundEvent.setPosition(pos); + levelSoundEvent.setRelativeVolumeDisabled(false); + levelSoundEvent.setExtraData(-1); + levelSoundEvent.setBabySound(false); + session.sendUpstreamPacket(levelSoundEvent); + } else { + // Custom record + StopSoundPacket stopSound = new StopSoundPacket(); + stopSound.setSoundName(bedrockSound); + session.sendUpstreamPacket(stopSound); + } return; } default -> { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java index bedddbc6e..83b5da219 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java @@ -92,7 +92,7 @@ public class JavaLevelParticlesTranslator extends PacketTranslator createParticle(GeyserSession session, Particle particle) { + public static @Nullable Function createParticle(GeyserSession session, Particle particle) { switch (particle.getType()) { case BLOCK -> { int blockState = session.getBlockMappings().getBedrockBlockId(((BlockParticleData) particle.getData()).getBlockState()); @@ -205,7 +205,7 @@ public class JavaLevelParticlesTranslator extends PacketTranslator chatTypeHolder, Component targetName, Component sender) { TextPacket textPacket = new TextPacket(); textPacket.setPlatformChatId(""); textPacket.setSourceName(""); @@ -330,14 +334,15 @@ public class MessageTranslator { textPacket.setNeedsTranslation(false); - TextDecoration decoration = session.getRegistryCache().chatTypes().byId(chatType); - if (decoration != null) { + ChatType chatType = chatTypeHolder.getOrCompute(session.getRegistryCache().chatTypes()::byId); + if (chatType != null && chatType.chat() != null) { + var chat = chatType.chat(); // As of 1.19 - do this to apply all the styling for signed messages // Though, Bedrock cannot care about the signed stuff. TranslatableComponent.Builder withDecoration = Component.translatable() - .key(decoration.translationKey()) - .style(decoration.style()); - Set parameters = decoration.parameters(); + .key(chat.translationKey()) + .style(TextDecoration.getStyle(chat)); + List parameters = chat.parameters(); List args = new ArrayList<>(3); if (parameters.contains(TextDecoration.Parameter.TARGET)) { args.add(targetName); @@ -348,7 +353,7 @@ public class MessageTranslator { if (parameters.contains(TextDecoration.Parameter.CONTENT)) { args.add(message); } - withDecoration.args(args); + withDecoration.arguments(args); textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.locale())); } else { session.getGeyser().getLogger().debug("Likely illegal chat type detection found."); @@ -420,6 +425,20 @@ public class MessageTranslator { return new String(newChars, 0, count - (whitespacesCount > 0 ? 1 : 0)).trim(); } + /** + * Deserialize an NbtMap provided from a registry into a string. + */ + // This may be a Component in the future. + public static String deserializeDescription(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 ""; + } + return translate; + } + public static void init() { // no-op } diff --git a/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java b/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java index fe5cc41e3..e73fe1a34 100644 --- a/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java @@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; @@ -43,11 +43,11 @@ public final class BlockUtils { private static boolean correctTool(GeyserSession session, Block block, String itemToolType) { return switch (itemToolType) { - case "axe" -> session.getTagCache().is(BlockTag.AXE_EFFECTIVE, block); - case "hoe" -> session.getTagCache().is(BlockTag.HOE_EFFECTIVE, block); - case "pickaxe" -> session.getTagCache().is(BlockTag.PICKAXE_EFFECTIVE, block); + case "axe" -> session.getTagCache().is(BlockTag.MINEABLE_AXE, block); + case "hoe" -> session.getTagCache().is(BlockTag.MINEABLE_HOE, block); + case "pickaxe" -> session.getTagCache().is(BlockTag.MINEABLE_PICKAXE, block); case "shears" -> session.getTagCache().is(BlockTag.LEAVES, block) || session.getTagCache().is(BlockTag.WOOL, block); - case "shovel" -> session.getTagCache().is(BlockTag.SHOVEL_EFFECTIVE, block); + case "shovel" -> session.getTagCache().is(BlockTag.MINEABLE_SHOVEL, block); case "sword" -> block == Blocks.COBWEB; default -> { session.getGeyser().getLogger().warning("Unknown tool type: " + itemToolType); @@ -145,7 +145,7 @@ public final class BlockUtils { toolCanBreak = canToolTierBreakBlock(session, block, toolTier); } - int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(components, Enchantment.JavaEnchantment.EFFICIENCY); + int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(session, components, BedrockEnchantment.EFFICIENCY); int hasteLevel = 0; int miningFatigueLevel = 0; @@ -160,7 +160,7 @@ public final class BlockUtils { boolean waterInEyes = session.getCollisionManager().isWaterInEyes(); boolean insideOfWaterWithoutAquaAffinity = waterInEyes && - ItemUtils.getEnchantmentLevel(session.getPlayerInventory().getItem(5).getComponents(), Enchantment.JavaEnchantment.AQUA_AFFINITY) < 1; + ItemUtils.getEnchantmentLevel(session, session.getPlayerInventory().getItem(5).getComponents(), BedrockEnchantment.AQUA_AFFINITY) < 1; return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround()); diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index d11c1f9e4..bfb70a4ed 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -77,6 +77,12 @@ public final class EntityUtils { case BAD_OMEN -> 28; case HERO_OF_THE_VILLAGE -> 29; case DARKNESS -> 30; + case TRIAL_OMEN -> 31; + case WIND_CHARGED -> 32; + case WEAVING -> 33; + case OOZING -> 34; + case INFESTED -> 35; + case RAID_OMEN -> 36; default -> effect.ordinal() + 1; }; } diff --git a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java index c9d9903d4..eec0d173d 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java @@ -27,17 +27,27 @@ package org.geysermc.geyser.util; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.enchantment.Enchantment; +import org.geysermc.geyser.item.enchantment.EnchantmentComponent; import org.geysermc.geyser.item.type.FishingRodItem; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; -public class ItemUtils { +import java.util.Map; - public static int getEnchantmentLevel(@Nullable DataComponents components, Enchantment.JavaEnchantment enchantment) { +public final class ItemUtils { + + /** + * Cheap hack. Proper solution is to read the enchantment effects. + */ + @Deprecated + public static int getEnchantmentLevel(GeyserSession session, @Nullable DataComponents components, BedrockEnchantment bedrockEnchantment) { if (components == null) { return 0; } @@ -47,7 +57,36 @@ public class ItemUtils { return 0; } - return enchantmentData.getEnchantments().getOrDefault(enchantment.ordinal(), 0); + for (Map.Entry entry : enchantmentData.getEnchantments().entrySet()) { + Enchantment enchantment = session.getRegistryCache().enchantments().byId(entry.getKey()); + if (enchantment.bedrockEnchantment() == bedrockEnchantment) { + return entry.getValue(); + } + } + return 0; + } + + public static boolean hasEffect(GeyserSession session, @Nullable ItemStack itemStack, EnchantmentComponent component) { + if (itemStack == null) { + return false; + } + DataComponents components = itemStack.getDataComponents(); + if (components == null) { + return false; + } + + ItemEnchantments enchantmentData = components.get(DataComponentType.ENCHANTMENTS); + if (enchantmentData == null) { + return false; + } + + for (Integer id : enchantmentData.getEnchantments().keySet()) { + Enchantment enchantment = session.getRegistryCache().enchantments().byId(id); + if (enchantment.effects().contains(component)) { + return true; + } + } + return false; } /** @@ -73,4 +112,7 @@ public class ItemUtils { } return components.get(DataComponentType.CUSTOM_NAME); } + + private ItemUtils() { + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java similarity index 74% rename from core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java rename to core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java index 970e128a4..5f1c8e084 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java +++ b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * 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 @@ -23,13 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.registry.type; +package org.geysermc.geyser.util; -import it.unimi.dsi.fastutil.ints.IntSet; -import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import net.kyori.adventure.key.Key; +import org.intellij.lang.annotations.Subst; -import java.util.Set; +public final class MinecraftKey { -public record EnchantmentData(int rarityMultiplier, int maxLevel, Set incompatibleEnchantments, - IntSet validItems) { + /** + * To prevent constant warnings from invalid regex. + */ + public static Key key(@Subst("empty") String s) { + return Key.key(s); + } } diff --git a/core/src/main/java/org/geysermc/geyser/util/Ordered.java b/core/src/main/java/org/geysermc/geyser/util/Ordered.java new file mode 100644 index 000000000..08ff5df72 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/Ordered.java @@ -0,0 +1,33 @@ +/* + * 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.util; + +/** + * Represents anything that could be tracked like a enum, without also creating a name and enum-wide array. + */ +public interface Ordered { + int ordinal(); +} diff --git a/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java b/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java index 09543baca..1e39c4ade 100644 --- a/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.util; import com.google.common.base.Charsets; +import net.kyori.adventure.key.Key; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; @@ -54,7 +55,7 @@ public class PluginMessageUtils { } public static void sendMessage(GeyserSession session, String channel, byte[] data) { - session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, data)); + session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(Key.key(channel), data)); } private static byte[] writeVarInt(int value) { diff --git a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java index 7559347e9..524d241db 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -33,7 +33,6 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -52,7 +51,7 @@ public final class SoundUtils { * @param sound the sound name * @return a sound event from the given sound */ - private static @Nullable SoundEvent toSoundEvent(String sound) { + public static @Nullable SoundEvent toSoundEvent(String sound) { try { return SoundEvent.valueOf(sound.toUpperCase(Locale.ROOT).replace(".", "_")); } catch (Exception ex) { diff --git a/core/src/main/resources/bedrock/entity_identifiers.dat b/core/src/main/resources/bedrock/entity_identifiers.dat index e7cdeb08e..95d00c246 100644 Binary files a/core/src/main/resources/bedrock/entity_identifiers.dat and b/core/src/main/resources/bedrock/entity_identifiers.dat differ diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 88e50df10..23cb22f9c 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 88e50df1008916c266428ac11f76f07dc24638c5 +Subproject commit 23cb22f9ceeb7f24b896a69a711944d7f3e756ed diff --git a/gradle.properties b/gradle.properties index ea473906a..a222b1d99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.3.2-SNAPSHOT +version=2.4.0-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8d5b4c18b..f57cf79de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,10 +12,10 @@ guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 TODO bump to 2.8.1 or similar (Spigot 1.16.5 version) after Merge gson-runtime = "2.10.1" websocket = "1.5.1" -protocol = "3.0.0.Beta2-20240606.172607-7" +protocol = "3.0.0.Beta2-20240616.144648-10" raknet = "1.0.0.CR3-20240416.144209-1" mcauthlib = "e5b0bcc" -mcprotocollib = "1.20.6-2-20240520.030045-8" +mcprotocollib = "1.21-20240616.154144-5" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" @@ -25,17 +25,17 @@ jline = "3.21.0" terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.9.2" -adapters = "1.12-SNAPSHOT" +adapters = "1.13-SNAPSHOT" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.3.0-SNAPSHOT" viaproxy = "3.2.1" -fabric-minecraft = "1.20.5" -fabric-loader = "0.15.10" -fabric-api = "0.97.6+1.20.5" +fabric-loader = "0.15.11" +fabric-api = "0.100.1+1.21" fabric-permissions = "0.2-SNAPSHOT" -neoforge-minecraft = "20.5.0-beta" +neoforge-minecraft = "21.0.0-beta" mixin = "0.8.5" +minecraft = "1.21" # plugin versions indra = "3.1.3" @@ -44,7 +44,7 @@ architectury-plugin = "3.4-SNAPSHOT" architectury-loom = "1.6-SNAPSHOT" minotaur = "2.8.7" lombok = "8.4" -blossom = "1.2.0" +blossom = "2.1.0" [libraries] base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-api" } @@ -96,8 +96,9 @@ paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", versio mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } +minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" } + # Check these on https://modmuss50.me/fabric.html -fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } fabric-permissions = { group = "me.lucko", name = "fabric-permissions-api", version.ref = "fabric-permissions" }