Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/configurate

This commit is contained in:
Camotoy 2024-06-24 16:31:43 -04:00
commit 528de86340
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
157 changed files with 3138 additions and 1837 deletions

14
.editorconfig Normal file
View file

@ -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

View file

@ -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' }}

View file

@ -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.

View file

@ -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();

View file

@ -34,3 +34,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-resolver-dns:.*"))
}
}
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.add("bungeecord")
}

View file

@ -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(" / \\");

View file

@ -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)

View file

@ -63,6 +63,7 @@ tasks {
modrinth {
loaders.add("fabric")
uploadFile.set(tasks.getByPath("remapModrinthJar"))
dependencies {
required.project("fabric-api")
}

View file

@ -23,8 +23,8 @@
"geyser.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.10",
"fabricloader": ">=0.15.11",
"fabric": "*",
"minecraft": ">=1.20.5"
"minecraft": ">=1.21"
}
}

View file

@ -55,4 +55,5 @@ tasks {
modrinth {
loaders.add("neoforge")
uploadFile.set(tasks.getByPath("remapModrinthJar"))
}

View file

@ -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"

View file

@ -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<List<String>> 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<String> 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());
})

View file

@ -78,3 +78,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("com.mojang:.*"))
}
}
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.addAll("spigot", "paper")
}

View file

@ -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);

View file

@ -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<List<String>> 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.

View file

@ -69,4 +69,9 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*"))
exclude(dependency("net.kyori:adventure-nbt:.*"))
}
}
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.addAll("velocity")
}

View file

@ -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)

View file

@ -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()

View file

@ -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)
}

View file

@ -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())
}

View file

@ -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")
}
}

View file

@ -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<BlossomExtension> {
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)"
}
}

View file

@ -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);
}
}

View file

@ -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";

View file

@ -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

View file

@ -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("<buildNumber>")) {
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()));
}
}

View file

@ -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 <T> the entity type this definition represents
*/
public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, EntityType entityType, String identifier,

View file

@ -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<BeeEntity> BEE;
public static final EntityDefinition<BlazeEntity> BLAZE;
public static final EntityDefinition<BoatEntity> BOAT;
public static final EntityDefinition<BoggedEntity> BOGGED;
public static final EntityDefinition<BreezeEntity> BREEZE;
public static final EntityDefinition<AbstractWindChargeEntity> BREEZE_WIND_CHARGE;
public static final EntityDefinition<CamelEntity> CAMEL;
public static final EntityDefinition<CatEntity> CAT;
public static final EntityDefinition<SpiderEntity> CAVE_SPIDER;
@ -165,6 +170,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<VindicatorEntity> VINDICATOR;
public static final EntityDefinition<AbstractMerchantEntity> WANDERING_TRADER;
public static final EntityDefinition<WardenEntity> WARDEN;
public static final EntityDefinition<AbstractWindChargeEntity> WIND_CHARGE;
public static final EntityDefinition<RaidParticipantEntity> WITCH;
public static final EntityDefinition<WitherEntity> WITHER;
public static final EntityDefinition<AbstractSkeletonEntity> WITHER_SKELETON;
@ -375,6 +381,18 @@ public final class EntityDefinitions {
.heightAndWidth(0.25f)
.build();
EntityFactory<AbstractWindChargeEntity> 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<AbstractArrowEntity> 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)

View file

@ -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;
}
}

View file

@ -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));
}
}
}

View file

@ -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. <br>
@ -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());

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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<org.geysermc.mcprotocollib.protocol.data.game.entity.type.PaintingType> entityMetadata) {
PaintingType type = PaintingType.getByPaintingType(entityMetadata.getValue());
public void setPaintingType(ObjectEntityMetadata<Holder<PaintingVariant>> 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) {

View file

@ -38,7 +38,7 @@ public class AmbientEntity extends MobEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return false;
}
}

View file

@ -43,7 +43,7 @@ public class DolphinEntity extends WaterEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return true;
}

View file

@ -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;
}
/**

View file

@ -122,7 +122,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return isNotLeashed();
}

View file

@ -38,7 +38,7 @@ public class WaterEntity extends CreatureEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return false;
}
}

View file

@ -72,7 +72,7 @@ public class AxolotlEntity extends AnimalEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return true;
}

View file

@ -63,7 +63,7 @@ public class HoglinEntity extends AnimalEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return isNotLeashed();
}

View file

@ -123,7 +123,7 @@ public class PandaEntity extends AnimalEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return false;
}

View file

@ -56,7 +56,7 @@ public class TurtleEntity extends AnimalEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return false;
}
}

View file

@ -84,7 +84,7 @@ public abstract class TameableEntity extends AnimalEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return isNotLeashed();
}
}

View file

@ -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<Item> 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<Holder<WolfVariant>> 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;
}

View file

@ -47,7 +47,7 @@ public class AbstractMerchantEntity extends AgeableEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return false;
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -58,7 +58,7 @@ public class ZoglinEntity extends MonsterEntity {
}
@Override
protected boolean canBeLeashed() {
public boolean canBeLeashed() {
return isNotLeashed();
}

View file

@ -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
* <p>
* 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);
}
}

View file

@ -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);

View file

@ -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;
/**

View file

@ -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;
}
}

View file

@ -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<String> 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<String> 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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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.
* <a href="https://minecraft.wiki/w/Arrow#Data_values">See here</a>
*/
@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;
}
}
}

View file

@ -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();
}

View file

@ -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
}

View file

@ -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<JavaEnchantment> combinedEnchantments = getEnchantments(input);
Object2IntMap<Enchantment> combinedEnchantments = getEnchantments(session, input);
int cost = 0;
for (Object2IntMap.Entry<JavaEnchantment> 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<Enchantment> 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<JavaEnchantment> getEnchantments(GeyserItemStack itemStack) {
private Object2IntMap<Enchantment> 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<JavaEnchantment> enchantments = new Object2IntOpenHashMap<>();
Object2IntMap<Enchantment> enchantments = new Object2IntOpenHashMap<>();
for (Map.Entry<Integer, Integer> 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;

View file

@ -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();

View file

@ -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<EnchantmentComponent> 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<EnchantmentComponent> 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<EnchantmentComponent> readEnchantmentComponents(NbtMap effects) {
Set<EnchantmentComponent> components = new HashSet<>();
for (Map.Entry<String, Object> 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
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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<Pair<BannerPattern, DyeColor>> 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<NbtMap> 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<BannerPatternLayer> 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)))
);
}

View file

@ -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);
}
}

View file

@ -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<NbtMap> 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<Enchantment> 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);
}

View file

@ -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
}
}
}

View file

@ -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()) {

View file

@ -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;
}
}

View file

@ -62,4 +62,9 @@ public class GoatHornItem extends Item {
return itemStack;
}
@Override
public boolean ignoreDamage() {
return true;
}
}

View file

@ -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<Component> loreComponents = components.get(DataComponentType.LORE);
if (loreComponents != null) {
if (loreComponents != null && components.get(DataComponentType.HIDE_TOOLTIP) == null) {
List<String> lore = builder.getOrCreateLore();
for (Component loreComponent : loreComponents) {
lore.add(MessageTranslator.convertMessage(loreComponent, session.locale()));
@ -170,7 +172,7 @@ public class Item {
* </ul>
* 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<NbtMap> enchantmentTag = bedrockTag.getList("ench", NbtType.COMPOUND);
// if (enchantmentTag != null) {
// List<Tag> 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<String, Tag> 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.JavaEnchantment, String> 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 */

View file

@ -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;
}
}

View file

@ -76,4 +76,9 @@ public class PotionItem extends Item {
}
return itemStack;
}
@Override
public boolean ignoreDamage() {
return true;
}
}

View file

@ -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());

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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<List<String>> apply) {
}
protected static final Function<Int2ObjectMap<byte[]>, DataComponents> RAW_TRANSFORMER = map -> {
try {
Map<DataComponentType<?>, DataComponent<?, ?>> components = new HashMap<>();

View file

@ -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)));

View file

@ -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;
}

View file

@ -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<Property<?>, List<Comparable<?>>> 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<Item> 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;
}

View file

@ -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<Boolean> property, boolean def) {
public <T extends Comparable<T>> T getValue(Property<T> 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));
}
}

View file

@ -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<String> getJavaVersions() {
return List.of("1.20.5", DEFAULT_JAVA_CODEC.getMinecraftVersion());
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion());
}
/**

View file

@ -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 <V> the value
*/
public class IntMappedRegistry<V> extends AbstractMappedRegistry<Integer, V, Int2ObjectMap<V>> {
protected <I> IntMappedRegistry(I input, RegistryLoader<I, Int2ObjectMap<V>> 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 <I> the input
* @param <V> the map value
* @return a new registry with the given RegistryLoader
*/
public static <I, V> IntMappedRegistry<V> create(RegistryLoader<I, Int2ObjectMap<V>> 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 <I> the input
* @param <V> the map value
* @return a new registry with the given RegistryLoader supplier
*/
public static <I, V> IntMappedRegistry<V> create(I input, Supplier<RegistryLoader<I, Int2ObjectMap<V>>> registryLoader) {
return new IntMappedRegistry<>(input, registryLoader.get());
}
}

View file

@ -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<BlockEntityType, BlockEntityTranslator> 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<Map<RecipeType, List<RecipeData>>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
/**
* A registry holding data of all the known enchantments.
*/
public static final SimpleMappedRegistry<JavaEnchantment, EnchantmentData> 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<Int2ObjectMap<GeyserRecipe>> 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<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
public static final SimpleMappedRegistry<RecipeType, List<GeyserRecipe>> 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();

View file

@ -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<String, Map<JavaEnchantment, EnchantmentData>> {
@Override
public Map<JavaEnchantment, EnchantmentData> 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<JavaEnchantment, EnchantmentData> enchantments = new EnumMap<>(JavaEnchantment.class);
for (Map.Entry<String, JsonElement> 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<JavaEnchantment> 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;
}
}

View file

@ -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<Object, Int2Objec
ingredients.add(getNonNull(mappings, Items.GHAST_TEAR));
ingredients.add(getNonNull(mappings, Items.TURTLE_HELMET));
ingredients.add(getNonNull(mappings, Items.PHANTOM_MEMBRANE));
// 1.21
ingredients.add(getNonNull(mappings, Items.STONE));
ingredients.add(getNonNull(mappings, Items.SLIME_BLOCK));
ingredients.add(getNonNull(mappings, Items.COBWEB));
ingredients.add(getNonNull(mappings, Items.BREEZE_ROD));
List<ItemMapping> inputs = List.of(
getNonNull(mappings, Items.POTION),

View file

@ -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<String, Map<RecipeType, List<GeyserRecipe>>> {
@Override
public Map<RecipeType, List<GeyserRecipe>> load(String input) {
Map<RecipeType, List<GeyserRecipe>> deserializedRecipes = new Object2ObjectOpenHashMap<>();
List<NbtMap> 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<RecipeType, List<GeyserRecipe>> getRecipes(NbtMap recipes, MinecraftCodecHelper helper) {
List<NbtMap> 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<GeyserRecipe> getShapelessRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());
for (NbtMap recipe : recipes) {
ItemStack output = toItemStack(recipe.getCompound("output"), helper);
List<NbtMap> 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<GeyserRecipe> getShapedRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());
for (NbtMap recipe : recipes) {
ItemStack output = toItemStack(recipe.getCompound("output"), helper);
List<int[]> shape = recipe.getList("shape", NbtType.INT_ARRAY);
// In the recipes mapping, each recipe is mapped by a number
List<ItemStack> 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);
}
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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<String> OMINOUS_BLOCKS = List.of("minecraft:trial_spawner", "minecraft:vault");
private static final List<String> NEW_BLOCKS = Stream.of(NEW_CORAL_BLOCKS, NEW_DOUBLE_PLANTS, NEW_STONE_BLOCK_SLABS, NEW_TALLGRASSES).flatMap(List::stream).toList();
private static final List<String> MODIFIED_BLOCKS = Stream.of(NEW_BLOCKS, OMINOUS_BLOCKS).flatMap(List::stream).toList();
private static final List<Item> 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;
}

View file

@ -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);

View file

@ -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<ItemMappings> 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<RecipeType, List<RecipeData>> craftingData = new EnumMap<>(RecipeType.class);
Int2ObjectMap<GeyserRecipe> 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<GeyserRecipe> 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<String> 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<String, ItemData> letterToRecipe = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = node.get("inputs").fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
JsonNode inputNode = entry.getValue();
ItemMapping inputEntry = mappings.getMapping(inputNode.get("identifier").asText());
letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode));
}
List<ItemData> 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<Ingredient> 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<ItemData> 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<Ingredient> 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();
}
}

View file

@ -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<ItemDefinition> {
}
} 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;
}
}

View file

@ -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<String, List<String>> javaToBedrockRecipeIds;
private final Map<String, List<String>> javaToBedrockRecipeIds;
@Setter
private Int2ObjectMap<GeyserRecipe> 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;

View file

@ -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);
}
}

View file

@ -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<String, Map<String, NbtMap>> DEFAULTS;
private static final Map<String, BiConsumer<RegistryCache, List<RegistryEntry>>> REGISTRIES = new HashMap<>();
private static final Map<Key, Map<Key, NbtMap>> DEFAULTS;
private static final Map<Key, BiConsumer<RegistryCache, List<RegistryEntry>>> 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<String, Map<String, NbtMap>> defaults = new HashMap<>();
Map<Key, Map<Key, NbtMap>> 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<NbtMap> rawValues = tag.getCompound(key)
List<NbtMap> rawValues = tag.getCompound(key.asString())
.getList("value", NbtType.COMPOUND);
Map<String, NbtMap> values = new HashMap<>();
Map<Key, NbtMap> 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<TextDecoration> chatTypes = new SimpleJavaRegistry<>();
private final JavaRegistry<ChatType> chatTypes = new SimpleJavaRegistry<>();
/**
* All dimensions that the client could possibly connect to.
*/
private final JavaRegistry<JavaDimension> dimensions = new SimpleJavaRegistry<>();
private final JavaRegistry<Enchantment> enchantments = new SimpleJavaRegistry<>();
private final JavaRegistry<JukeboxSong> jukeboxSongs = new SimpleJavaRegistry<>();
private final JavaRegistry<PaintingType> paintings = new SimpleJavaRegistry<>();
private final JavaRegistry<TrimMaterial> trimMaterials = new SimpleJavaRegistry<>();
private final JavaRegistry<TrimPattern> trimPatterns = new SimpleJavaRegistry<>();
private final JavaRegistry<BannerPattern> bannerPatterns = new SimpleJavaRegistry<>();
private final JavaRegistry<WolfEntity.WolfVariant> wolfVariants = new SimpleJavaRegistry<>();
private final JavaRegistry<WolfEntity.BuiltInWolfVariant> wolfVariants = new SimpleJavaRegistry<>();
public RegistryCache(GeyserSession session) {
this.session = session;
@ -138,9 +150,9 @@ public final class RegistryCache {
* @param <T> the class that represents these entries.
*/
private static <T> void register(String registry, Function<RegistryCache, JavaRegistry<T>> localCacheFunction, BiFunction<GeyserSession, RegistryEntry, T> reader) {
String key = "minecraft:" + registry;
Key key = MinecraftKey.key(registry);
REGISTRIES.put(key, (registryCache, entries) -> {
Map<String, NbtMap> localRegistry = null;
Map<Key, NbtMap> localRegistry = null;
JavaRegistry<T> 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<RegistryCache, int[]> localCacheFunction, ToIntFunction<RegistryEntry> 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++) {

Some files were not shown because too many files have changed in this diff Show more