diff --git a/.gitignore b/.gitignore index a44afd242..aff61aa60 100644 --- a/.gitignore +++ b/.gitignore @@ -249,6 +249,8 @@ locales/ /packs/ /dump.json /saved-refresh-tokens.json +/saved-auth-chains.json /custom_mappings/ /languages/ -/custom-skulls.yml \ No newline at end of file +/custom-skulls.yml +/permissions.yml diff --git a/README.md b/README.md index 14bdb17a9..dec1e9dd1 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,15 @@ 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.2 and Minecraft Java 1.21 +## Supported Versions +Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.30 and Minecraft Java 1.21/1.21.1. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/). ## Setting Up -Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. - -[![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4) +Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser. ## Links: - Website: https://geysermc.org -- Docs: https://wiki.geysermc.org/geyser/ +- Docs: https://geysermc.org/wiki/geyser/ - Download: https://geysermc.org/download - Discord: https://discord.gg/geysermc - Donate: https://opencollective.com/geysermc @@ -34,7 +33,7 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge - Some Entity Flags ## What can't be fixed -There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page. +There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://geysermc.org/wiki/geyser/current-limitations/) page. ## Compiling 1. Clone the repo to your computer @@ -42,12 +41,12 @@ There are a few things Geyser is unable to support due to various differences be 3. Run `gradlew build` and locate to `bootstrap/build` folder. ## Contributing -Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if +Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if you're interested in helping out with Geyser. ## Libraries Used: - [Adventure Text Library](https://github.com/KyoriPowered/adventure) -- [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol) -- [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib) +- [CloudburstMC Bedrock Protocol Library](https://github.com/CloudburstMC/Protocol) +- [GeyserMC's Java Protocol Library](https://github.com/GeyserMC/MCProtocolLib) - [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender) - [Simple Logging Facade for Java (slf4j)](https://github.com/qos-ch/slf4j) diff --git a/ap/build.gradle.kts b/ap/build.gradle.kts index e69de29bb..6c456c21b 100644 --- a/ap/build.gradle.kts +++ b/ap/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("geyser.base-conventions") +} diff --git a/api/build.gradle.kts b/api/build.gradle.kts index bd54a9ce4..eac02ebeb 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,8 +1,24 @@ plugins { + // Allow blossom to mark sources root of templates + idea id("geyser.publish-conventions") + alias(libs.plugins.blossom) } dependencies { api(libs.base.api) api(libs.math) -} \ No newline at end of file +} + +version = property("version")!! +val apiVersion = (version as String).removeSuffix("-SNAPSHOT") + +sourceSets { + main { + blossom { + javaSources { + property("version", apiVersion) + } + } + } +} diff --git a/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java b/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java new file mode 100644 index 000000000..f9a580e7b --- /dev/null +++ b/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api; + +import org.geysermc.api.util.ApiVersion; + +/** + * Not a public API. For internal use only. May change without notice. + * This class is processed before compilation to insert build properties. + */ +class BuildData { + static final String VERSION = "{{ version }}"; + static final ApiVersion API_VERSION; + + static { + String[] parts = VERSION.split("\\."); + if (parts.length != 3) { + throw new RuntimeException("Invalid api version: " + VERSION); + } + + try { + int human = Integer.parseInt(parts[0]); + int major = Integer.parseInt(parts[1]); + int minor = Integer.parseInt(parts[2]); + API_VERSION = new ApiVersion(human, major, minor); + } catch (Exception e) { + throw new RuntimeException("Invalid api version: " + VERSION, e); + } + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java index a9327d0db..5c20d06e1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; import org.geysermc.api.GeyserApiBase; +import org.geysermc.api.util.ApiVersion; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; @@ -169,4 +170,14 @@ public interface GeyserApi extends GeyserApiBase { static GeyserApi api() { return Geyser.api(GeyserApi.class); } + + /** + * Returns the {@link ApiVersion} representing the current Geyser api version. + * See the Geyser version outline) + * + * @return the current geyser api version + */ + default ApiVersion geyserApiVersion() { + return BuildData.API_VERSION; + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 2f1f2b24d..29922ae1e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -28,7 +28,9 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import java.util.Collections; import java.util.List; @@ -58,15 +60,15 @@ public interface Command { * Gets the permission node associated with * this command. * - * @return the permission node for this command + * @return the permission node for this command if defined, otherwise an empty string */ @NonNull String permission(); /** - * Gets the aliases for this command. + * Gets the aliases for this command, as an unmodifiable list * - * @return the aliases for this command + * @return the aliases for this command as an unmodifiable list */ @NonNull List aliases(); @@ -75,35 +77,39 @@ public interface Command { * Gets if this command is designed to be used only by server operators. * * @return if this command is designated to be used only by server operators. + * @deprecated this method is not guaranteed to provide meaningful or expected results. */ - boolean isSuggestedOpOnly(); - - /** - * Gets if this command is executable on console. - * - * @return if this command is executable on console - */ - boolean isExecutableOnConsole(); - - /** - * Gets the subcommands associated with this - * command. Mainly used within the Geyser Standalone - * GUI to know what subcommands are supported. - * - * @return the subcommands associated with this command - */ - @NonNull - default List subCommands() { - return Collections.emptyList(); + @Deprecated(forRemoval = true) + default boolean isSuggestedOpOnly() { + return false; } /** - * Used to send a deny message to Java players if this command can only be used by Bedrock players. - * - * @return true if this command can only be used by Bedrock players. + * @return true if this command is executable on console + * @deprecated use {@link #isPlayerOnly()} instead (inverted) */ - default boolean isBedrockOnly() { - return false; + @Deprecated(forRemoval = true) + default boolean isExecutableOnConsole() { + return !isPlayerOnly(); + } + + /** + * @return true if this command can only be used by players + */ + boolean isPlayerOnly(); + + /** + * @return true if this command can only be used by Bedrock players + */ + boolean isBedrockOnly(); + + /** + * @deprecated this method will always return an empty immutable list + */ + @Deprecated(forRemoval = true) + @NonNull + default List subCommands() { + return Collections.emptyList(); } /** @@ -128,7 +134,7 @@ public interface Command { * is an instance of this source. * * @param sourceType the source type - * @return the builder + * @return this builder */ Builder source(@NonNull Class sourceType); @@ -136,7 +142,7 @@ public interface Command { * Sets the command name. * * @param name the command name - * @return the builder + * @return this builder */ Builder name(@NonNull String name); @@ -144,23 +150,40 @@ public interface Command { * Sets the command description. * * @param description the command description - * @return the builder + * @return this builder */ Builder description(@NonNull String description); /** - * Sets the permission node. + * Sets the permission node required to run this command.
+ * It will not be registered with any permission registries, such as an underlying server, + * or a permissions Extension (unlike {@link #permission(String, TriState)}). * * @param permission the permission node - * @return the builder + * @return this builder */ Builder permission(@NonNull String permission); + /** + * Sets the permission node and its default value. The usage of the default value is platform dependant + * and may or may not be used. For example, it may be registered to an underlying server. + *

+ * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions, + * especially if the same permission is required by multiple commands. Also see this event for TriState meanings. + * + * @param permission the permission node + * @param defaultValue the node's default value + * @return this builder + * @deprecated this method is experimental and may be removed in the future + */ + @Deprecated + Builder permission(@NonNull String permission, @NonNull TriState defaultValue); + /** * Sets the aliases. * * @param aliases the aliases - * @return the builder + * @return this builder */ Builder aliases(@NonNull List aliases); @@ -168,46 +191,62 @@ public interface Command { * Sets if this command is designed to be used only by server operators. * * @param suggestedOpOnly if this command is designed to be used only by server operators - * @return the builder + * @return this builder + * @deprecated this method is not guaranteed to produce meaningful or expected results */ + @Deprecated(forRemoval = true) Builder suggestedOpOnly(boolean suggestedOpOnly); /** * Sets if this command is executable on console. * * @param executableOnConsole if this command is executable on console - * @return the builder + * @return this builder + * @deprecated use {@link #isPlayerOnly()} instead (inverted) */ + @Deprecated(forRemoval = true) Builder executableOnConsole(boolean executableOnConsole); + /** + * Sets if this command can only be executed by players. + * + * @param playerOnly if this command is player only + * @return this builder + */ + Builder playerOnly(boolean playerOnly); + + /** + * Sets if this command can only be executed by bedrock players. + * + * @param bedrockOnly if this command is bedrock only + * @return this builder + */ + Builder bedrockOnly(boolean bedrockOnly); + /** * Sets the subcommands. * * @param subCommands the subcommands - * @return the builder + * @return this builder + * @deprecated this method has no effect */ - Builder subCommands(@NonNull List subCommands); - - /** - * Sets if this command is bedrock only. - * - * @param bedrockOnly if this command is bedrock only - * @return the builder - */ - Builder bedrockOnly(boolean bedrockOnly); + @Deprecated(forRemoval = true) + default Builder subCommands(@NonNull List subCommands) { + return this; + } /** * Sets the {@link CommandExecutor} for this command. * * @param executor the command executor - * @return the builder + * @return this builder */ Builder executor(@NonNull CommandExecutor executor); /** * Builds the command. * - * @return the command + * @return a new command from this builder */ @NonNull Command build(); diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index 45276e2c4..c1453f579 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -26,6 +26,10 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.connection.GeyserConnection; + +import java.util.UUID; /** * Represents an instance capable of sending commands. @@ -64,6 +68,17 @@ public interface CommandSource { */ boolean isConsole(); + /** + * @return a Java UUID if this source represents a player, otherwise null + */ + @Nullable UUID playerUuid(); + + /** + * @return a GeyserConnection if this source represents a Bedrock player that is connected + * to this Geyser instance, otherwise null + */ + @Nullable GeyserConnection connection(); + /** * Returns the locale of the command source. * diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index 9bda4f903..0a580f975 100644 --- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -60,6 +60,16 @@ public interface GeyserConnection extends Connection, CommandSource { */ @NonNull EntityData entities(); + /** + * Returns the current ping of the connection. + */ + int ping(); + + /** + * Closes the currently open form on the client. + */ + void closeForm(); + /** * @param javaId the Java entity ID to look up. * @return a {@link GeyserEntity} if present in this connection's entity tracker. diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java index 90b3fc821..48c717089 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java @@ -81,4 +81,10 @@ public interface EntityData { * @return whether the movement is locked */ boolean isMovementLocked(); + + /** + * Sends a request to the Java server to switch the items in the main and offhand. + * There is no guarantee of the server accepting the request. + */ + void switchHands(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index 994373752..d136202bd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event { /** * Gets all the registered built-in {@link Command}s. * - * @return all the registered built-in commands + * @return all the registered built-in commands as an unmodifiable map */ @NonNull Map commands(); diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java new file mode 100644 index 000000000..43ebc2c50 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2023 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.api.event.lifecycle; + +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.geyser.api.permission.PermissionChecker; + +/** + * Fired by any permission manager implementations that wish to add support for custom permission checking. + * This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone and ViaProxy. + *

+ * Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker} + * will result in that checker having a higher priority than others. + */ +public interface GeyserRegisterPermissionCheckersEvent extends Event { + + void register(PermissionChecker checker); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java new file mode 100644 index 000000000..4f06c4e5f --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2023 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.api.event.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.util.TriState; + +/** + * Fired by anything that wishes to gather permission nodes and defaults. + *

+ * This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. + * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge, Geyser-Standalone, and Geyser-ViaProxy + * It may be fired by a 3rd party regardless of the platform. + */ +public interface GeyserRegisterPermissionsEvent extends Event { + + /** + * Registers a permission node and its default value with the firer.

+ * {@link TriState#TRUE} corresponds to all players having the permission by default.
+ * {@link TriState#NOT_SET} corresponds to only server operators having the permission by default (if such a concept exists on the platform).
+ * {@link TriState#FALSE} corresponds to no players having the permission by default.
+ * + * @param permission the permission node to register + * @param defaultValue the default value of the node + */ + void register(@NonNull String permission, @NonNull TriState defaultValue); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 993bdee44..1eacfea9a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -107,6 +107,15 @@ public interface Extension extends EventRegistrar { return this.extensionLoader().description(this); } + /** + * @return the root command that all of this extension's commands will stem from. + * By default, this is the extension's id. + */ + @NonNull + default String rootCommand() { + return this.description().id(); + } + /** * Gets the extension's logger * diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index 2df3ee815..25daf450f 100644 --- a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -59,33 +59,46 @@ public interface ExtensionDescription { String main(); /** - * Gets the extension's major api version + * Represents the human api version that the extension requires. + * See the Geyser version outline) + * for more details on the Geyser API version. * - * @return the extension's major api version + * @return the extension's requested human api version + */ + int humanApiVersion(); + + /** + * Represents the major api version that the extension requires. + * See the Geyser version outline) + * for more details on the Geyser API version. + * + * @return the extension's requested major api version */ int majorApiVersion(); /** - * Gets the extension's minor api version + * Represents the minor api version that the extension requires. + * See the Geyser version outline) + * for more details on the Geyser API version. * - * @return the extension's minor api version + * @return the extension's requested minor api version */ int minorApiVersion(); /** - * Gets the extension's patch api version - * - * @return the extension's patch api version + * No longer in use. Geyser is now using an adaption of the romantic versioning scheme. + * See here for details. */ - int patchApiVersion(); + @Deprecated(forRemoval = true) + default int patchApiVersion() { + return minorApiVersion(); + } /** - * Gets the extension's api version. - * - * @return the extension's api version + * Returns the extension's requested Geyser Api version. */ default String apiVersion() { - return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); + return humanApiVersion() + "." + majorApiVersion() + "." + minorApiVersion(); } /** diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java new file mode 100644 index 000000000..c0d4af2f4 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2023 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.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.util.TriState; + +/** + * Something capable of checking if a {@link CommandSource} has a permission + */ +@FunctionalInterface +public interface PermissionChecker { + + /** + * Checks if the given source has a permission + * + * @param source the {@link CommandSource} whose permissions should be queried + * @param permission the permission node to check + * @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission + * node itself was not found, and the source does not have such permission. + * {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values. + */ + @NonNull + TriState hasPermission(@NonNull CommandSource source, @NonNull String permission); +} diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 910e50723..1564b7f75 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -1,5 +1,12 @@ +plugins { + id("geyser.platform-conventions") + id("geyser.modrinth-uploading-conventions") +} + dependencies { api(projects.core) + + implementation(libs.cloud.bungee) implementation(libs.adventure.text.serializer.bungeecord) compileOnlyApi(libs.bungeecord.proxy) } @@ -8,13 +15,15 @@ platformRelocate("net.md_5.bungee.jni") platformRelocate("com.fasterxml.jackson") platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound platformRelocate("net.kyori") +platformRelocate("org.incendo") +platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform provided(libs.bungeecord.proxy) -application { - mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain" } tasks.withType { diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java index 3c3853ed8..1193a52b3 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -36,6 +36,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; @@ -43,6 +44,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; @AllArgsConstructor public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener { @@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List future.complete(event); } })); - ProxyPingEvent event = future.join(); + + ProxyPingEvent event; + + try { + event = future.get(100, TimeUnit.MILLISECONDS); + } catch (Throwable cause) { + String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : ""; + GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause); + return null; + } + ServerPing response = event.getResponse(); return new GeyserPingInfo( response.getDescriptionComponent().toLegacyText(), diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index cd6b59f64..7adfd488f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.bungeecord; import io.netty.channel.Channel; import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.protocol.ProtocolConstants; @@ -34,17 +35,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; +import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bungee.BungeeCommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; import java.io.File; import java.io.IOException; @@ -54,19 +58,17 @@ import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; -import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeInjector geyserInjector; private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger()); private IGeyserPingPassthrough geyserBungeePingPassthrough; - private GeyserImpl geyser; @Override @@ -99,10 +101,31 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); this.geyserInjector = new GeyserBungeeInjector(this); + + // Registration of listeners occurs only once + this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener()); } @Override public void onEnable() { + if (geyser == null) { + return; // Config did not load properly! + } + + // After Geyser initialize for parity with other platforms. + var sourceConverter = new CommandSourceConverter<>( + CommandSender.class, + id -> getProxy().getPlayer(id), + () -> getProxy().getConsole(), + BungeeCommandSource::new + ); + CommandManager cloud = new BungeeCommandManager<>( + this, + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults + // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating // task that waits for a field to be filled which is set after the plugin enable // process is complete @@ -142,11 +165,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } this.geyserLogger.setDebug(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - } else { - // For consistency with other platforms - create command manager before GeyserImpl#start() - // This ensures the command events are called before the item/block ones are - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); } // Force-disable query if enabled, or else Geyser won't enable @@ -181,16 +199,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } this.geyserInjector.initializeLocalChannel(this); - - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands)); - } } @Override @@ -226,8 +234,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java index c68839b20..0a89b5421 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java @@ -29,8 +29,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; @@ -40,7 +40,7 @@ public final class GeyserBungeeUpdateListener implements Listener { public void onPlayerJoin(final PostLoginEvent event) { if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { final ProxiedPlayer player = event.getPlayer(); - if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + if (player.hasPermission(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player)); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index e3099f170..10ccc5bac 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -27,19 +27,22 @@ package org.geysermc.geyser.platform.bungeecord.command; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; +import java.util.UUID; public class BungeeCommandSource implements GeyserCommandSource { - private final net.md_5.bungee.api.CommandSender handle; + private final CommandSender handle; - public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) { + public BungeeCommandSource(CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded GeyserLocale.loadGeyserLocale(this.locale()); @@ -72,12 +75,20 @@ public class BungeeCommandSource implements GeyserCommandSource { return !(handle instanceof ProxiedPlayer); } + @Override + public @Nullable UUID playerUuid() { + if (handle instanceof ProxiedPlayer player) { + return player.getUniqueId(); + } + return null; + } + @Override public String locale() { if (handle instanceof ProxiedPlayer player) { Locale locale = player.getLocale(); if (locale != null) { - // Locale can be null early on in the conneciton + // Locale can be null early on in the connection return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); } } @@ -86,6 +97,12 @@ public class BungeeCommandSource implements GeyserCommandSource { @Override public boolean hasPermission(String permission) { - return handle.hasPermission(permission); + // Handle blank permissions ourselves, as bungeecord only handles empty ones + return permission.isBlank() || handle.hasPermission(permission); + } + + @Override + public Object handle() { + return handle; } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java deleted file mode 100644 index 2d02c9950..000000000 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ /dev/null @@ -1,89 +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.platform.bungeecord.command; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.plugin.Command; -import net.md_5.bungee.api.plugin.TabExecutor; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private final GeyserCommandExecutor commandExecutor; - - public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) { - super(name); - - this.commandExecutor = new GeyserCommandExecutor(geyser, commands); - } - - @Override - public void execute(CommandSender sender, String[] args) { - BungeeCommandSource commandSender = new BungeeCommandSource(sender); - GeyserSession session = this.commandExecutor.getGeyserSession(commandSender); - - if (args.length > 0) { - GeyserCommand command = this.commandExecutor.getCommand(args[0]); - if (command != null) { - if (!sender.hasPermission(command.permission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return; - } - if (command.isBedrockOnly() && session == null) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return; - } - command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); - commandSender.sendMessage(ChatColor.RED + message); - } - } else { - this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]); - } - } - - @Override - public Iterable onTabComplete(CommandSender sender, String[] args) { - if (args.length == 1) { - return commandExecutor.tabComplete(new BungeeCommandSource(sender)); - } else { - return Collections.emptyList(); - } - } -} diff --git a/bootstrap/mod/build.gradle.kts b/bootstrap/mod/build.gradle.kts index 32224d00b..c43f123ec 100644 --- a/bootstrap/mod/build.gradle.kts +++ b/bootstrap/mod/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("geyser.modded-conventions") +} + architectury { common("neoforge", "fabric") } @@ -16,7 +20,8 @@ afterEvaluate { dependencies { api(projects.core) compileOnly(libs.mixin) + compileOnly(libs.mixinextras) // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE! compileOnly(libs.fabric.loader) -} \ No newline at end of file +} diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index 25bd0af9d..56bec322e 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -1,5 +1,6 @@ plugins { - application + id("geyser.modded-conventions") + id("geyser.modrinth-uploading-conventions") } architectury { @@ -25,10 +26,7 @@ dependencies { shadow(libs.protocol.connection) { isTransitive = false } shadow(libs.protocol.common) { isTransitive = false } shadow(libs.protocol.codec) { isTransitive = false } - shadow(libs.mcauthlib) { isTransitive = false } shadow(libs.raknet) { isTransitive = false } - - // Consequences of shading + relocating mcauthlib: shadow/relocate mcpl! shadow(libs.mcprotocollib) { isTransitive = false } // Since we also relocate cloudburst protocol: shade erosion common @@ -38,13 +36,12 @@ dependencies { shadow(projects.api) { isTransitive = false } shadow(projects.common) { isTransitive = false } - // Permissions - modImplementation(libs.fabric.permissions) - include(libs.fabric.permissions) + modImplementation(libs.cloud.fabric) + include(libs.cloud.fabric) } -application { - mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain" } relocate("org.cloudburstmc.netty") @@ -67,4 +64,4 @@ modrinth { dependencies { required.project("fabric-api") } -} \ No newline at end of file +} diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java index c363ade8f..149246d59 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.platform.fabric; -import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @@ -34,9 +33,16 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; +import org.geysermc.geyser.platform.mod.command.ModCommandSource; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.fabric.FabricServerCommandManager; public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer { @@ -70,20 +76,23 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer())); this.onGeyserInitialize(); + + var sourceConverter = CommandSourceConverter.layered( + CommandSourceStack.class, + id -> getServer().getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + () -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet + ModCommandSource::new + ); + CommandManager cloud = new FabricServerCommandManager<>( + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults } @Override public boolean isServer() { return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER); } - - @Override - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { - return Permissions.check(source, permissionNode); - } - - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - return Permissions.check(source, permissionNode, permissionLevel); - } } diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index e0e7c2dfa..4ab005b4f 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -1,16 +1,18 @@ plugins { - application + id("geyser.modded-conventions") + id("geyser.modrinth-uploading-conventions") } -// This is provided by "org.cloudburstmc.math.mutable" too, so yeet. -// NeoForge's class loader is *really* annoying. -provided("org.cloudburstmc.math", "api") - architectury { platformSetupLoomIde() neoForge() } +// This is provided by "org.cloudburstmc.math.mutable" too, so yeet. +// NeoForge's class loader is *really* annoying. +provided("org.cloudburstmc.math", "api") +provided("com.google.errorprone", "error_prone_annotations") + val includeTransitive: Configuration = configurations.getByName("includeTransitive") dependencies { @@ -37,10 +39,13 @@ dependencies { // Include all transitive deps of core via JiJ includeTransitive(projects.core) + + modImplementation(libs.cloud.neoforge) + include(libs.cloud.neoforge) } -application { - mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain" } tasks { @@ -56,4 +61,4 @@ tasks { modrinth { loaders.add("neoforge") uploadFile.set(tasks.getByPath("remapModrinthJar")) -} \ No newline at end of file +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java index b97e42389..ad56eda39 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.neoforge; import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.EventPriority; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLLoader; @@ -35,15 +36,22 @@ import net.neoforged.neoforge.event.GameShuttingDownEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; -import org.checkerframework.checker.nullness.qual.NonNull; +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; +import org.geysermc.geyser.platform.mod.command.ModCommandSource; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.neoforge.NeoForgeServerCommandManager; + +import java.util.Objects; @Mod(ModConstants.MOD_ID) public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { - private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler(); - public GeyserNeoForgeBootstrap(ModContainer container) { super(new GeyserNeoForgePlatform(container)); @@ -56,9 +64,26 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { NeoForge.EVENT_BUS.addListener(this::onServerStopping); NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); - NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather); + + NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onPermissionGather); this.onGeyserInitialize(); + + var sourceConverter = CommandSourceConverter.layered( + CommandSourceStack.class, + id -> getServer().getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + () -> getServer().createCommandSourceStack(), + ModCommandSource::new + ); + CommandManager cloud = new NeoForgeServerCommandManager<>( + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud); + this.setCommandRegistry(registry); + // An auxiliary listener for registering undefined permissions belonging to commands. See javadocs for more info. + NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined); } private void onServerStarted(ServerStartedEvent event) { @@ -87,13 +112,17 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { return FMLLoader.getDist().isDedicatedServer(); } - @Override - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { - return this.permissionHandler.hasPermission(source, permissionNode); - } + private void onPermissionGather(PermissionGatherEvent.Nodes event) { + getGeyser().eventBus().fire( + (GeyserRegisterPermissionsEvent) (permission, defaultValue) -> { + Objects.requireNonNull(permission, "permission"); + Objects.requireNonNull(defaultValue, "permission default for " + permission); - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel); + if (permission.isBlank()) { + return; + } + PermissionUtils.register(permission, defaultValue, event); + } + ); } } diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java new file mode 100644 index 000000000..a8854d5d9 --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java @@ -0,0 +1,101 @@ +/* + * 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.platform.neoforge; + +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.neoforge.PermissionNotRegisteredException; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class GeyserNeoForgeCommandRegistry extends CommandRegistry { + + /** + * Permissions with an undefined permission default. Use Set to not register the same fallback more than once. + * NeoForge requires that all permissions are registered, and cloud-neoforge follows that. + * This is unlike most platforms, on which we wouldn't register a permission if no default was provided. + */ + private final Set undefinedPermissions = new HashSet<>(); + + public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager cloud) { + super(geyser, cloud); + } + + @Override + protected void register(GeyserCommand command, Map commands) { + super.register(command, commands); + + // FIRST STAGE: Collect all permissions that may have undefined defaults. + if (!command.permission().isBlank() && command.permissionDefault() == null) { + // Permission requirement exists but no default value specified. + undefinedPermissions.add(command.permission()); + } + } + + @Override + protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) { + super.onRegisterPermissions(event); + + // SECOND STAGE + // Now that we are aware of all commands, we can eliminate some incorrect assumptions. + // Example: two commands may have the same permission, but only of them defines a permission default. + undefinedPermissions.removeAll(permissionDefaults.keySet()); + } + + /** + * Registers permissions with possibly undefined defaults. + * Should be subscribed late to allow extensions and mods to register a desired permission default first. + */ + void onPermissionGatherForUndefined(PermissionGatherEvent.Nodes event) { + // THIRD STAGE + for (String permission : undefinedPermissions) { + if (PermissionUtils.register(permission, TriState.NOT_SET, event)) { + // The permission was not already registered + geyser.getLogger().debug("Registered permission " + permission + " with fallback default value of NOT_SET"); + } + } + } + + @Override + public boolean hasPermission(GeyserCommandSource source, String permission) { + // NeoForgeServerCommandManager will throw this exception if the permission is not registered to the server. + // We can't realistically ensure that every permission is registered (calls by API users), so we catch this. + // This works for our calls, but not for cloud's internal usage. For that case, see above. + try { + return super.hasPermission(source, permission); + } catch (PermissionNotRegisteredException e) { + return false; + } + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java deleted file mode 100644 index 0a5f8f052..000000000 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2019-2023 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.platform.neoforge; - -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.player.Player; -import net.neoforged.neoforge.server.permission.PermissionAPI; -import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; -import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; -import net.neoforged.neoforge.server.permission.nodes.PermissionNode; -import net.neoforged.neoforge.server.permission.nodes.PermissionType; -import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.Constants; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommandManager; - -import java.lang.reflect.Constructor; -import java.util.HashMap; -import java.util.Map; - -public class GeyserNeoForgePermissionHandler { - - private static final Constructor PERMISSION_NODE_CONSTRUCTOR; - - static { - try { - @SuppressWarnings("rawtypes") - Constructor constructor = PermissionNode.class.getDeclaredConstructor( - String.class, - PermissionType.class, - PermissionNode.PermissionResolver.class, - PermissionDynamicContextKey[].class - ); - constructor.setAccessible(true); - PERMISSION_NODE_CONSTRUCTOR = constructor; - } catch (NoSuchMethodException e) { - throw new RuntimeException("Unable to construct PermissionNode!", e); - } - } - - private final Map> permissionNodes = new HashMap<>(); - - public void onPermissionGather(PermissionGatherEvent.Nodes event) { - this.registerNode(Constants.UPDATE_PERMISSION, event); - - GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager(); - for (Map.Entry entry : commandManager.commands().entrySet()) { - Command command = entry.getValue(); - - // Don't register aliases - if (!command.name().equals(entry.getKey())) { - continue; - } - - this.registerNode(command.permission(), event); - } - - for (Map commands : commandManager.extensionCommands().values()) { - for (Map.Entry entry : commands.entrySet()) { - Command command = entry.getValue(); - - // Don't register aliases - if (!command.name().equals(entry.getKey())) { - continue; - } - - this.registerNode(command.permission(), event); - } - } - } - - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { - PermissionNode node = this.permissionNodes.get(permissionNode); - if (node == null) { - GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode); - return false; - } - - return PermissionAPI.getPermission((ServerPlayer) source, node); - } - - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - if (!source.isPlayer()) { - return true; - } - assert source.getPlayer() != null; - boolean permission = this.hasPermission(source.getPlayer(), permissionNode); - if (!permission) { - return source.getPlayer().hasPermissions(permissionLevel); - } - - return true; - } - - private void registerNode(String node, PermissionGatherEvent.Nodes event) { - PermissionNode permissionNode = this.createNode(node); - - // NeoForge likes to crash if you try and register a duplicate node - if (!event.getNodes().contains(permissionNode)) { - event.addNodes(permissionNode); - this.permissionNodes.put(node, permissionNode); - } - } - - @SuppressWarnings("unchecked") - private PermissionNode createNode(String node) { - // The typical constructors in PermissionNode require a - // mod id, which means our permission nodes end up becoming - // geyser_neoforge. instead of just . We work around - // this by using reflection to access the constructor that - // doesn't require a mod id or ResourceLocation. - try { - return (PermissionNode) PERMISSION_NODE_CONSTRUCTOR.newInstance( - node, - PermissionTypes.BOOLEAN, - (PermissionNode.PermissionResolver) (player, playerUUID, context) -> false, - new PermissionDynamicContextKey[0] - ); - } catch (Exception e) { - throw new RuntimeException("Unable to create permission node " + node, e); - } - } -} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java new file mode 100644 index 000000000..c57dc9a6c --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java @@ -0,0 +1,79 @@ +/* + * 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.platform.neoforge; + +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.platform.neoforge.mixin.PermissionNodeMixin; + +/** + * Common logic for handling the more complicated way we have to register permission on NeoForge + */ +public class PermissionUtils { + + private PermissionUtils() { + //no + } + + /** + * Registers the given permission and its default value to the event. If the permission has the same name as one + * that has already been registered to the event, it will not be registered. In other words, it will not override. + * + * @param permission the permission to register + * @param permissionDefault the permission's default value. See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings. + * @param event the registration event + * @return true if the permission was registered + */ + public static boolean register(String permission, TriState permissionDefault, PermissionGatherEvent.Nodes event) { + // NeoForge likes to crash if you try and register a duplicate node + if (event.getNodes().stream().noneMatch(n -> n.getNodeName().equals(permission))) { + PermissionNode node = createNode(permission, permissionDefault); + event.addNodes(node); + return true; + } + return false; + } + + private static PermissionNode createNode(String node, TriState permissionDefault) { + return PermissionNodeMixin.geyser$construct( + node, + PermissionTypes.BOOLEAN, + (player, playerUUID, context) -> switch (permissionDefault) { + case TRUE -> true; + case FALSE -> false; + case NOT_SET -> { + if (player != null) { + yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel()); + } + yield false; // NeoForge javadocs say player is null in the case of an offline player. + } + } + ); + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java new file mode 100644 index 000000000..a43acd58a --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java @@ -0,0 +1,48 @@ +/* + * 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.platform.neoforge.mixin; + +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(value = PermissionNode.class, remap = false) // this is API - do not remap +public interface PermissionNodeMixin { + + /** + * Invokes the matching private constructor in {@link PermissionNode}. + *

+ * The typical constructors in PermissionNode require a mod id, which means our permission nodes + * would end up becoming {@code geyser_neoforge.} instead of just {@code }. + */ + @SuppressWarnings("rawtypes") // the varargs + @Invoker("") + static PermissionNode geyser$construct(String nodeName, PermissionType type, PermissionNode.PermissionResolver defaultResolver, PermissionDynamicContextKey... dynamics) { + throw new IllegalStateException(); + } +} diff --git a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml index fa01bb6ec..56b7d68e1 100644 --- a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -11,6 +11,8 @@ authors="GeyserMC" description="${description}" [[mixins]] config = "geyser.mixins.json" +[[mixins]] +config = "geyser_neoforge.mixins.json" [[dependencies.geyser_neoforge]] modId="neoforge" type="required" diff --git a/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json new file mode 100644 index 000000000..f1653051c --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.geyser.platform.neoforge.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "PermissionNodeMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index d7373f0a9..69d6dc9a4 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -25,30 +25,21 @@ package org.geysermc.geyser.platform.mod; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; import net.minecraft.server.MinecraftServer; -import net.minecraft.world.entity.player.Player; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor; import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager; import org.geysermc.geyser.text.GeyserLocale; @@ -59,7 +50,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.SocketAddress; import java.nio.file.Path; -import java.util.Map; import java.util.UUID; @RequiredArgsConstructor @@ -70,13 +60,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { private final GeyserModPlatform platform; + @Getter private GeyserImpl geyser; private Path dataFolder; - @Setter + @Setter @Getter private MinecraftServer server; - private GeyserCommandManager geyserCommandManager; + @Setter + private CommandRegistry commandRegistry; private GeyserModConfiguration geyserConfig; private GeyserModInjector geyserInjector; private final GeyserModLogger geyserLogger = new GeyserModLogger(); @@ -94,13 +86,14 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { this.geyserLogger.setDebug(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.geyser = GeyserImpl.load(this.platform.platformType(), this); - - // Create command manager here, since the permission handler on neo needs it - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); } public void onGeyserEnable() { + // "Disabling" a mod isn't possible; so if we fail to initialize we need to manually stop here + if (geyser == null) { + return; + } + if (GeyserImpl.getInstance().isReloading()) { if (!loadConfig()) { return; @@ -130,50 +123,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { if (isServer()) { this.geyserInjector.initializeLocalChannel(this); } - - // Start command building - // Set just "geyser" as the help command - GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser, - (GeyserCommand) geyser.commandManager().getCommands().get("help")); - LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor); - - // Register all subcommands as valid - for (Map.Entry command : geyser.commandManager().getCommands().entrySet()) { - GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue()); - builder.then(Commands.literal(command.getKey()) - .executes(executor) - // Could also test for Bedrock but depending on when this is called it may backfire - .requires(executor::testPermission) - // Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command - .then(Commands.argument("args", StringArgumentType.greedyString()) - .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args"))) - .requires(executor::testPermission))); - } - server.getCommands().getDispatcher().register(builder); - - // Register extension commands - for (Map.Entry> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) { - Map extensionCommands = extensionMapEntry.getValue(); - if (extensionCommands.isEmpty()) { - continue; - } - - // Register help command for just "/" - GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser, - (GeyserCommand) extensionCommands.get("help")); - LiteralArgumentBuilder extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor); - - for (Map.Entry command : extensionCommands.entrySet()) { - GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue()); - extCmdBuilder.then(Commands.literal(command.getKey()) - .executes(executor) - .requires(executor::testPermission) - .then(Commands.argument("args", StringArgumentType.greedyString()) - .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args"))) - .requires(executor::testPermission))); - } - server.getCommands().getDispatcher().register(extCmdBuilder); - } } @Override @@ -206,8 +155,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return commandRegistry; } @Override @@ -235,6 +184,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { return this.server.getServerVersion(); } + @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null @NonNull @Override public String getServerBindAddress() { @@ -270,10 +220,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { return this.platform.resolveResource(resource); } - public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode); - - public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel); - @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { try { diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java index 11ca0bc4f..6a724155f 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java @@ -25,17 +25,18 @@ package org.geysermc.geyser.platform.mod; -import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; -import org.geysermc.geyser.Constants; -import org.geysermc.geyser.platform.mod.command.ModCommandSender; +import org.geysermc.geyser.Permissions; +import org.geysermc.geyser.platform.mod.command.ModCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserModUpdateListener { public static void onPlayReady(Player player) { - CommandSourceStack stack = player.createCommandSourceStack(); - if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack)); + // Should be creating this in the supplier, but we need it for the permission check. + // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it. + ModCommandSource source = new ModCommandSource(player.createCommandSourceStack()); + if (source.hasPermission(Permissions.CHECK_UPDATE)) { + VersionCheckUtils.checkForGeyserUpdate(() -> source); } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java deleted file mode 100644 index 694dc732e..000000000 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java +++ /dev/null @@ -1,75 +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.platform.mod.command; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.commands.CommandSourceStack; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.platform.mod.GeyserModBootstrap; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Collections; - -public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command { - private final GeyserCommand command; - - public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) { - super(geyser, Collections.singletonMap(command.name(), command)); - this.command = command; - } - - public boolean testPermission(CommandSourceStack source) { - return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); - } - - @Override - public int run(CommandContext context) { - return runWithArgs(context, ""); - } - - public int runWithArgs(CommandContext context, String args) { - CommandSourceStack source = context.getSource(); - ModCommandSender sender = new ModCommandSender(source); - GeyserSession session = getGeyserSession(sender); - if (!testPermission(source)) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return 0; - } - - if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); - return 0; - } - - command.execute(session, sender, args.split(" ")); - return 0; - } -} diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java similarity index 77% rename from bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java index 5bebfae93..af1f368b3 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java @@ -31,19 +31,21 @@ import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.text.ChatColor; import java.util.Objects; +import java.util.UUID; -public class ModCommandSender implements GeyserCommandSource { +public class ModCommandSource implements GeyserCommandSource { private final CommandSourceStack source; - public ModCommandSender(CommandSourceStack source) { + public ModCommandSource(CommandSourceStack source) { this.source = source; + // todo find locale? } @Override @@ -75,8 +77,24 @@ public class ModCommandSender implements GeyserCommandSource { return !(source.getEntity() instanceof ServerPlayer); } + @Override + public @Nullable UUID playerUuid() { + if (source.getEntity() instanceof ServerPlayer player) { + return player.getUUID(); + } + return null; + } + @Override public boolean hasPermission(String permission) { - return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel()); + // Unlike other bootstraps; we delegate to cloud here too: + // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that + // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p + return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission); + } + + @Override + public Object handle() { + return source; } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java new file mode 100644 index 000000000..98620588e --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java @@ -0,0 +1,79 @@ +/* + * 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.platform.mod.mixin.server; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.SoundEvent; +import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(BlockItem.class) +public class BlockPlaceMixin { + + @Inject(method = "place", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V")) + private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable cir, BlockPlaceContext blockPlaceContext2, BlockState blockState, BlockPos blockPos, Level level, Player player, ItemStack itemStack, BlockState blockState2, SoundType soundType) { + if (player == null) { + return; + } + + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID()); + if (session == null) { + return; + } + + Vector3f position = Vector3f.from( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ); + + LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket(); + placeBlockSoundPacket.setSound(SoundEvent.PLACE); + placeBlockSoundPacket.setPosition(position); + placeBlockSoundPacket.setBabySound(false); + placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(blockState2))); + placeBlockSoundPacket.setIdentifier(":"); + session.sendUpstreamPacket(placeBlockSoundPacket); + session.setLastBlockPlacePosition(null); + session.setLastBlockPlaced(null); + } +} diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java new file mode 100644 index 000000000..6ac51ba52 --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java @@ -0,0 +1,127 @@ +/* + * 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.platform.mod.mixin.server; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.piston.PistonBaseBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.cloudburstmc.math.vector.Vector3i; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Mixin(PistonBaseBlock.class) +public class PistonBaseBlockMixin { + + @Shadow + @Final + private boolean isSticky; + + @ModifyExpressionValue(method = "moveBlocks", + at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;") + ) + private HashMap geyser$onMapCreate(HashMap original, @Share("pushBlocks") LocalRef> localRef) { + localRef.set(original); + return original; + } + + @Inject(method = "moveBlocks", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/piston/PistonStructureResolver;getToDestroy()Ljava/util/List;") + ) + private void geyser$onBlocksMove(Level level, BlockPos blockPos, Direction direction, boolean isExtending, CallbackInfoReturnable cir, @Share("pushBlocks") LocalRef> localRef) { + PistonValueType type = isExtending ? PistonValueType.PUSHING : PistonValueType.PULLING; + boolean sticky = this.isSticky; + + Object2ObjectMap attachedBlocks = new Object2ObjectArrayMap<>(); + boolean blocksFilled = false; + + for (Map.Entry entry : GeyserImpl.getInstance().getSessionManager().getSessions().entrySet()) { + Player player = level.getPlayerByUUID(entry.getKey()); + //noinspection resource + if (player == null || !player.level().equals(level)) { + continue; + } + GeyserSession session = entry.getValue(); + + int dX = Math.abs(blockPos.getX() - player.getBlockX()) >> 4; + int dZ = Math.abs(blockPos.getZ() - player.getBlockZ()) >> 4; + if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) { + // Ignore pistons outside the player's render distance + continue; + } + + // Trying to grab the blocks from the world like other platforms would result in the moving piston block + // being returned instead. + if (!blocksFilled) { + Map blocks = localRef.get(); + for (Map.Entry blockStateEntry : blocks.entrySet()) { + int blockStateId = Block.BLOCK_STATE_REGISTRY.getId(blockStateEntry.getValue()); + org.geysermc.geyser.level.block.type.BlockState state = org.geysermc.geyser.level.block.type.BlockState.of(blockStateId); + attachedBlocks.put(geyser$fromBlockPos(blockStateEntry.getKey()), state); + } + blocksFilled = true; + } + + org.geysermc.geyser.level.physics.Direction orientation = org.geysermc.geyser.level.physics.Direction.VALUES[direction.ordinal()]; + + Vector3i position = geyser$fromBlockPos(blockPos); + session.executeInEventLoop(() -> { + PistonCache pistonCache = session.getPistonCache(); + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> + new PistonBlockEntity(session, position, orientation, sticky, !isExtending)); + blockEntity.setAction(type, attachedBlocks); + }); + } + } + + @Unique + private static Vector3i geyser$fromBlockPos(BlockPos pos) { + return Vector3i.from(pos.getX(), pos.getY(), pos.getZ()); + } + +} diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java index db1768737..89452eba3 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java @@ -48,7 +48,6 @@ 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; @@ -111,12 +110,6 @@ public class GeyserModWorldManager extends GeyserWorldManager { return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion(); } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - ServerPlayer player = getPlayer(session); - return GeyserModBootstrap.getInstance().hasPermission(player, permission); - } - @Override public GameMode getDefaultGameMode(GeyserSession session) { return GameMode.byId(server.getDefaultGameType().getId()); diff --git a/bootstrap/mod/src/main/resources/geyser.mixins.json b/bootstrap/mod/src/main/resources/geyser.mixins.json index 47b2f60f3..e820e654d 100644 --- a/bootstrap/mod/src/main/resources/geyser.mixins.json +++ b/bootstrap/mod/src/main/resources/geyser.mixins.json @@ -4,6 +4,8 @@ "package": "org.geysermc.geyser.platform.mod.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ + "server.BlockPlaceMixin", + "server.PistonBaseBlockMixin", "server.ServerConnectionListenerMixin" ], "server": [ diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index fcb85f100..feabfdd7a 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,3 +1,8 @@ +plugins { + id("geyser.platform-conventions") + id("geyser.modrinth-uploading-conventions") +} + dependencies { api(projects.core) api(libs.erosion.bukkit.common) { @@ -17,12 +22,12 @@ dependencies { classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations }) + implementation(libs.cloud.paper) implementation(libs.commodore) implementation(libs.adventure.text.serializer.bungeecord) compileOnly(libs.folia.api) - compileOnly(libs.paper.mojangapi) compileOnlyApi(libs.viaversion) } @@ -33,13 +38,15 @@ platformRelocate("com.fasterxml.jackson") platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") +platformRelocate("org.incendo") +platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform provided(libs.viaversion) -application { - mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.spigot.GeyserSpigotMain" } tasks.withType { @@ -79,5 +86,7 @@ tasks.withType { modrinth { uploadFile.set(tasks.getByPath("shadowJar")) + gameVersions.addAll("1.16.5", "1.17", "1.17.1", "1.18", "1.18.1", "1.18.2", "1.19", + "1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6") loaders.addAll("spigot", "paper") } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 2d13155f2..a2d5c992b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -30,37 +30,34 @@ import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import io.netty.buffer.ByteBuf; -import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; import org.bukkit.block.data.BlockData; -import org.bukkit.command.CommandMap; -import org.bukkit.command.PluginCommand; +import org.bukkit.command.CommandSender; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.paper.PaperAdapters; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; -import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; -import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandRegistry; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager; @@ -68,21 +65,21 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorld import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.bukkit.BukkitCommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.LegacyPaperCommandManager; import java.io.File; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - private GeyserSpigotCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotInjector geyserInjector; private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ? @@ -120,7 +117,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); geyserLogger.error(""); geyserLogger.error("*********************************************"); - Bukkit.getPluginManager().disablePlugin(this); return; } @@ -134,7 +130,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); geyserLogger.error(""); geyserLogger.error("*********************************************"); - Bukkit.getPluginManager().disablePlugin(this); return; } } @@ -147,10 +142,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!"); geyserLogger.error(""); geyserLogger.error("*********************************************"); - Bukkit.getPluginManager().disablePlugin(this); return; } + try { + // Check spigot config for BungeeCord mode + if (Bukkit.getServer().spigot().getConfig().getBoolean("settings.bungeecord")) { + warnInvalidProxySetups("BungeeCord"); + return; + } + + // Now: Check for velocity mode - deliberately after checking bungeecord because this is a paper only option + if (Bukkit.getServer().spigot().getPaperConfig().getBoolean("proxies.velocity.enabled")) { + warnInvalidProxySetups("Velocity"); + return; + } + } catch (NoSuchMethodError e) { + // no-op + } + if (!loadConfig()) { return; } @@ -165,31 +175,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onEnable() { - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); - this.geyserCommandManager.init(); - - // Because Bukkit locks its command map upon startup, we need to - // add our plugin commands in onEnable, but populating the executor - // can happen at any time (later in #onGeyserEnable()) - CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); - for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) { - // Thanks again, Bukkit - try { - Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); - constructor.setAccessible(true); - - PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this); - pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!"); - - commandMap.register(extension.description().id(), "geyserext", pluginCommand); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { - this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex); - } + // Disabling the plugin in onLoad() is not supported; we need to manually stop here and disable ourselves + if (geyser == null) { + Bukkit.getPluginManager().disablePlugin(this); + return; } + // Register commands after Geyser initialization, but before the server starts. + var sourceConverter = new CommandSourceConverter<>( + CommandSender.class, + Bukkit::getPlayer, + Bukkit::getConsoleSender, + SpigotCommandSource::new + ); + LegacyPaperCommandManager cloud; + try { + // LegacyPaperCommandManager works for spigot too, see https://cloud.incendo.org/minecraft/paper + cloud = new LegacyPaperCommandManager<>( + this, + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + // Commodore brigadier on Spigot/Paper 1.13 - 1.18.2 + // Paper-only brigadier on 1.19+ + cloud.registerBrigadier(); + } catch (BukkitCommandManager.BrigadierInitializationException e) { + geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage()); + } + + this.commandRegistry = new SpigotCommandRegistry(geyser, cloud); + // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes Bukkit.getPluginManager().registerEvents(new Listener() { - @EventHandler public void onServerLoaded(ServerLoadEvent event) { if (event.getType() == ServerLoadEvent.LoadType.RELOAD) { @@ -227,7 +249,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - // Don't need to re-create the world manager/re-register commands/reinject when reloading + // Don't need to re-create the world manager/reinject when reloading if (GeyserImpl.getInstance().isReloading()) { return; } @@ -282,79 +304,40 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Using default world manager."); } - PluginCommand geyserCommand = this.getCommand("geyser"); - Objects.requireNonNull(geyserCommand, "base command cannot be null"); - geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); - - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - PluginCommand command = this.getCommand(entry.getKey().description().id()); - if (command == null) { - continue; - } - - command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); - } - // Register permissions so they appear in, for example, LuckPerms' UI - // Re-registering permissions throws an error - for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { - Command command = entry.getValue(); - if (command.aliases().contains(entry.getKey())) { - // Don't register aliases - continue; + // Re-registering permissions without removing it throws an error + PluginManager pluginManager = Bukkit.getPluginManager(); + geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { + Objects.requireNonNull(permission, "permission"); + Objects.requireNonNull(def, "permission default for " + permission); + + if (permission.isBlank()) { + return; + } + PermissionDefault permissionDefault = switch (def) { + case TRUE -> PermissionDefault.TRUE; + case FALSE -> PermissionDefault.FALSE; + case NOT_SET -> PermissionDefault.OP; + }; + + Permission existingPermission = pluginManager.getPermission(permission); + if (existingPermission != null) { + geyserLogger.debug("permission " + permission + " with default " + + existingPermission.getDefault() + " is being overridden by " + permissionDefault); + + pluginManager.removePermission(permission); } - Bukkit.getPluginManager().addPermission(new Permission(command.permission(), - GeyserLocale.getLocaleStringLog(command.description()), - command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); - } - - // Register permissions for extension commands - for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) { - for (Map.Entry entry : commandEntry.getValue().entrySet()) { - Command command = entry.getValue(); - if (command.aliases().contains(entry.getKey())) { - // Don't register aliases - continue; - } - - if (command.permission().isBlank()) { - continue; - } - - // Avoid registering the same permission twice, e.g. for the extension help commands - if (Bukkit.getPluginManager().getPermission(command.permission()) != null) { - GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered"); - continue; - } - - Bukkit.getPluginManager().addPermission(new Permission(command.permission(), - GeyserLocale.getLocaleStringLog(command.description()), - command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); - } - } - - Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION, - "Whether update notifications can be seen", PermissionDefault.OP)); + pluginManager.addPermission(new Permission(permission, permissionDefault)); + }); // Events cannot be unregistered - re-registering results in duplicate firings GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + pluginManager.registerEvents(blockPlaceListener, this); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + pluginManager.registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); - - boolean brigadierSupported = CommodoreProvider.isSupported(); - geyserLogger.debug("Brigadier supported? " + brigadierSupported); - if (brigadierSupported) { - GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); - } + pluginManager.registerEvents(new GeyserSpigotUpdateListener(), this); } @Override @@ -390,8 +373,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override @@ -494,4 +477,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return true; } + + private void warnInvalidProxySetups(String platform) { + geyserLogger.error("*********************************************"); + geyserLogger.error(""); + geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy_backend", platform)); + geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.setup_guide", "https://geysermc.org/wiki/geyser/setup/")); + geyserLogger.error(""); + geyserLogger.error("*********************************************"); + } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java index 5e3c4def8..8a8a43460 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java @@ -29,8 +29,8 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; @@ -40,7 +40,7 @@ public final class GeyserSpigotUpdateListener implements Listener { public void onPlayerJoin(final PlayerJoinEvent event) { if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { final Player player = event.getPlayer(); - if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + if (player.hasPermission(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java deleted file mode 100644 index 61900174c..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java +++ /dev/null @@ -1,61 +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.platform.spigot.command; - -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import me.lucko.commodore.Commodore; -import me.lucko.commodore.CommodoreProvider; -import org.bukkit.Bukkit; -import org.bukkit.command.PluginCommand; -import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; - -/** - * Needs to be a separate class so pre-1.13 loads correctly. - */ -public final class GeyserBrigadierSupport { - - public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) { - // Enable command completions if supported - // This is beneficial because this is sent over the network and Bedrock can see it - Commodore commodore = CommodoreProvider.getCommodore(plugin); - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); - for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) { - builder.then(LiteralArgumentBuilder.literal(command)); - } - commodore.register(pluginCommand, builder); - - try { - Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin); - plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); - } catch (ClassNotFoundException e) { - plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener."); - } - } - - private GeyserBrigadierSupport() { - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java deleted file mode 100644 index dcec045ab..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.platform.spigot.command; - -import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; -import com.mojang.brigadier.tree.CommandNode; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; - -import java.net.InetSocketAddress; -import java.util.Iterator; -import java.util.Map; - -public final class GeyserPaperCommandListener implements Listener { - - @SuppressWarnings("UnstableApiUsage") - @EventHandler - public void onCommandSend(AsyncPlayerSendCommandsEvent event) { - // Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2 - // event.hasFiredAsync is never true - if (event.isAsynchronous()) { - CommandNode geyserBrigadier = event.getCommandNode().getChild("geyser"); - if (geyserBrigadier != null) { - Player player = event.getPlayer(); - boolean isJavaPlayer = isProbablyJavaPlayer(player); - Map commands = GeyserImpl.getInstance().commandManager().getCommands(); - Iterator> it = geyserBrigadier.getChildren().iterator(); - - while (it.hasNext()) { - CommandNode subnode = it.next(); - Command command = commands.get(subnode.getName()); - if (command != null) { - if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) { - // Remove this from the node as we don't have permission to use it - it.remove(); - } - } - } - } - } - } - - /** - * This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that - * chance, though. - */ - private boolean isProbablyJavaPlayer(Player player) { - if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) { - // For sure this is a Bedrock player - return false; - } - - if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) { - InetSocketAddress address = player.getAddress(); - if (address != null) { - return address.getPort() != 0; - } - } - return true; - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java deleted file mode 100644 index 6780bde17..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ /dev/null @@ -1,88 +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.platform.spigot.command; - -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor { - - public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map commands) { - super(geyser, commands); - } - - @Override - public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) { - SpigotCommandSource commandSender = new SpigotCommandSource(sender); - GeyserSession session = getGeyserSession(commandSender); - - if (args.length > 0) { - GeyserCommand geyserCommand = getCommand(args[0]); - if (geyserCommand != null) { - if (!sender.hasPermission(geyserCommand.permission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return true; - } - if (geyserCommand.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale())); - return true; - } - geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); - return true; - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); - commandSender.sendMessage(ChatColor.RED + message); - } - } else { - getCommand("help").execute(session, commandSender, new String[0]); - return true; - } - return true; - } - - @Override - public List onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) { - if (args.length == 1) { - return tabComplete(new SpigotCommandSource(sender)); - } - return Collections.emptyList(); - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java similarity index 61% rename from bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java index 655d3be23..39496d2c6 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2023 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 @@ -29,16 +29,21 @@ import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.incendo.cloud.CommandManager; import java.lang.reflect.Field; -public class GeyserSpigotCommandManager extends GeyserCommandManager { +public class SpigotCommandRegistry extends CommandRegistry { - private static final CommandMap COMMAND_MAP; + private final CommandMap commandMap; + + public SpigotCommandRegistry(GeyserImpl geyser, CommandManager cloud) { + super(geyser, cloud); - static { CommandMap commandMap = null; try { // Paper-only @@ -49,24 +54,28 @@ public class GeyserSpigotCommandManager extends GeyserCommandManager { Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); cmdMapField.setAccessible(true); commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); + } catch (Exception ex) { + geyser.getLogger().error("Failed to get Spigot's CommandMap", ex); } } - COMMAND_MAP = commandMap; - } - - public GeyserSpigotCommandManager(GeyserImpl geyser) { - super(geyser); + this.commandMap = commandMap; } + @NonNull @Override - public String description(String command) { - Command cmd = COMMAND_MAP.getCommand(command.replace("/", "")); - return cmd != null ? cmd.getDescription() : ""; - } + public String description(@NonNull String command, @NonNull String locale) { + // check if the command is /geyser or an extension command so that we can localize the description + String description = super.description(command, locale); + if (!description.isBlank()) { + return description; + } - public static CommandMap getCommandMap() { - return COMMAND_MAP; + if (commandMap != null) { + Command cmd = commandMap.getCommand(command); + if (cmd != null) { + return cmd.getDescription(); + } + } + return ""; } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index 365e9ad17..c1fb837c2 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -27,17 +27,21 @@ package org.geysermc.geyser.platform.spigot.command; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; -public class SpigotCommandSource implements GeyserCommandSource { - private final org.bukkit.command.CommandSender handle; +import java.util.UUID; - public SpigotCommandSource(org.bukkit.command.CommandSender handle) { +public class SpigotCommandSource implements GeyserCommandSource { + private final CommandSender handle; + + public SpigotCommandSource(CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded GeyserLocale.loadGeyserLocale(locale()); @@ -65,11 +69,24 @@ public class SpigotCommandSource implements GeyserCommandSource { handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message)); } + @Override + public Object handle() { + return handle; + } + @Override public boolean isConsole() { return handle instanceof ConsoleCommandSender; } + @Override + public @Nullable UUID playerUuid() { + if (handle instanceof Player player) { + return player.getUniqueId(); + } + return null; + } + @SuppressWarnings("deprecation") @Override public String locale() { @@ -83,6 +100,7 @@ public class SpigotCommandSource implements GeyserCommandSource { @Override public boolean hasPermission(String permission) { - return handle.hasPermission(permission); + // Don't trust Spigot to handle blank permissions + return permission.isBlank() || handle.hasPermission(permission); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 73356c4e7..6588a22a3 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -128,15 +128,6 @@ public class GeyserSpigotWorldManager extends WorldManager { return GameMode.byId(Bukkit.getDefaultGameMode().ordinal()); } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - Player player = Bukkit.getPlayer(session.javaUuid()); - if (player != null) { - return player.hasPermission(permission); - } - return false; - } - @Override public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) { Player bukkitPlayer; diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 6e81ccdb6..14e98f577 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -6,11 +6,3 @@ version: ${version} softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 folia-supported: true -commands: - geyser: - description: The main command for Geyser. - usage: /geyser - permission: geyser.command -permissions: - geyser.command: - default: true diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index eaf895108..b210693c1 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -1,5 +1,10 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer +plugins { + application + id("geyser.platform-conventions") +} + val terminalConsoleVersion = "1.2.0" val jlineVersion = "3.21.0" diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index f289fa2ba..87fbbf0aa 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -42,7 +42,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -69,7 +70,8 @@ import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { - private GeyserCommandManager geyserCommandManager; + private StandaloneCloudCommandManager cloud; + private CommandRegistry commandRegistry; private GeyserStandaloneConfiguration geyserConfig; private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger(); private IGeyserPingPassthrough geyserPingPassthrough; @@ -222,13 +224,24 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyser = GeyserImpl.load(PlatformType.STANDALONE, this); - geyserCommandManager = new GeyserCommandManager(geyser); - geyserCommandManager.init(); + boolean reloading = geyser.isReloading(); + if (!reloading) { + // Currently there would be no significant benefit of re-initializing commands. Also, we would have to unsubscribe CommandRegistry. + // Fire GeyserDefineCommandsEvent after PreInitEvent, before PostInitEvent, for consistency with other bootstraps. + cloud = new StandaloneCloudCommandManager(geyser); + commandRegistry = new CommandRegistry(geyser, cloud); + } GeyserImpl.start(); + if (!reloading) { + // Event must be fired after CommandRegistry has subscribed its listener. + // Also, the subscription for the Permissions class is created when Geyser is initialized. + cloud.fireRegisterPermissionsEvent(); + } + if (gui != null) { - gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager); + gui.enableCommands(geyser.getScheduledThread(), commandRegistry); } geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -255,8 +268,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { @Override public void onGeyserDisable() { - // We can re-register commands on standalone, so why not - GeyserImpl.getInstance().commandManager().getCommands().clear(); geyser.disable(); } @@ -277,8 +288,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return commandRegistry; } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index 3a34920ce..21e6a5e82 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -44,7 +44,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override protected void runCommand(String line) { - GeyserImpl.getInstance().commandManager().runCommand(this, line); + // don't block the terminal! + GeyserImpl geyser = GeyserImpl.getInstance(); + geyser.getScheduledThread().execute(() -> geyser.commandRegistry().runCommand(this, line)); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index b82d8cc94..4cbd178af 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -28,7 +28,7 @@ package org.geysermc.geyser.platform.standalone.gui; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -271,15 +271,14 @@ public class GeyserStandaloneGUI { } /** - * Enable the command input box. + * Enables the command input box. * - * @param executor the executor for running commands off the GUI thread - * @param commandManager the command manager to delegate commands to + * @param executor the executor that commands will be run on + * @param registry the command registry containing all current commands */ - public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) { + public void enableCommands(ScheduledExecutorService executor, CommandRegistry registry) { // we don't want to block the GUI thread with the command execution - // todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler - commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS); + commandListener.dispatcher = cmd -> executor.execute(() -> registry.runCommand(logger, cmd)); commandInput.setEnabled(true); commandInput.requestFocusInWindow(); } @@ -344,13 +343,14 @@ public class GeyserStandaloneGUI { private class CommandListener implements ActionListener { - private Consumer handler; + private Consumer dispatcher; @Override public void actionPerformed(ActionEvent e) { - String command = commandInput.getText(); + // the headless variant of Standalone strips trailing whitespace for us - we need to manually + String command = commandInput.getText().stripTrailing(); appendConsole(command + "\n"); // show what was run in the console - handler.accept(command); // run the command + dispatcher.accept(command); // run the command commandInput.setText(""); // clear the input } } diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 4daad9784..05035e271 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -1,14 +1,22 @@ +plugins { + id("geyser.platform-conventions") + id("geyser.modrinth-uploading-conventions") +} + dependencies { annotationProcessor(libs.velocity.api) api(projects.core) compileOnlyApi(libs.velocity.api) + api(libs.cloud.velocity) } platformRelocate("com.fasterxml.jackson") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") platformRelocate("org.yaml") +platformRelocate("org.incendo") +platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated exclude("com.google.*:*") @@ -38,8 +46,8 @@ exclude("net.kyori:adventure-nbt:*") // These dependencies are already present on the platform provided(libs.velocity.api) -application { - mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.velocity.GeyserVelocityMain" } tasks.withType { @@ -74,4 +82,4 @@ tasks.withType { modrinth { uploadFile.set(tasks.getByPath("shadowJar")) loaders.addAll("velocity") -} \ No newline at end of file +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 539bdadbf..8fa47f569 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.platform.velocity; import com.google.inject.Inject; -import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; @@ -34,24 +34,28 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor; +import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.velocity.VelocityCommandManager; import org.slf4j.Logger; import java.io.File; @@ -59,29 +63,28 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Map; import java.util.UUID; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") public class GeyserVelocityPlugin implements GeyserBootstrap { private final ProxyServer proxyServer; - private final CommandManager commandManager; + private final PluginContainer container; private final GeyserVelocityLogger geyserLogger; - private GeyserCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityInjector geyserInjector; private IGeyserPingPassthrough geyserPingPassthrough; + private CommandRegistry commandRegistry; private GeyserImpl geyser; @Getter private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/"); @Inject - public GeyserVelocityPlugin(ProxyServer server, Logger logger, CommandManager manager) { - this.geyserLogger = new GeyserVelocityLogger(logger); + public GeyserVelocityPlugin(ProxyServer server, PluginContainer container, Logger logger) { this.proxyServer = server; - this.commandManager = manager; + this.container = container; + this.geyserLogger = new GeyserVelocityLogger(logger); } @Override @@ -106,19 +109,36 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); this.geyserInjector = new GeyserVelocityInjector(proxyServer); + + // We need to register commands here, rather than in onGeyserEnable which is invoked during the appropriate ListenerBoundEvent. + // Reason: players can connect after a listener is bound, and a player join locks registration to the cloud CommandManager. + var sourceConverter = new CommandSourceConverter<>( + CommandSource.class, + id -> proxyServer.getPlayer(id).orElse(null), + proxyServer::getConsoleCommandSource, + VelocityCommandSource::new + ); + CommandManager cloud = new VelocityCommandManager<>( + container, + proxyServer, + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults } @Override public void onGeyserEnable() { + // If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start + if (geyser == null) { + return; + } if (GeyserImpl.getInstance().isReloading()) { if (!loadConfig()) { return; } this.geyserLogger.setDebug(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - } else { - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); } GeyserImpl.start(); @@ -129,22 +149,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } - // No need to re-register commands when reloading - if (GeyserImpl.getInstance().isReloading()) { - return; + // No need to re-register events + if (!GeyserImpl.getInstance().isReloading()) { + proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } - - this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); - } - - proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override @@ -175,8 +183,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java index 31e584612..c1c88b70d 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java @@ -28,8 +28,8 @@ package org.geysermc.geyser.platform.velocity; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.proxy.Player; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; @@ -39,7 +39,7 @@ public final class GeyserVelocityUpdateListener { public void onPlayerJoin(PostLoginEvent event) { if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { final Player player = event.getPlayer(); - if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + if (player.hasPermission(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player)); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java deleted file mode 100644 index c89c35b06..000000000 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ /dev/null @@ -1,83 +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.platform.velocity.command; - -import com.velocitypowered.api.command.SimpleCommand; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand { - - public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map commands) { - super(geyser, commands); - } - - @Override - public void execute(Invocation invocation) { - GeyserCommandSource sender = new VelocityCommandSource(invocation.source()); - GeyserSession session = getGeyserSession(sender); - - if (invocation.arguments().length > 0) { - GeyserCommand command = getCommand(invocation.arguments()[0]); - if (command != null) { - if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).permission())) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } - if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); - return; - } - command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale()); - sender.sendMessage(ChatColor.RED + message); - } - } else { - getCommand("help").execute(session, sender, new String[0]); - } - } - - @Override - public List suggest(Invocation invocation) { - // Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot. - if (invocation.arguments().length == 0 || invocation.arguments().length == 1) { - return tabComplete(new VelocityCommandSource(invocation.source())); - } - return Collections.emptyList(); - } -} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index 403e4cb20..2240f9988 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -31,10 +31,12 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; +import java.util.UUID; public class VelocityCommandSource implements GeyserCommandSource { @@ -72,6 +74,14 @@ public class VelocityCommandSource implements GeyserCommandSource { return handle instanceof ConsoleCommandSource; } + @Override + public @Nullable UUID playerUuid() { + if (handle instanceof Player player) { + return player.getUniqueId(); + } + return null; + } + @Override public String locale() { if (handle instanceof Player) { @@ -83,6 +93,12 @@ public class VelocityCommandSource implements GeyserCommandSource { @Override public boolean hasPermission(String permission) { - return handle.hasPermission(permission); + // Handle blank permissions ourselves, as velocity only handles empty ones + return permission.isBlank() || handle.hasPermission(permission); + } + + @Override + public Object handle() { + return handle; } } diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts index 6eadc790f..c13862a27 100644 --- a/bootstrap/viaproxy/build.gradle.kts +++ b/bootstrap/viaproxy/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("geyser.platform-conventions") +} + dependencies { api(projects.core) @@ -8,12 +12,14 @@ platformRelocate("net.kyori") platformRelocate("org.yaml") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("org.cloudburstmc.netty") +platformRelocate("org.incendo") +platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated // These dependencies are already present on the platform provided(libs.viaproxy) -application { - mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain" } tasks.withType { diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java index bdc80335a..b5e614468 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java @@ -34,13 +34,15 @@ import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; import org.apache.logging.log4j.LogManager; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -50,7 +52,6 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.LoopbackUtil; -import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -66,7 +67,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser")); private GeyserViaProxyConfiguration config; private GeyserImpl geyser; - private GeyserCommandManager commandManager; + private StandaloneCloudCommandManager cloud; + private CommandRegistry commandRegistry; private IGeyserPingPassthrough pingPassthrough; @Override @@ -87,7 +89,9 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst @EventHandler private void onConsoleCommand(final ConsoleCommandEvent event) { final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); - if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) { + CommandRegistry registry = this.getCommandRegistry(); + if (registry.rootCommands().contains(command)) { + registry.runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs())); event.setCancelled(true); } } @@ -128,21 +132,36 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst @Override public void onGeyserEnable() { - if (GeyserImpl.getInstance().isReloading()) { + // If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start + if (geyser == null) { + return; + } + boolean reloading = geyser.isReloading(); + if (reloading) { if (!this.loadConfig()) { return; } + } else { + // Only initialized once - documented in the Geyser-Standalone bootstrap + this.cloud = new StandaloneCloudCommandManager(geyser); + this.commandRegistry = new CommandRegistry(geyser, cloud); } - this.commandManager = new GeyserCommandManager(this.geyser); - this.commandManager.init(); - GeyserImpl.start(); + if (!reloading) { + // Event must be fired after CommandRegistry has subscribed its listener. + // Also, the subscription for the Permissions class is created when Geyser is initialized (by GeyserImpl#start) + this.cloud.fireRegisterPermissionsEvent(); + } + if (ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().newerThanOrEqualTo(LegacyProtocolVersion.b1_8tob1_8_1)) { // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser); } + if (this.config.getRemote().authType() == AuthType.FLOODGATE) { + ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true); + } } @Override @@ -166,8 +185,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.commandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override @@ -185,7 +204,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst return new GeyserViaProxyDumpInfo(); } - @NotNull + @NonNull @Override public String getServerBindAddress() { if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) { @@ -209,6 +228,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst return false; } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { try { final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this); diff --git a/bootstrap/viaproxy/src/main/resources/viaproxy.yml b/bootstrap/viaproxy/src/main/resources/viaproxy.yml index 66fbdb932..89fc612cd 100644 --- a/bootstrap/viaproxy/src/main/resources/viaproxy.yml +++ b/bootstrap/viaproxy/src/main/resources/viaproxy.yml @@ -2,4 +2,4 @@ name: "${name}-ViaProxy" version: "${version}" author: "${author}" main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin" -min-version: "3.2.1" +min-version: "3.3.2" diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 190386667..b87490880 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -12,9 +12,14 @@ repositories { } dependencies { + // This is for the LibsAccessor.kt hack // this is OK as long as the same version catalog is used in the main build and build-logic // see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192 implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + + // This is for applying plugins, and using the version from the libs.versions.toml + // Unfortunately they still need to be applied by their string name in the convention scripts. + implementation(libs.lombok) implementation(libs.indra) implementation(libs.shadow) implementation(libs.architectury.plugin) diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 63bde189b..bd4560d11 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -8,4 +8,4 @@ dependencyResolutionManagement { } } -rootProject.name = "build-logic" \ No newline at end of file +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 41e11344b..1b81f6601 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -118,3 +118,12 @@ open class DownloadFilesTask : DefaultTask() { private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = if (excludedOn and bit > 0) section else "" +fun projectVersion(project: Project): String = + project.version.toString().replace("SNAPSHOT", "b" + buildNumber()) + +fun versionName(project: Project): String = + "Geyser-" + project.name.replaceFirstChar { it.uppercase() } + "-" + projectVersion(project) + +fun buildNumber(): Int = + (System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 + diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 950c0184b..093f0a8c0 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -3,9 +3,10 @@ plugins { id("net.kyori.indra") } -dependencies { - compileOnly("org.checkerframework", "checker-qual", "3.19.0") -} +val rootProperties: Map = project.rootProject.properties +group = rootProperties["group"] as String + "." + rootProperties["id"] as String +version = rootProperties["version"] as String +description = rootProperties["description"] as String indra { github("GeyserMC", "Geyser") { @@ -20,18 +21,52 @@ indra { } } -tasks { - processResources { - // Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) { - expand( - "id" to "geyser", - "name" to "Geyser", - "version" to project.version, - "description" to project.description, - "url" to "https://geysermc.org", - "author" to "GeyserMC" - ) - } +dependencies { + compileOnly("org.checkerframework", "checker-qual", libs.checker.qual.get().version) +} + +repositories { + // mavenLocal() + + mavenCentral() + + // Floodgate, Cumulus etc. + maven("https://repo.opencollab.dev/main") + + // Paper, Velocity + maven("https://repo.papermc.io/repository/maven-public") + + // Spigot + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { + mavenContent { snapshotsOnly() } } -} \ No newline at end of file + + // BungeeCord + maven("https://oss.sonatype.org/content/repositories/snapshots") { + mavenContent { snapshotsOnly() } + } + + // NeoForge + maven("https://maven.neoforged.net/releases") { + mavenContent { releasesOnly() } + } + + // Minecraft + maven("https://libraries.minecraft.net") { + name = "minecraft" + mavenContent { releasesOnly() } + } + + // ViaVersion + maven("https://repo.viaversion.com") { + name = "viaversion" + } + + // Jitpack for e.g. MCPL + maven("https://jitpack.io") { + content { includeGroupByRegex("com\\.github\\..*") } + } + + // For Adventure snapshots + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} diff --git a/build-logic/src/main/kotlin/geyser.build-logic.gradle.kts b/build-logic/src/main/kotlin/geyser.build-logic.gradle.kts deleted file mode 100644 index e69de29bb..000000000 diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index 7952bcf14..779d6446a 100644 --- a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -2,10 +2,9 @@ import net.fabricmc.loom.task.RemapJarTask import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.maven plugins { - id("geyser.publish-conventions") + id("geyser.platform-conventions") id("architectury-plugin") id("dev.architectury.loom") } @@ -37,6 +36,10 @@ provided("io.netty", "netty-resolver-dns") provided("io.netty", "netty-resolver-dns-native-macos") provided("org.ow2.asm", "asm") +// cloud-fabric/cloud-neoforge jij's all cloud depends already +provided("org.incendo", ".*") +provided("io.leangen.geantyref", "geantyref") + architectury { minecraft = libs.minecraft.get().version as String } @@ -82,7 +85,7 @@ tasks { register("remapModrinthJar", RemapJarTask::class) { dependsOn(shadowJar) inputFile.set(shadowJar.get().archiveFile) - archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER")) + archiveVersion.set(versionName(project)) archiveClassifier.set("") } } @@ -112,12 +115,3 @@ dependencies { minecraft(libs.minecraft) mappings(loom.officialMojangMappings()) } - -repositories { - // mavenLocal() - 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") -} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts index d710ae1a2..d2e207fa4 100644 --- a/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts @@ -8,11 +8,12 @@ tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody) modrinth { token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token? projectId.set("geyser") - versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER")) + versionName.set(versionName(project)) + versionNumber.set(projectVersion(project)) versionType.set("beta") changelog.set(System.getenv("CHANGELOG") ?: "") - gameVersions.add(libs.minecraft.get().version as String) + gameVersions.addAll("1.21", libs.minecraft.get().version as String) failSilently.set(true) syncBodyFrom.set(rootProject.file("README.md").readText()) -} \ No newline at end of file +} diff --git a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts index 81d224906..7a342783b 100644 --- a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts @@ -1,4 +1,20 @@ plugins { - application id("geyser.publish-conventions") -} \ No newline at end of file + id("io.freefair.lombok") +} + +tasks { + processResources { + // Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) { + expand( + "id" to "geyser", + "name" to "Geyser", + "version" to project.version, + "description" to project.description, + "url" to "https://geysermc.org", + "author" to "GeyserMC" + ) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index dfbf9837f..7f700a2f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,55 +1,5 @@ plugins { - `java-library` // Ensure AP works in eclipse (no effect on other IDEs) eclipse - id("geyser.build-logic") - alias(libs.plugins.lombok) apply false + id("geyser.base-conventions") } - -allprojects { - group = properties["group"] as String + "." + properties["id"] as String - version = properties["version"] as String - description = properties["description"] as String -} - -val basePlatforms = setOf( - projects.bungeecord, - projects.spigot, - projects.standalone, - projects.velocity, - projects.viaproxy -).map { it.dependencyProject } - -val moddedPlatforms = setOf( - projects.fabric, - projects.neoforge, - projects.mod -).map { it.dependencyProject } - -val modrinthPlatforms = setOf( - projects.bungeecord, - projects.fabric, - projects.neoforge, - projects.spigot, - projects.velocity -).map { it.dependencyProject } - -subprojects { - apply { - plugin("java-library") - plugin("io.freefair.lombok") - plugin("geyser.build-logic") - } - - when (this) { - in basePlatforms -> plugins.apply("geyser.platform-conventions") - in moddedPlatforms -> plugins.apply("geyser.modded-conventions") - else -> plugins.apply("geyser.base-conventions") - } - - // Not combined with platform-conventions as that also contains - // platforms which we cant publish to modrinth - if (modrinthPlatforms.contains(this)) { - plugins.apply("geyser.modrinth-uploading-conventions") - } -} \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts index efba08c8d..166ffe9f5 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("geyser.publish-conventions") + id("io.freefair.lombok") } dependencies { diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index 406204759..1a92f9c40 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * 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 @@ -39,15 +39,19 @@ public enum DeviceOs { OSX("macOS"), AMAZON("Amazon"), GEARVR("Gear VR"), - HOLOLENS("Hololens"), + @Deprecated HOLOLENS("Hololens"), UWP("Windows"), WIN32("Windows x86"), DEDICATED("Dedicated"), - TVOS("Apple TV"), - PS4("PS4"), + @Deprecated TVOS("Apple TV"), + /** + * This is for all PlayStation platforms not just PS4 + */ + PS4("PlayStation"), NX("Switch"), - XBOX("Xbox One"), - WINDOWS_PHONE("Windows Phone"); + XBOX("Xbox"), + @Deprecated WINDOWS_PHONE("Windows Phone"), + LINUX("Linux"); private static final DeviceOs[] VALUES = values(); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1d1794cf7..b0ea5fdf6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,6 +3,7 @@ plugins { idea alias(libs.plugins.blossom) id("geyser.publish-conventions") + id("io.freefair.lombok") } dependencies { @@ -25,11 +26,10 @@ dependencies { api(libs.bundles.protocol) - api(libs.mcauthlib) + api(libs.minecraftauth) api(libs.mcprotocollib) { exclude("io.netty", "netty-all") - exclude("com.github.GeyserMC", "packetlib") - exclude("com.github.GeyserMC", "mcauthlib") + exclude("net.raphimc", "MinecraftAuth") } implementation(libs.raknet) { @@ -52,12 +52,16 @@ dependencies { // Adventure text serialization api(libs.bundles.adventure) + // command library + api(libs.cloud.core) + api(libs.erosion.common) { isTransitive = false } // Test testImplementation(libs.junit) + testImplementation(libs.mockito) // Annotation Processors compileOnly(projects.ap) @@ -101,9 +105,6 @@ sourceSets { } } -fun buildNumber(): Int = - (System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 - fun isDevBuild(branch: String, repository: String): Boolean { return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not() } @@ -137,7 +138,7 @@ inner class GitInfo { buildNumber = buildNumber() isDev = isDevBuild(branch, repository) - val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}") + val projectVersion = if (isDev) project.version else projectVersion(project) version = "$projectVersion ($gitVersion)" } } diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 588b25172..7f00075d8 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -35,11 +35,11 @@ public final class Constants { public static final String NEWS_PROJECT_NAME = "geyser"; 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"; + @Deprecated static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json"; + static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json"; public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom"; @@ -54,4 +54,4 @@ public final class Constants { } GLOBAL_API_WS_URI = wsUri; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index a9414d9d0..3063fa4f6 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -27,7 +27,7 @@ package org.geysermc.geyser; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; @@ -82,11 +82,11 @@ public interface GeyserBootstrap { GeyserLogger getGeyserLogger(); /** - * Returns the current CommandManager + * Returns the current CommandRegistry * - * @return The current CommandManager + * @return The current CommandRegistry */ - GeyserCommandManager getGeyserCommandManager(); + CommandRegistry getCommandRegistry(); /** * Returns the current PingPassthrough manager diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 88cc74691..9df1d2189 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import io.netty.channel.epoll.Epoll; import io.netty.util.NettyRuntime; import io.netty.util.concurrent.DefaultThreadFactory; @@ -38,6 +39,8 @@ import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession; +import net.raphimc.minecraftauth.step.msa.StepMsaToken; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -55,19 +58,25 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.event.lifecycle.*; +import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.erosion.UnixSocketClientListener; import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.impl.MinecraftVersionImpl; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.GeyserServer; @@ -85,7 +94,13 @@ import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.AssetUtils; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.util.Metrics; +import org.geysermc.geyser.util.MinecraftAuthLogger; +import org.geysermc.geyser.util.NewsHandler; +import org.geysermc.geyser.util.VersionCheckUtils; +import org.geysermc.geyser.util.WebUtils; import org.geysermc.mcprotocollib.network.tcp.TcpSession; import java.io.File; @@ -97,16 +112,24 @@ import java.net.UnknownHostException; import java.nio.file.Path; import java.security.Key; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @Getter -public class GeyserImpl implements GeyserApi { +public class GeyserImpl implements GeyserApi, EventRegistrar { public static final ObjectMapper JSON_MAPPER = new ObjectMapper() .enable(JsonParser.Feature.IGNORE_UNDEFINED) .enable(JsonParser.Feature.ALLOW_COMMENTS) @@ -133,12 +156,6 @@ public class GeyserImpl implements GeyserApi { private final SessionManager sessionManager = new SessionManager(); - /** - * This is used in GeyserConnect to stop the bedrock server binding to a port - */ - @Setter - private static boolean shouldStartListener = true; - private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; private NewsHandler newsHandler; @@ -161,7 +178,7 @@ public class GeyserImpl implements GeyserApi { private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; @Getter(AccessLevel.NONE) - private Map savedRefreshTokens; + private Map savedAuthChains; @Getter private static GeyserImpl instance; @@ -209,9 +226,7 @@ public class GeyserImpl implements GeyserApi { 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(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc")); logger.info(""); } logger.info("******************************************"); @@ -244,6 +259,9 @@ public class GeyserImpl implements GeyserApi { CompletableFuture.runAsync(AssetUtils::downloadAndRunClientJarTasks); }); + // Register our general permissions when possible + eventBus.subscribe(this, GeyserRegisterPermissionsEvent.class, Permissions::register); + startInstance(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -345,22 +363,6 @@ public class GeyserImpl implements GeyserApi { } } - String broadcastPort = System.getProperty("geyserBroadcastPort", ""); - if (!broadcastPort.isEmpty()) { - int parsedPort; - try { - parsedPort = Integer.parseInt(broadcastPort); - if (parsedPort < 1 || parsedPort > 65535) { - throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!"); - } - } catch (NumberFormatException e) { - logger.error(String.format("Invalid broadcast port: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); - parsedPort = config.getBedrock().port(); - } - config.getBedrock().setBroadcastPort(parsedPort); - logger.info("Broadcast port set from system property: " + parsedPort); - } - if (platformType != PlatformType.VIAPROXY) { boolean floodgatePresent = bootstrap.testFloodgatePluginPresent(); if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { @@ -375,6 +377,26 @@ public class GeyserImpl implements GeyserApi { } } + // Now that the Bedrock port may have been changed, also check the broadcast port (configurable on all platforms) + String broadcastPort = System.getProperty("geyserBroadcastPort", ""); + if (!broadcastPort.isEmpty()) { + try { + int parsedPort = Integer.parseInt(broadcastPort); + if (parsedPort < 1 || parsedPort > 65535) { + throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!"); + } + config.getBedrock().setBroadcastPort(parsedPort); + logger.info("Broadcast port set from system property: " + parsedPort); + } catch (NumberFormatException e) { + logger.error(String.format("Invalid broadcast port from system property: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); + } + } + + // It's set to 0 only if no system property or manual config value was set + if (config.getBedrock().broadcastPort() == 0) { + config.getBedrock().setBroadcastPort(config.getBedrock().port()); + } + String remoteAddress = config.getRemote().address(); // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { @@ -403,7 +425,7 @@ public class GeyserImpl implements GeyserApi { } CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); - DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether + BedrockDimension.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads"); if (bedrockThreadCount == null) { @@ -411,24 +433,27 @@ public class GeyserImpl implements GeyserApi { bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); } - if (shouldStartListener) { - this.geyserServer = new GeyserServer(this, bedrockThreadCount); - this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port())) - .whenComplete((avoid, throwable) -> { - if (throwable == null) { - logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().address(), - String.valueOf(config.getBedrock().port()))); + this.geyserServer = new GeyserServer(this, bedrockThreadCount); + this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port())) + .whenComplete((avoid, throwable) -> { + String address = config.getBedrock().address(); + String port = String.valueOf(config.getBedrock().port()); // otherwise we get commas + + if (throwable == null) { + if ("0.0.0.0".equals(address)) { + // basically just hide it in the log because some people get confused and try to change it + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start.ip_suppressed", port)); } else { - String address = config.getBedrock().address(); - int port = config.getBedrock().port(); - logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port))); - if (!"0.0.0.0".equals(address)) { - logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN)); - logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN)); - } + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", address, port)); } - }).join(); - } + } else { + logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, port)); + if (!"0.0.0.0".equals(address)) { + logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN)); + logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN)); + } + } + }).join(); if (config.getRemote().authType() == AuthType.FLOODGATE) { try { @@ -534,37 +559,84 @@ public class GeyserImpl implements GeyserApi { if (config.getRemote().authType() == AuthType.ONLINE) { // May be written/read to on multiple threads from each GeyserSession as well as writing the config - savedRefreshTokens = new ConcurrentHashMap<>(); + savedAuthChains = new ConcurrentHashMap<>(); - File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); - if (tokensFile.exists()) { + // TODO Remove after a while - just a migration help + //noinspection deprecation + File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + if (refreshTokensFile.exists()) { + logger.info("Migrating refresh tokens to auth chains..."); + TypeReference> type = new TypeReference<>() { }; + Map refreshTokens = null; + try { + refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type); + } catch (IOException e) { + // ignored - we'll just delete this file :)) + } + + if (refreshTokens != null) { + List validUsers = config.getSavedUserLogins(); + final Gson gson = new Gson(); + for (Map.Entry entry : refreshTokens.entrySet()) { + String user = entry.getKey(); + if (!validUsers.contains(user)) { + continue; + } + + // Migrate refresh tokens to auth chains + try { + StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10); + StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput( + MinecraftAuthLogger.INSTANCE, + PendingMicrosoftAuthentication.AUTH_CLIENT, + new StepMsaToken.RefreshToken(entry.getValue()) + ); + + String authChain = gson.toJson(javaSession.toJson(fullJavaSession)); + savedAuthChains.put(user, authChain); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " + + "They will need to sign in the next time they join Geyser."); + } + + // Ensure the new additions are written to the file + scheduleAuthChainsWrite(); + } + } + + // Finally: Delete it. Goodbye! + refreshTokensFile.delete(); + } + + File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); + if (authChainsFile.exists()) { TypeReference> type = new TypeReference<>() { }; - Map refreshTokenFile = null; + Map authChainFile = null; try { - refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type); + authChainFile = JSON_MAPPER.readValue(authChainsFile, type); } catch (IOException e) { logger.error("Cannot load saved user tokens!", e); } - if (refreshTokenFile != null) { + if (authChainFile != null) { List validUsers = config.getSavedUserLogins(); boolean doWrite = false; - for (Map.Entry entry : refreshTokenFile.entrySet()) { + for (Map.Entry entry : authChainFile.entrySet()) { String user = entry.getKey(); if (!validUsers.contains(user)) { // Perform a write to this file to purge the now-unused name doWrite = true; continue; } - savedRefreshTokens.put(user, entry.getValue()); + savedAuthChains.put(user, entry.getValue()); } if (doWrite) { - scheduleRefreshTokensWrite(); + scheduleAuthChainsWrite(); } } } } else { - savedRefreshTokens = null; + savedAuthChains = null; } newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); @@ -645,16 +717,11 @@ public class GeyserImpl implements GeyserApi { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done")); } - scheduledThread.shutdown(); - geyserServer.shutdown(); - if (skinUploader != null) { - skinUploader.close(); - } - newsHandler.shutdown(); - - if (this.erosionUnixListener != null) { - this.erosionUnixListener.close(); - } + runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown); + runIfNonNull(geyserServer, GeyserServer::shutdown); + runIfNonNull(skinUploader, FloodgateSkinUploader::close); + runIfNonNull(newsHandler, NewsHandler::shutdown); + runIfNonNull(erosionUnixListener, UnixSocketClientListener::close); Registries.RESOURCE_PACKS.get().clear(); @@ -666,7 +733,6 @@ public class GeyserImpl implements GeyserApi { if (isEnabled) { this.disable(); } - this.commandManager().getCommands().clear(); // Disable extensions, fire the shutdown event this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); @@ -704,9 +770,12 @@ public class GeyserImpl implements GeyserApi { return this.extensionManager; } + /** + * @return the current CommandRegistry in use. The instance may change over the lifecycle of the Geyser runtime. + */ @NonNull - public GeyserCommandManager commandManager() { - return this.bootstrap.getGeyserCommandManager(); + public CommandRegistry commandRegistry() { + return this.bootstrap.getCommandRegistry(); } @Override @@ -816,11 +885,11 @@ public class GeyserImpl implements GeyserApi { } @Nullable - public String refreshTokenFor(@NonNull String bedrockName) { - return savedRefreshTokens.get(bedrockName); + public String authChainFor(@NonNull String bedrockName) { + return savedAuthChains.get(bedrockName); } - public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) { + public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) { if (!getConfig().getSavedUserLogins().contains(bedrockName)) { // Do not save this login return; @@ -828,20 +897,26 @@ public class GeyserImpl implements GeyserApi { // We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken // refreshes the token for us - if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) { - scheduleRefreshTokensWrite(); + if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) { + scheduleAuthChainsWrite(); } } - private void scheduleRefreshTokensWrite() { + private void runIfNonNull(T nullable, Consumer consumer) { + if (nullable != null) { + consumer.accept(nullable); + } + } + + private void scheduleAuthChainsWrite() { scheduledThread.execute(() -> { // Ensure all writes are handled on the same thread - File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); TypeReference> type = new TypeReference<>() { }; - try (FileWriter writer = new FileWriter(savedTokens)) { + try (FileWriter writer = new FileWriter(savedAuthChains)) { JSON_MAPPER.writerFor(type) .withDefaultPrettyPrinter() - .writeValue(writer, savedRefreshTokens); + .writeValue(writer, this.savedAuthChains); } catch (IOException e) { getLogger().error("Unable to write saved refresh tokens!", e); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index aa79e3630..f408de29c 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; +import java.util.UUID; public interface GeyserLogger extends GeyserCommandSource { @@ -129,6 +130,11 @@ public interface GeyserLogger extends GeyserCommandSource { return true; } + @Override + default @Nullable UUID playerUuid() { + return null; + } + @Override default boolean hasPermission(String permission) { return true; diff --git a/core/src/main/java/org/geysermc/geyser/Permissions.java b/core/src/main/java/org/geysermc/geyser/Permissions.java new file mode 100644 index 000000000..b65a5af7a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/Permissions.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.TriState; + +import java.util.HashMap; +import java.util.Map; + +/** + * Permissions related to Geyser + */ +public final class Permissions { + private static final Map PERMISSIONS = new HashMap<>(); + + public static final String CHECK_UPDATE = register("geyser.update"); + public static final String SERVER_SETTINGS = register("geyser.settings.server"); + public static final String SETTINGS_GAMERULES = register("geyser.settings.gamerules"); + + private Permissions() { + //no + } + + private static String register(String permission) { + return register(permission, TriState.NOT_SET); + } + + @SuppressWarnings("SameParameterValue") + private static String register(String permission, TriState permissionDefault) { + PERMISSIONS.put(permission, permissionDefault); + return permission; + } + + public static void register(GeyserRegisterPermissionsEvent event) { + for (Map.Entry permission : PERMISSIONS.entrySet()) { + event.register(permission.getKey(), permission.getValue()); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java new file mode 100644 index 000000000..54681abea --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -0,0 +1,302 @@ +/* + * 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.command; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; +import org.geysermc.geyser.command.defaults.AdvancementsCommand; +import org.geysermc.geyser.command.defaults.ConnectionTestCommand; +import org.geysermc.geyser.command.defaults.DumpCommand; +import org.geysermc.geyser.command.defaults.ExtensionsCommand; +import org.geysermc.geyser.command.defaults.HelpCommand; +import org.geysermc.geyser.command.defaults.ListCommand; +import org.geysermc.geyser.command.defaults.OffhandCommand; +import org.geysermc.geyser.command.defaults.PingCommand; +import org.geysermc.geyser.command.defaults.ReloadCommand; +import org.geysermc.geyser.command.defaults.SettingsCommand; +import org.geysermc.geyser.command.defaults.StatisticsCommand; +import org.geysermc.geyser.command.defaults.StopCommand; +import org.geysermc.geyser.command.defaults.VersionCommand; +import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; +import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.Command.Builder; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static org.geysermc.geyser.command.GeyserCommand.DEFAULT_ROOT_COMMAND; + +/** + * Registers all built-in and extension commands to the given Cloud CommandManager. + *

+ * Fires {@link GeyserDefineCommandsEvent} upon construction. + *

+ * Subscribes to {@link GeyserRegisterPermissionsEvent} upon construction. + * A new instance of this class (that registers the same permissions) shouldn't be created until the previous + * instance is unsubscribed from the event. + */ +public class CommandRegistry implements EventRegistrar { + + private static final String GEYSER_ROOT_PERMISSION = "geyser.command"; + + protected final GeyserImpl geyser; + private final CommandManager cloud; + private final boolean applyRootPermission; + + /** + * Map of Geyser subcommands to their Commands + */ + private final Map commands = new Object2ObjectOpenHashMap<>(13); + + /** + * Map of Extensions to maps of their subcommands + */ + private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); + + /** + * Map of root commands (that are for extensions) to Extensions + */ + private final Map extensionRootCommands = new Object2ObjectOpenHashMap<>(0); + + /** + * Map containing only permissions that have been registered with a default value + */ + protected final Map permissionDefaults = new Object2ObjectOpenHashMap<>(13); + + /** + * Creates a new CommandRegistry. Does apply a root permission. If undesired, use the other constructor. + */ + public CommandRegistry(GeyserImpl geyser, CommandManager cloud) { + this(geyser, cloud, true); + } + + /** + * Creates a new CommandRegistry + * + * @param geyser the Geyser instance + * @param cloud the cloud command manager to register commands to + * @param applyRootPermission true if this registry should apply a permission to Geyser and Extension root commands. + * This currently exists because we want to retain the root command permission for Spigot, + * but don't want to add it yet to platforms like Velocity where we cannot natively + * specify a permission default. Doing so will break setups as players would suddenly not + * have the required permission to execute any Geyser commands. + */ + public CommandRegistry(GeyserImpl geyser, CommandManager cloud, boolean applyRootPermission) { + this.geyser = geyser; + this.cloud = cloud; + this.applyRootPermission = applyRootPermission; + + // register our custom exception handlers + ExceptionHandlers.register(cloud); + + // begin command registration + HelpCommand help = new HelpCommand(DEFAULT_ROOT_COMMAND, "help", "geyser.commands.help.desc", "geyser.command.help", this.commands); + registerBuiltInCommand(help); + buildRootCommand(GEYSER_ROOT_PERMISSION, help); // build root and delegate to help + + registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerBuiltInCommand(new OffhandCommand("offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerBuiltInCommand(new SettingsCommand("settings", "geyser.commands.settings.desc", "geyser.command.settings")); + registerBuiltInCommand(new StatisticsCommand("statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); + registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); + registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest")); + registerBuiltInCommand(new PingCommand("ping", "geyser.commands.ping.desc", "geyser.command.ping")); + if (this.geyser.getPlatformType() == PlatformType.STANDALONE) { + registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + } + + if (!this.geyser.extensionManager().extensions().isEmpty()) { + registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); + } + + GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) { + + @Override + public void register(@NonNull Command command) { + if (!(command instanceof GeyserExtensionCommand extensionCommand)) { + throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?"); + } + + registerExtensionCommand(extensionCommand.extension(), extensionCommand); + } + }; + this.geyser.eventBus().fire(defineCommandsEvent); + + // Stuff that needs to be done on a per-extension basis + for (Map.Entry> entry : this.extensionCommands.entrySet()) { + Extension extension = entry.getKey(); + + // Register this extension's root command + extensionRootCommands.put(extension.rootCommand(), extension); + + // Register help commands for all extensions with commands + String id = extension.description().id(); + HelpCommand extensionHelp = new HelpCommand( + extension.rootCommand(), + "help", + "geyser.commands.exthelp.desc", + "geyser.command.exthelp." + id, + entry.getValue()); // commands it provides help for + + registerExtensionCommand(extension, extensionHelp); + buildRootCommand("geyser.extension." + id + ".command", extensionHelp); + } + + // Wait for the right moment (depends on the platform) to register permissions. + geyser.eventBus().subscribe(this, GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions); + } + + /** + * @return an immutable view of the root commands registered to this command registry + */ + @NonNull + public Collection rootCommands() { + return cloud.rootCommands(); + } + + /** + * For internal Geyser commands + */ + private void registerBuiltInCommand(GeyserCommand command) { + register(command, this.commands); + } + + private void registerExtensionCommand(@NonNull Extension extension, @NonNull GeyserCommand command) { + register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); + } + + protected void register(GeyserCommand command, Map commands) { + String root = command.rootCommand(); + String name = command.name(); + if (commands.containsKey(name)) { + throw new IllegalArgumentException("Command with root=%s, name=%s already registered".formatted(root, name)); + } + + command.register(cloud); + commands.put(name, command); + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", root + " " + name)); + + for (String alias : command.aliases()) { + commands.put(alias, command); + } + + String permission = command.permission(); + TriState defaultValue = command.permissionDefault(); + if (!permission.isBlank() && defaultValue != null) { + + TriState existingDefault = permissionDefaults.get(permission); + // Extensions might be using the same permission for two different commands + if (existingDefault != null && existingDefault != defaultValue) { + geyser.getLogger().debug("Overriding permission default %s:%s with %s".formatted(permission, existingDefault, defaultValue)); + } + + permissionDefaults.put(permission, defaultValue); + } + } + + /** + * Registers a root command to cloud that delegates to the given help command. + * The name of this root command is the root of the given help command. + * + * @param permission the permission of the root command. currently, it may or may not be + * applied depending on the platform. see below. + * @param help the help command to delegate to + */ + private void buildRootCommand(String permission, HelpCommand help) { + Builder builder = cloud.commandBuilder(help.rootCommand()); + + if (applyRootPermission) { + builder = builder.permission(permission); + permissionDefaults.put(permission, TriState.TRUE); + } + + cloud.command(builder.handler(context -> { + GeyserCommandSource source = context.sender(); + if (!source.hasPermission(help.permission())) { + // delegate if possible - otherwise we have nothing else to offer the user. + source.sendLocaleString(ExceptionHandlers.PERMISSION_FAIL_LANG_KEY); + return; + } + help.execute(source); + })); + } + + protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) { + for (Map.Entry permission : permissionDefaults.entrySet()) { + event.register(permission.getKey(), permission.getValue()); + } + } + + public boolean hasPermission(GeyserCommandSource source, String permission) { + // Handle blank permissions ourselves, as cloud only handles empty ones + return permission.isBlank() || cloud.hasPermission(source, permission); + } + + /** + * Returns the description of the given command + * + * @param command the root command node + * @param locale the ideal locale that the description should be in + * @return a description if found, otherwise an empty string. The locale is not guaranteed. + */ + @NonNull + public String description(@NonNull String command, @NonNull String locale) { + if (command.equals(DEFAULT_ROOT_COMMAND)) { + return GeyserLocale.getPlayerLocaleString("geyser.command.root.geyser", locale); + } + + Extension extension = extensionRootCommands.get(command); + if (extension != null) { + return GeyserLocale.getPlayerLocaleString("geyser.command.root.extension", locale, extension.name()); + } + return ""; + } + + /** + * Dispatches a command into cloud and handles any thrown exceptions. + * This method may or may not be blocking, depending on the {@link ExecutionCoordinator} in use by cloud. + */ + public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { + cloud.commandExecutor().executeCommand(source, command); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java new file mode 100644 index 000000000..1fa5871e0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.SenderMapper; + +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Converts {@link GeyserCommandSource}s to the server's command sender type (and back) in a lenient manner. + * + * @param senderType class of the server command sender type + * @param playerLookup function for looking up a player command sender by UUID + * @param consoleProvider supplier of the console command sender + * @param commandSourceLookup supplier of the platform implementation of the {@link GeyserCommandSource} + * @param server command sender type + */ +public record CommandSourceConverter(Class senderType, + Function playerLookup, + Supplier consoleProvider, + Function commandSourceLookup +) implements SenderMapper { + + /** + * Creates a new CommandSourceConverter for a server platform + * in which the player type is not a command sender type, and must be mapped. + * + * @param senderType class of the command sender type + * @param playerLookup function for looking up a player by UUID + * @param senderLookup function for converting a player to a command sender + * @param consoleProvider supplier of the console command sender + * @param commandSourceLookup supplier of the platform implementation of {@link GeyserCommandSource} + * @return a new CommandSourceConverter + * @param

server player type + * @param server command sender type + */ + public static CommandSourceConverter layered(Class senderType, + Function playerLookup, + Function senderLookup, + Supplier consoleProvider, + Function commandSourceLookup) { + Function lookup = uuid -> { + P player = playerLookup.apply(uuid); + if (player == null) { + return null; + } + return senderLookup.apply(player); + }; + return new CommandSourceConverter<>(senderType, lookup, consoleProvider, commandSourceLookup); + } + + @Override + public @NonNull GeyserCommandSource map(@NonNull S base) { + return commandSourceLookup.apply(base); + } + + @Override + public @NonNull S reverse(GeyserCommandSource source) throws IllegalArgumentException { + Object handle = source.handle(); + if (senderType.isInstance(handle)) { + return senderType.cast(handle); // one of the server platform implementations + } + + if (source.isConsole()) { + return consoleProvider.get(); // one of the loggers + } + + if (!(source instanceof GeyserSession)) { + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source); + Thread.dumpStack(); + } + } + + // Ideally lookup should only be necessary for GeyserSession + UUID uuid = source.playerUuid(); + if (uuid != null) { + return playerLookup.apply(uuid); + } + + throw new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid())); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/ExceptionHandlers.java b/core/src/main/java/org/geysermc/geyser/command/ExceptionHandlers.java new file mode 100644 index 000000000..45657a596 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/ExceptionHandlers.java @@ -0,0 +1,129 @@ +/* + * 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.command; + +import io.leangen.geantyref.GenericTypeReflector; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.text.MinecraftLocale; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.exception.ArgumentParseException; +import org.incendo.cloud.exception.CommandExecutionException; +import org.incendo.cloud.exception.InvalidCommandSenderException; +import org.incendo.cloud.exception.InvalidSyntaxException; +import org.incendo.cloud.exception.NoPermissionException; +import org.incendo.cloud.exception.NoSuchCommandException; +import org.incendo.cloud.exception.handling.ExceptionController; + +import java.lang.reflect.Type; +import java.util.function.BiConsumer; + +/** + * Geyser's exception handlers for command execution with Cloud. + * Overrides Cloud's defaults so that messages can be customized to our liking: localization, etc. + */ +final class ExceptionHandlers { + + final static String PERMISSION_FAIL_LANG_KEY = "geyser.command.permission_fail"; + + private final ExceptionController controller; + + private ExceptionHandlers(ExceptionController controller) { + this.controller = controller; + } + + /** + * Clears the existing handlers that are registered to the given command manager, and repopulates them. + * + * @param manager the manager whose exception handlers will be modified + */ + static void register(CommandManager manager) { + new ExceptionHandlers(manager.exceptionController()).register(); + } + + private void register() { + // Yeet the default exception handlers that cloud provides so that we can perform localization. + controller.clearHandlers(); + + registerExceptionHandler(InvalidSyntaxException.class, + (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())); + + registerExceptionHandler(InvalidCommandSenderException.class, (src, e) -> { + // We currently don't use cloud sender type requirements anywhere. + // This can be implemented better in the future if necessary. + Type type = e.requiredSenderTypes().iterator().next(); // just grab the first + String typeString = GenericTypeReflector.getTypeName(type); + src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), typeString); + }); + + registerExceptionHandler(NoPermissionException.class, ExceptionHandlers::handleNoPermission); + + registerExceptionHandler(NoSuchCommandException.class, + (src, e) -> src.sendLocaleString("geyser.command.not_found")); + + registerExceptionHandler(ArgumentParseException.class, + (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())); + + registerExceptionHandler(CommandExecutionException.class, + (src, e) -> handleUnexpectedThrowable(src, e.getCause())); + + registerExceptionHandler(Throwable.class, + (src, e) -> handleUnexpectedThrowable(src, e.getCause())); + } + + private void registerExceptionHandler(Class type, BiConsumer handler) { + controller.registerHandler(type, context -> handler.accept(context.context().sender(), context.exception())); + } + + private static void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) { + // custom handling if the source can't use the command because of additional requirements + if (exception.permissionResult() instanceof GeyserPermission.Result result) { + if (result.meta() == GeyserPermission.Result.Meta.NOT_BEDROCK) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); + return; + } + if (result.meta() == GeyserPermission.Result.Meta.NOT_PLAYER) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); + return; + } + } else { + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Expected a GeyserPermission.Result for %s but instead got %s from %s".formatted(exception.currentChain(), exception.permissionResult(), exception.missingPermission())); + } + } + + // Result.NO_PERMISSION or generic permission failure + source.sendLocaleString(PERMISSION_FAIL_LANG_KEY); + } + + private static void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) { + source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key + GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 47d57e73f..3cc05ca0c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -25,65 +25,187 @@ package org.geysermc.geyser.command; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.CommandDescription; +import org.jetbrains.annotations.Contract; import java.util.Collections; import java.util.List; -@Accessors(fluent = true) -@Getter -@RequiredArgsConstructor -public abstract class GeyserCommand implements Command { +public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { + public static final String DEFAULT_ROOT_COMMAND = "geyser"; + + /** + * The second literal of the command. Note: the first literal is {@link #rootCommand()}. + */ + @NonNull + private final String name; - protected final String name; /** * The description of the command - will attempt to be translated. */ - protected final String description; - protected final String permission; - - private List aliases = Collections.emptyList(); - - public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args); + @NonNull + private final String description; /** - * If false, hides the command from being shown on the Geyser Standalone GUI. - * - * @return true if the command can be run on the server console - */ - @Override - public boolean isExecutableOnConsole() { - return true; - } - - /** - * Used in the GUI to know what subcommands can be run - * - * @return a list of all possible subcommands, or empty if none. + * The permission node required to run the command, or blank if not required. */ @NonNull - @Override - public List subCommands() { - return Collections.emptyList(); + private final String permission; + + /** + * The default value of the permission node. + * A null value indicates that the permission node should not be registered whatsoever. + * See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings. + */ + @Nullable + private final TriState permissionDefault; + + /** + * True if this command can be executed by players + */ + private final boolean playerOnly; + + /** + * True if this command can only be run by bedrock players + */ + private final boolean bedrockOnly; + + /** + * The aliases of the command {@link #name}. This should not be modified after construction. + */ + protected List aliases = Collections.emptyList(); + + public GeyserCommand(@NonNull String name, @NonNull String description, + @NonNull String permission, @Nullable TriState permissionDefault, + boolean playerOnly, boolean bedrockOnly) { + + if (name.isBlank()) { + throw new IllegalArgumentException("Command cannot be null or blank!"); + } + if (permission.isBlank()) { + // Cloud treats empty permissions as available to everyone, but not blank permissions. + // When registering commands, we must convert ALL whitespace permissions into empty ones, + // because we cannot override permission checks that Cloud itself performs + permission = ""; + permissionDefault = null; + } + + this.name = name; + this.description = description; + this.permission = permission; + this.permissionDefault = permissionDefault; + + if (bedrockOnly && !playerOnly) { + throw new IllegalArgumentException("Command cannot be bedrockOnly if it is not playerOnly"); + } + + this.playerOnly = playerOnly; + this.bedrockOnly = bedrockOnly; } - public void setAliases(List aliases) { - this.aliases = aliases; + public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault) { + this(name, description, permission, permissionDefault, false, false); + } + + @NonNull + @Override + public final String name() { + return name; + } + + @NonNull + @Override + public final String description() { + return description; + } + + @NonNull + @Override + public final String permission() { + return permission; + } + + @Nullable + public final TriState permissionDefault() { + return permissionDefault; + } + + @Override + public final boolean isPlayerOnly() { + return playerOnly; + } + + @Override + public final boolean isBedrockOnly() { + return bedrockOnly; + } + + @NonNull + @Override + public final List aliases() { + return Collections.unmodifiableList(aliases); } /** - * Used for permission defaults on server implementations. - * - * @return if this command is designated to be used only by server operators. + * @return the first (literal) argument of this command, which comes before {@link #name()}. */ - @Override - public boolean isSuggestedOpOnly() { - return false; + public String rootCommand() { + return DEFAULT_ROOT_COMMAND; } -} \ No newline at end of file + + /** + * Returns a {@link org.incendo.cloud.permission.Permission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}. + * + * @param manager the manager to be used for permission node checking + * @return a permission that will properly restrict usage of this command + */ + public final GeyserPermission commandPermission(CommandManager manager) { + return new GeyserPermission(bedrockOnly, playerOnly, permission, manager); + } + + /** + * Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it. + * A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isPlayerOnly()} + * is applied. The Applicable from {@link #meta()} is also applied to the builder. + */ + @Contract(value = "_ -> new", pure = true) + public final Command.Builder baseBuilder(CommandManager manager) { + return manager.commandBuilder(rootCommand()) + .literal(name, aliases.toArray(new String[0])) + .permission(commandPermission(manager)) + .apply(meta()); + } + + /** + * @return an Applicable that applies this command's description + */ + protected Command.Builder.Applicable meta() { + return builder -> builder + .commandDescription(CommandDescription.commandDescription(GeyserLocale.getLocaleStringLog(description))); // used in cloud-bukkit impl + } + + /** + * Registers this command to the given command manager. + * This method may be overridden to register more than one command. + *

+ * The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)} + * applied as the handler is registered to the manager. + */ + public void register(CommandManager manager) { + manager.command(baseBuilder(manager).handler(this::execute)); + } + + /** + * Executes this command + * @param context the context with which this command should be executed + */ + public abstract void execute(CommandContext context); +} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java deleted file mode 100644 index 37d2ef4fb..000000000 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java +++ /dev/null @@ -1,98 +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.command; - -import lombok.AllArgsConstructor; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.session.GeyserSession; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Represents helper functions for listening to {@code /geyser} or {@code /geyserext} commands. - */ -@AllArgsConstructor -public class GeyserCommandExecutor { - - protected final GeyserImpl geyser; - private final Map commands; - - public GeyserCommand getCommand(String label) { - return (GeyserCommand) commands.get(label); - } - - @Nullable - public GeyserSession getGeyserSession(GeyserCommandSource sender) { - if (sender.isConsole()) { - return null; - } - - for (GeyserSession session : geyser.getSessionManager().getSessions().values()) { - if (sender.name().equals(session.getPlayerEntity().getUsername())) { - return session; - } - } - return null; - } - - /** - * Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender. - * - * @param sender The command sender to receive the tab complete suggestions. - * If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions. - * If the command sender is not a bedrock player, bedrock commands will not be shown. - * If the command sender does not have the permission for a given command, the command will not be shown. - * @return A list of command names to include in the tab complete - */ - public List tabComplete(GeyserCommandSource sender) { - if (getGeyserSession(sender) != null) { - // Bedrock doesn't get tab completions or argument suggestions - return Collections.emptyList(); - } - - List availableCommands = new ArrayList<>(); - - // Only show commands they have permission to use - for (Map.Entry entry : commands.entrySet()) { - Command geyserCommand = entry.getValue(); - if (sender.hasPermission(geyserCommand.permission())) { - if (geyserCommand.isBedrockOnly()) { - // Don't show commands the JE player can't run - continue; - } - - availableCommands.add(entry.getKey()); - } - } - - return availableCommands; - } -} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java deleted file mode 100644 index 72ed22381..000000000 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ /dev/null @@ -1,330 +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.command; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandExecutor; -import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; -import org.geysermc.geyser.api.extension.Extension; -import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; -import org.geysermc.geyser.command.defaults.AdvancementsCommand; -import org.geysermc.geyser.command.defaults.ConnectionTestCommand; -import org.geysermc.geyser.command.defaults.DumpCommand; -import org.geysermc.geyser.command.defaults.ExtensionsCommand; -import org.geysermc.geyser.command.defaults.HelpCommand; -import org.geysermc.geyser.command.defaults.ListCommand; -import org.geysermc.geyser.command.defaults.OffhandCommand; -import org.geysermc.geyser.command.defaults.ReloadCommand; -import org.geysermc.geyser.command.defaults.SettingsCommand; -import org.geysermc.geyser.command.defaults.StatisticsCommand; -import org.geysermc.geyser.command.defaults.StopCommand; -import org.geysermc.geyser.command.defaults.VersionCommand; -import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; -import org.geysermc.geyser.extension.command.GeyserExtensionCommand; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -@RequiredArgsConstructor -public class GeyserCommandManager { - - @Getter - private final Map commands = new Object2ObjectOpenHashMap<>(12); - private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); - - private final GeyserImpl geyser; - - public void init() { - registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); - registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); - registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); - registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); - registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); - registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); - registerBuiltInCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); - registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); - registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); - registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest")); - if (this.geyser.getPlatformType() == PlatformType.STANDALONE) { - registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); - } - - if (!this.geyser.extensionManager().extensions().isEmpty()) { - registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); - } - - GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) { - - @Override - public void register(@NonNull Command command) { - if (!(command instanceof GeyserExtensionCommand extensionCommand)) { - throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?"); - } - - registerExtensionCommand(extensionCommand.extension(), extensionCommand); - } - }; - - this.geyser.eventBus().fire(defineCommandsEvent); - - // Register help commands for all extensions with commands - for (Map.Entry> entry : this.extensionCommands.entrySet()) { - String id = entry.getKey().description().id(); - registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue())); - } - } - - /** - * For internal Geyser commands - */ - public void registerBuiltInCommand(GeyserCommand command) { - register(command, this.commands); - } - - public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) { - register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); - } - - private void register(Command command, Map commands) { - commands.put(command.name(), command); - geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); - - if (command.aliases().isEmpty()) { - return; - } - - for (String alias : command.aliases()) { - commands.put(alias, command); - } - } - - @NonNull - public Map commands() { - return Collections.unmodifiableMap(this.commands); - } - - @NonNull - public Map> extensionCommands() { - return Collections.unmodifiableMap(this.extensionCommands); - } - - public boolean runCommand(GeyserCommandSource sender, String command) { - Extension extension = null; - for (Extension loopedExtension : this.extensionCommands.keySet()) { - if (command.startsWith(loopedExtension.description().id() + " ")) { - extension = loopedExtension; - break; - } - } - - if (!command.startsWith("geyser ") && extension == null) { - return false; - } - - command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", ""); - String label; - String[] args; - - if (!command.contains(" ")) { - label = command.toLowerCase(Locale.ROOT); - args = new String[0]; - } else { - label = command.substring(0, command.indexOf(" ")).toLowerCase(Locale.ROOT); - String argLine = command.substring(command.indexOf(" ") + 1); - args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; - } - - Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label); - if (cmd == null) { - sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); - return false; - } - - if (cmd instanceof GeyserCommand) { - if (sender instanceof GeyserSession) { - ((GeyserCommand) cmd).execute((GeyserSession) sender, sender, args); - } else { - if (!cmd.isBedrockOnly()) { - ((GeyserCommand) cmd).execute(null, sender, args); - } else { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); - } - } - } - - return true; - } - - /** - * Returns the description of the given command - * - * @param command Command to get the description for - * @return Command description - */ - public String description(String command) { - return ""; - } - - @RequiredArgsConstructor - public static class CommandBuilder implements Command.Builder { - private final Extension extension; - private Class sourceType; - private String name; - private String description = ""; - private String permission = ""; - private List aliases; - private boolean suggestedOpOnly = false; - private boolean executableOnConsole = true; - private List subCommands; - private boolean bedrockOnly; - private CommandExecutor executor; - - @Override - public Command.Builder source(@NonNull Class sourceType) { - this.sourceType = sourceType; - return this; - } - - public CommandBuilder name(@NonNull String name) { - this.name = name; - return this; - } - - public CommandBuilder description(@NonNull String description) { - this.description = description; - return this; - } - - public CommandBuilder permission(@NonNull String permission) { - this.permission = permission; - return this; - } - - public CommandBuilder aliases(@NonNull List aliases) { - this.aliases = aliases; - return this; - } - - @Override - public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { - this.suggestedOpOnly = suggestedOpOnly; - return this; - } - - public CommandBuilder executableOnConsole(boolean executableOnConsole) { - this.executableOnConsole = executableOnConsole; - return this; - } - - public CommandBuilder subCommands(@NonNull List subCommands) { - this.subCommands = subCommands; - return this; - } - - public CommandBuilder bedrockOnly(boolean bedrockOnly) { - this.bedrockOnly = bedrockOnly; - return this; - } - - public CommandBuilder executor(@NonNull CommandExecutor executor) { - this.executor = executor; - return this; - } - - @NonNull - public GeyserExtensionCommand build() { - if (this.name == null || this.name.isBlank()) { - throw new IllegalArgumentException("Command cannot be null or blank!"); - } - - if (this.sourceType == null) { - throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); - } - - return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { - - @SuppressWarnings("unchecked") - @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { - Class sourceType = CommandBuilder.this.sourceType; - CommandExecutor executor = CommandBuilder.this.executor; - if (sourceType.isInstance(session)) { - executor.execute((T) session, this, args); - return; - } - - if (sourceType.isInstance(sender)) { - executor.execute((T) sender, this, args); - return; - } - - GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); - } - - @NonNull - @Override - public List aliases() { - return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases; - } - - @Override - public boolean isSuggestedOpOnly() { - return CommandBuilder.this.suggestedOpOnly; - } - - @NonNull - @Override - public List subCommands() { - return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands; - } - - @Override - public boolean isBedrockOnly() { - return CommandBuilder.this.bedrockOnly; - } - - @Override - public boolean isExecutableOnConsole() { - return CommandBuilder.this.executableOnConsole; - } - }; - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index 88d148b11..c14767496 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -25,11 +25,16 @@ package org.geysermc.geyser.command; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import java.util.UUID; + /** * Implemented on top of any class that can send a command. * For example, it wraps around Spigot's CommandSender class. @@ -46,4 +51,29 @@ public interface GeyserCommandSource extends CommandSource { default void sendMessage(Component message) { sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); } + + default void sendLocaleString(String key, Object... values) { + sendMessage(GeyserLocale.getPlayerLocaleString(key, locale(), values)); + } + + default void sendLocaleString(String key) { + sendMessage(GeyserLocale.getPlayerLocaleString(key, locale())); + } + + @Override + default @Nullable GeyserSession connection() { + UUID uuid = playerUuid(); + if (uuid == null) { + return null; + } + return GeyserImpl.getInstance().connectionByUuid(uuid); + } + + /** + * @return the underlying platform handle that this source represents. + * If such handle doesn't exist, this itself is returned. + */ + default Object handle() { + return this; + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java new file mode 100644 index 000000000..1ee677e97 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import lombok.AllArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.permission.Permission; +import org.incendo.cloud.permission.PermissionResult; +import org.incendo.cloud.permission.PredicatePermission; + +import static org.geysermc.geyser.command.GeyserPermission.Result.Meta; + +@AllArgsConstructor +public class GeyserPermission implements PredicatePermission { + + /** + * True if this permission requires the command source to be a bedrock player + */ + private final boolean bedrockOnly; + + /** + * True if this permission requires the command source to be any player + */ + private final boolean playerOnly; + + /** + * The permission node that the command source must have + */ + private final String permission; + + /** + * The command manager to delegate permission checks to + */ + private final CommandManager manager; + + @Override + public @NonNull Result testPermission(@NonNull GeyserCommandSource source) { + if (bedrockOnly) { + if (source.connection() == null) { + return new Result(Meta.NOT_BEDROCK); + } + // connection is present -> it is a player -> playerOnly is irrelevant + } else if (playerOnly) { + if (source.isConsole()) { + return new Result(Meta.NOT_PLAYER); // must be a player but is console + } + } + + if (permission.isBlank() || manager.hasPermission(source, permission)) { + return new Result(Meta.ALLOWED); + } + return new Result(Meta.NO_PERMISSION); + } + + @Override + public @NonNull CloudKey key() { + return CloudKey.cloudKey(permission); + } + + /** + * Basic implementation of cloud's {@link PermissionResult} that delegates to the more informative {@link Meta}. + */ + public final class Result implements PermissionResult { + + private final Meta meta; + + private Result(Meta meta) { + this.meta = meta; + } + + public Meta meta() { + return meta; + } + + @Override + public boolean allowed() { + return meta == Meta.ALLOWED; + } + + @Override + public @NonNull Permission permission() { + return GeyserPermission.this; + } + + /** + * More detailed explanation of whether the permission check passed. + */ + public enum Meta { + + /** + * The source must be a bedrock player, but is not. + */ + NOT_BEDROCK, + + /** + * The source must be a player, but is not. + */ + NOT_PLAYER, + + /** + * The source does not have a required permission node. + */ + NO_PERMISSION, + + /** + * The source meets all requirements. + */ + ALLOWED + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 466515b3f..75b9252da 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -25,33 +25,32 @@ package org.geysermc.geyser.command.defaults; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; public class AdvancedTooltipsCommand extends GeyserCommand { + public AdvancedTooltipsCommand(String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.TRUE, true, true); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; - session.setAdvancedTooltips(!session.isAdvancedTooltips()); - session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); - session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); - } - } + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; + String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; + session.setAdvancedTooltips(!session.isAdvancedTooltips()); + session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW + + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + + " " + ChatColor.RESET + + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 28253433f..0cba28f33 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -25,29 +25,23 @@ package org.geysermc.geyser.command.defaults; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; public class AdvancementsCommand extends GeyserCommand { + public AdvancementsCommand(String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.TRUE, true, true); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - session.getAdvancementsCache().buildAndShowMenuForm(); - } - } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); + session.getAdvancementsCache().buildAndShowMenuForm(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 981c97595..d2066dba1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -26,90 +26,82 @@ package org.geysermc.geyser.command.defaults; import com.fasterxml.jackson.databind.JsonNode; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.CompletableFuture; +import static org.incendo.cloud.parser.standard.IntegerParser.integerParser; +import static org.incendo.cloud.parser.standard.StringParser.stringParser; + public class ConnectionTestCommand extends GeyserCommand { + /* * The MOTD is temporarily changed during the connection test. * This allows us to check if we are pinging the correct Geyser instance */ public static String CONNECTION_TEST_MOTD = null; - private final GeyserImpl geyser; + private static final String ADDRESS = "address"; + private static final String PORT = "port"; + private final GeyserImpl geyser; private final Random random = new Random(); public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { - // Only allow the console to create dumps on Geyser Standalone - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) + .required(ADDRESS, stringParser()) + .optional(PORT, integerParser(0, 65535)) + .handler(this::execute)); + } - if (args.length == 0) { - sender.sendMessage("Provide the server IP and port you are trying to test Bedrock connections for. Example: `test.geysermc.org:19132`"); - return; - } + @Override + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + String ipArgument = context.get(ADDRESS); + Integer portArgument = context.getOrDefault(PORT, null); // null if port was not specified // Replace "<" and ">" symbols if they are present to avoid the common issue of people including them - String[] fullAddress = args[0].replace("<", "").replace(">", "").split(":", 2); - - // Still allow people to not supply a port and fallback to 19132 - int port; - if (fullAddress.length == 2) { - try { - port = Integer.parseInt(fullAddress[1]); - } catch (NumberFormatException e) { - // can occur if e.g. "/geyser connectiontest : is ran - sender.sendMessage("Not a valid port! Specify a valid numeric port."); - return; - } - } else { - port = geyser.getConfig().getBedrock().broadcastPort(); - } - String ip = fullAddress[0]; + final String ip = ipArgument.replace("<", "").replace(">", ""); + final int port = portArgument != null ? portArgument : geyser.getConfig().getBedrock().broadcastPort(); // default bedrock port // Issue: people commonly checking placeholders if (ip.equals("ip")) { - sender.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check."); + source.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check."); return; } // Issue: checking 0.0.0.0 won't work if (ip.equals("0.0.0.0")) { - sender.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4."); + source.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4."); return; } // Issue: people testing local ip if (ip.equals("localhost") || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.")) { - sender.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP."); + source.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP."); return; } // Issue: port out of bounds if (port <= 0 || port >= 65535) { - sender.sendMessage("The port you specified is invalid! Please specify a valid port."); + source.sendMessage("The port you specified is invalid! Please specify a valid port."); return; } @@ -118,37 +110,37 @@ public class ConnectionTestCommand extends GeyserCommand { // Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing if (config.getBedrock().broadcastPort() == config.getBedrock().port()) { if (port != config.getBedrock().port()) { - if (fullAddress.length == 2) { - sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" + if (portArgument != null) { + source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" + config.getBedrock().port() + ")"); - sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config."); + source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config."); if (config.getBedrock().isCloneRemotePort()) { - sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); + source.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); } } else { - sender.sendMessage("You did not specify the port to check (add it with \":\"), " + + source.sendMessage("You did not specify the port to check (add it with \":\"), " + "and the default port 19132 does not match the port in your Geyser configuration (" + config.getBedrock().port() + ")!"); - sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`."); + source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`."); } } } else { if (config.getBedrock().broadcastPort() != port) { - sender.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration (" + source.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration (" + config.getBedrock().broadcastPort() + "). "); - sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on."); - sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config."); + source.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on."); + source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config."); } } // Issue: is the `bedrock` `address` in the config different? if (!config.getBedrock().address().equals("0.0.0.0")) { - sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); + source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); } // Issue: did someone turn on enable-proxy-protocol, and they didn't mean it? if (config.getBedrock().isEnableProxyProtocol()) { - sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + + source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + "Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled."); } @@ -157,14 +149,14 @@ public class ConnectionTestCommand extends GeyserCommand { // Issue: SRV record? String[] record = WebUtils.findSrvRecord(geyser, ip); if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) { - sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2] + source.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2] + ". If that fails, re-run this command with that address and port."); return; } // Issue: does Loopback need applying? if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) { - sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " + + source.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " + "See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer"); } @@ -178,7 +170,7 @@ public class ConnectionTestCommand extends GeyserCommand { String connectionTestMotd = "Geyser Connection Test " + randomStr; CONNECTION_TEST_MOTD = connectionTestMotd; - sender.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait..."); + source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait..."); JsonNode output; try { String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8); @@ -200,31 +192,31 @@ public class ConnectionTestCommand extends GeyserCommand { JsonNode pong = ping.get("pong"); String remoteMotd = pong.get("motd").asText(); if (!connectionTestMotd.equals(remoteMotd)) { - sender.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " + + source.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " + "Did you supply the correct IP and port of your server?"); - sendLinks(sender); + sendLinks(source); return; } if (ping.get("tcpFirst").asBoolean()) { - sender.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information."); - sendLinks(sender); + source.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information."); + sendLinks(source); return; } - sender.sendMessage("Your server is likely online and working as of " + when + "!"); - sendLinks(sender); + source.sendMessage("Your server is likely online and working as of " + when + "!"); + sendLinks(source); return; } - sender.sendMessage("Your server is likely unreachable from outside the network!"); + source.sendMessage("Your server is likely unreachable from outside the network!"); JsonNode message = output.get("message"); if (message != null && !message.asText().isEmpty()) { - sender.sendMessage("Got the error message: " + message.asText()); + source.sendMessage("Got the error message: " + message.asText()); } - sendLinks(sender); + sendLinks(source); } catch (Exception e) { - sender.sendMessage("An error occurred while trying to check your connection! Check the console for more information."); + source.sendMessage("An error occurred while trying to check your connection! Check the console for more information."); geyser.getLogger().error("Error while trying to check your connection!", e); } }); @@ -235,9 +227,4 @@ public class ConnectionTestCommand extends GeyserCommand { "https://wiki.geysermc.org/geyser/setup/"); sender.sendMessage("If that does not work, see " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on Discord: " + "https://discord.gg/geysermc"); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index b3fee375f..fc46f0108 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -29,42 +29,70 @@ import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.dump.DumpInfo; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.SuggestionProvider; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser; + public class DumpCommand extends GeyserCommand { + private static final String ARGUMENTS = "args"; + private static final Iterable SUGGESTIONS = List.of("full", "offline", "logs"); + private final GeyserImpl geyser; private static final ObjectMapper MAPPER = new ObjectMapper(); private static final String DUMP_URL = "https://dump.geysermc.org/"; public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); - + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - // Only allow the console to create dumps on Geyser Standalone - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) + .optional(ARGUMENTS, stringArrayParser(), SuggestionProvider.blockingStrings((ctx, input) -> { + // parse suggestions here + List inputs = new ArrayList<>(); + while (input.hasRemainingInput()) { + inputs.add(input.readStringSkipWhitespace()); + } + + if (inputs.size() <= 2) { + return SUGGESTIONS; // only `geyser dump` was typed (2 literals) + } + + // the rest of the input after `geyser dump` is for this argument + inputs = inputs.subList(2, inputs.size()); + + // don't suggest any words they have already typed + List suggestions = new ArrayList<>(); + SUGGESTIONS.forEach(suggestions::add); + suggestions.removeAll(inputs); + return suggestions; + })) + .handler(this::execute)); + } + + @Override + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + String[] args = context.getOrDefault(ARGUMENTS, new String[0]); boolean showSensitive = false; boolean offlineDump = false; @@ -75,25 +103,28 @@ public class DumpCommand extends GeyserCommand { case "full" -> showSensitive = true; case "offline" -> offlineDump = true; case "logs" -> addLog = true; + default -> context.sender().sendMessage("Invalid geyser dump option " + arg + "! Fallback to no arguments."); } } } AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale())); String dumpData; try { + DumpInfo dump = new DumpInfo(geyser, addLog); + if (offlineDump) { DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); // Make arrays easier to read prettyPrinter.indentArraysWith(new DefaultIndenter(" ", "\n")); - dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(new DumpInfo(addLog)); + dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(dump); } else { - dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog)); + dumpData = MAPPER.writeValueAsString(dump); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -101,21 +132,21 @@ public class DumpCommand extends GeyserCommand { String uploadedDumpUrl; if (offlineDump) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", source.locale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale())); String response; JsonNode responseNode; @@ -123,33 +154,22 @@ public class DumpCommand extends GeyserCommand { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); - if (!sender.isConsole()) { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl)); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + if (!source.isConsole()) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", source.name(), uploadedDumpUrl)); } } - - @NonNull - @Override - public List subCommands() { - return Arrays.asList("offline", "full", "logs"); - } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java index df33437d9..24881f2ca 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -25,14 +25,14 @@ package org.geysermc.geyser.command.defaults; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.Comparator; import java.util.List; @@ -41,22 +41,23 @@ public class ExtensionsCommand extends GeyserCommand { private final GeyserImpl geyser; public ExtensionsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); - + super(name, description, permission, TriState.TRUE); this.geyser = geyser; } @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + // TODO: Pagination int page = 1; int maxPage = 1; - String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", sender.locale(), page, maxPage); - sender.sendMessage(header); + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", source.locale(), page, maxPage); + source.sendMessage(header); this.geyser.extensionManager().extensions().stream().sorted(Comparator.comparing(Extension::name)).forEach(extension -> { String extensionName = (extension.isEnabled() ? ChatColor.GREEN : ChatColor.RED) + extension.name(); - sender.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors())); + source.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors())); }); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index c9671b089..9911863ab 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -25,61 +25,59 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.GeyserImpl; +import com.google.common.base.Predicates; import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; +import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Map; public class HelpCommand extends GeyserCommand { - private final GeyserImpl geyser; - private final String baseCommand; - private final Map commands; + private final String rootCommand; + private final Collection commands; - public HelpCommand(GeyserImpl geyser, String name, String description, String permission, - String baseCommand, Map commands) { - super(name, description, permission); - this.geyser = geyser; - this.baseCommand = baseCommand; - this.commands = commands; - - this.setAliases(Collections.singletonList("?")); + public HelpCommand(String rootCommand, String name, String description, String permission, Map commands) { + super(name, description, permission, TriState.TRUE); + this.rootCommand = rootCommand; + this.commands = commands.values(); + this.aliases = Collections.singletonList("?"); } - /** - * Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session. - * - * @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden. - * @param sender The CommandSender to send the help message to. - * @param args Not used. - */ @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { + public String rootCommand() { + return rootCommand; + } + + @Override + public void execute(CommandContext context) { + execute(context.sender()); + } + + public void execute(GeyserCommandSource source) { + boolean bedrockPlayer = source.connection() != null; + + // todo: pagination int page = 1; int maxPage = 1; - String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header"; - String header = GeyserLocale.getPlayerLocaleString(translationKey, sender.locale(), page, maxPage); - sender.sendMessage(header); + String translationKey = this.rootCommand.equals(DEFAULT_ROOT_COMMAND) ? "geyser.commands.help.header" : "geyser.commands.extensions.header"; + String header = GeyserLocale.getPlayerLocaleString(translationKey, source.locale(), page, maxPage); + source.sendMessage(header); - this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - Command cmd = entry.getValue(); - - // Standalone hack-in since it doesn't have a concept of permissions - if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.permission())) { - // Only list commands the player can actually run - if (cmd.isBedrockOnly() && session == null) { - return; - } - - sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " + - GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale())); - } - }); + this.commands.stream() + .distinct() // remove aliases + .filter(bedrockPlayer ? Predicates.alwaysTrue() : cmd -> !cmd.isBedrockOnly()) // remove bedrock only commands if not a bedrock player + .filter(cmd -> source.hasPermission(cmd.permission())) + .sorted(Comparator.comparing(Command::name)) + .forEachOrdered(cmd -> { + String description = GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale()); + source.sendMessage(ChatColor.YELLOW + "/" + rootCommand + " " + cmd.name() + ChatColor.WHITE + ": " + description); + }); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 90446fbb6..5a76ab902 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -26,10 +26,12 @@ package org.geysermc.geyser.command.defaults; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.stream.Collectors; @@ -38,22 +40,18 @@ public class ListCommand extends GeyserCommand { private final GeyserImpl geyser; public ListCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); - + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.locale(), - geyser.getSessionManager().size(), - geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); - sender.sendMessage(message); - } + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(), + geyser.getSessionManager().size(), + geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); - @Override - public boolean isSuggestedOpOnly() { - return true; + source.sendMessage(message); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index 6188e6924..5f9061618 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -25,33 +25,23 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; public class OffhandCommand extends GeyserCommand { - public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + public OffhandCommand(String name, String description, String permission) { + super(name, description, permission, TriState.TRUE, true, true); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session == null) { - return; - } - + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.requestOffhandSwap(); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/PingCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/PingCommand.java new file mode 100644 index 000000000..f39be0528 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/PingCommand.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2023 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.command.defaults; + +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; + +public class PingCommand extends GeyserCommand { + + public PingCommand(String name, String description, String permission) { + super(name, description, permission, TriState.TRUE, true, true); + } + + @Override + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.ping.message", session.locale(), session.ping())); + } +} + diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 987860238..e54b83ddf 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.concurrent.TimeUnit; @@ -39,27 +39,17 @@ public class ReloadCommand extends GeyserCommand { private final GeyserImpl geyser; public ReloadCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - return; - } - - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.locale()); - - sender.sendMessage(message); + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale())); geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); //FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 7828cf1d2..a5734a69f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -25,31 +25,24 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; public class SettingsCommand extends GeyserCommand { - public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + + public SettingsCommand(String name, String description, String permission) { + super(name, description, permission, TriState.TRUE, true, true); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - session.sendForm(SettingsUtils.buildForm(session)); - } - } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); + session.sendForm(SettingsUtils.buildForm(session)); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index 5952ea00d..eebb9170c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -25,35 +25,28 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; +import org.incendo.cloud.context.CommandContext; + +import java.util.Objects; public class StatisticsCommand extends GeyserCommand { - public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + public StatisticsCommand(String name, String description, String permission) { + super(name, description, permission, TriState.TRUE, true, true); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session == null) return; + public void execute(CommandContext context) { + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.setWaitingForStatistics(true); - ServerboundClientCommandPacket ServerboundClientCommandPacket = new ServerboundClientCommandPacket(ClientCommand.STATS); - session.sendDownstreamGamePacket(ServerboundClientCommandPacket); - } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; + ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); + session.sendDownstreamGamePacket(packet); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 1cd3050c9..f6dc1610a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -25,12 +25,11 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.Collections; @@ -39,24 +38,13 @@ public class StopCommand extends GeyserCommand { private final GeyserImpl geyser; public StopCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; - - this.setAliases(Collections.singletonList("shutdown")); + this.aliases = Collections.singletonList("shutdown"); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } - + public void execute(CommandContext context) { geyser.getBootstrap().onGeyserShutdown(); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index c6852d577..8d34c1bf0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -29,13 +29,14 @@ import com.fasterxml.jackson.databind.JsonNode; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.context.CommandContext; import java.io.IOException; import java.util.List; @@ -45,13 +46,14 @@ public class VersionCommand extends GeyserCommand { private final GeyserImpl geyser; public VersionCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); - + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + String bedrockVersions; List supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS; if (supportedCodecs.size() > 1) { @@ -67,45 +69,37 @@ public class VersionCommand extends GeyserCommand { javaVersions = supportedJavaVersions.get(0); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.locale(), + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(), 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)) { + if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.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")); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", source.locale(), "https://discord.gg/geysermc")); return; } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.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())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale())); return; } - sender.sendMessage(GeyserLocale.getPlayerLocaleString( + source.sendMessage(GeyserLocale.getPlayerLocaleString( "geyser.commands.version.outdated", - sender.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download" + source.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())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", source.locale())); } } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java new file mode 100644 index 000000000..edacd49ff --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java @@ -0,0 +1,42 @@ +/* + * 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.command.standalone; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.Collections; +import java.util.Set; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final +public class PermissionConfiguration { + + @JsonProperty("default-permissions") + private Set defaultPermissions = Collections.emptySet(); +} diff --git a/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java new file mode 100644 index 000000000..99c53f319 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java @@ -0,0 +1,126 @@ +/* + * 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.command.standalone; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.permission.PermissionChecker; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.internal.CommandRegistrationHandler; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class StandaloneCloudCommandManager extends CommandManager { + + private final GeyserImpl geyser; + + /** + * The checkers we use to test if a command source has a permission + */ + private final List permissionCheckers = new ArrayList<>(); + + /** + * Any permissions that all connections have + */ + private final Set basePermissions = new ObjectOpenHashSet<>(); + + public StandaloneCloudCommandManager(GeyserImpl geyser) { + super(ExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); + // simpleCoordinator: execute commands immediately on the calling thread. + // nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled. + this.geyser = geyser; + + // allow any extensions to customize permissions + geyser.getEventBus().fire((GeyserRegisterPermissionCheckersEvent) permissionCheckers::add); + + // must still implement a basic permission system + try { + File permissionsFile = geyser.getBootstrap().getConfigFolder().resolve("permissions.yml").toFile(); + FileUtils.fileOrCopiedFromResource(permissionsFile, "permissions.yml", geyser.getBootstrap()); + PermissionConfiguration config = FileUtils.loadConfig(permissionsFile, PermissionConfiguration.class); + basePermissions.addAll(config.getDefaultPermissions()); + } catch (Exception e) { + geyser.getLogger().error("Failed to load permissions.yml - proceeding without it", e); + } + } + + /** + * Fire a {@link GeyserRegisterPermissionsEvent} to determine any additions or removals to the base list of + * permissions. This should be called after any event listeners have been registered, such as that of {@link CommandRegistry}. + */ + public void fireRegisterPermissionsEvent() { + geyser.getEventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { + Objects.requireNonNull(permission, "permission"); + Objects.requireNonNull(def, "permission default for " + permission); + + if (permission.isBlank()) { + return; + } + if (def == TriState.TRUE) { + basePermissions.add(permission); + } + }); + } + + @Override + public boolean hasPermission(@NonNull GeyserCommandSource sender, @NonNull String permission) { + // Note: the two GeyserCommandSources on Geyser-Standalone are GeyserLogger and GeyserSession + // GeyserLogger#hasPermission always returns true + // GeyserSession#hasPermission delegates to this method, + // which is why this method doesn't just call GeyserCommandSource#hasPermission + if (sender.isConsole()) { + return true; + } + + // An empty or blank permission is treated as a lack of permission requirement + if (permission.isBlank()) { + return true; + } + + for (PermissionChecker checker : permissionCheckers) { + Boolean result = checker.hasPermission(sender, permission).toBoolean(); + if (result != null) { + return result; + } + // undefined - try the next checker to see if it has a defined value + } + // fallback to our list of default permissions + // note that a PermissionChecker may in fact override any values set here by returning FALSE + return basePermissions.contains(permission); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 6989dc10a..515e1a629 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -81,7 +81,7 @@ public class DumpInfo { private final FlagsInfo flagsInfo; private final List extensionInfo; - public DumpInfo(boolean addLog) { + public DumpInfo(GeyserImpl geyser, boolean addLog) { this.versionInfo = new VersionInfo(); this.cpuCount = Runtime.getRuntime().availableProcessors(); @@ -91,7 +91,7 @@ public class DumpInfo { this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY); - this.config = GeyserImpl.getInstance().getConfig(); + this.config = geyser.getConfig(); this.floodgate = new Floodgate(); String md5Hash = "unknown"; @@ -107,7 +107,7 @@ public class DumpInfo { //noinspection UnstableApiUsage sha256Hash = byteSource.hash(Hashing.sha256()).toString(); } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (this.config.isDebugMode()) { e.printStackTrace(); } } @@ -116,18 +116,22 @@ public class DumpInfo { this.ramInfo = new RamInfo(); if (addLog) { - this.logsInfo = new LogsInfo(); + this.logsInfo = new LogsInfo(geyser); } this.userPlatforms = new Object2IntOpenHashMap<>(); - for (GeyserSession session : GeyserImpl.getInstance().getSessionManager().getAllSessions()) { + for (GeyserSession session : geyser.getSessionManager().getAllSessions()) { DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } - this.connectionAttempts = GeyserImpl.getInstance().getGeyserServer().getConnectionAttempts(); + if (geyser.getGeyserServer() != null) { + this.connectionAttempts = geyser.getGeyserServer().getConnectionAttempts(); + } else { + this.connectionAttempts = 0; // Fallback if Geyser failed to fully startup + } - this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo(); + this.bootstrapInfo = geyser.getBootstrap().getDumpInfo(); this.flagsInfo = new FlagsInfo(); @@ -244,10 +248,10 @@ public class DumpInfo { public static class LogsInfo { private String link; - public LogsInfo() { + public LogsInfo(GeyserImpl geyser) { try { Map fields = new HashMap<>(); - fields.put("content", FileUtils.readAllLines(GeyserImpl.getInstance().getBootstrap().getLogsPath()).collect(Collectors.joining("\n"))); + fields.put("content", FileUtils.readAllLines(geyser.getBootstrap().getLogsPath()).collect(Collectors.joining("\n"))); JsonNode logData = GeyserImpl.JSON_MAPPER.readTree(WebUtils.postForm("https://api.mclo.gs/1/log", fields)); diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java index f9b65a545..ea3950bd4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java @@ -25,10 +25,10 @@ package org.geysermc.geyser.entity; -import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; -import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType; -import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import java.util.Locale; +import java.util.function.BiConsumer; import lombok.Setter; import lombok.experimental.Accessors; import org.geysermc.geyser.GeyserImpl; @@ -37,10 +37,10 @@ import org.geysermc.geyser.entity.properties.GeyserEntityProperties; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.translator.entity.EntityMetadataTranslator; - -import java.util.List; -import java.util.Locale; -import java.util.function.BiConsumer; +import org.geysermc.geyser.util.EnvironmentUtils; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType; +import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; /** * Represents data for an entity. This includes properties such as height and width, as well as the list of entity @@ -146,8 +146,13 @@ public record EntityDefinition(EntityFactory factory, Entit return this; } + /** + * Build the given entity. If a testing environment has been discovered the entity is not registered, + * otherwise it is. This is to prevent all the registries from loading, which will fail (and should + * not be loaded) while testing + */ public EntityDefinition build() { - return build(true); + return build(!EnvironmentUtils.isUnitTesting); } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 11b4a32d1..39357eb60 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -25,34 +25,131 @@ 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; -import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata; -import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.factory.EntityFactory; import org.geysermc.geyser.entity.properties.GeyserEntityProperties; -import org.geysermc.geyser.entity.type.*; -import org.geysermc.geyser.entity.type.living.*; -import org.geysermc.geyser.entity.type.living.animal.*; -import org.geysermc.geyser.entity.type.living.animal.horse.*; +import org.geysermc.geyser.entity.type.AbstractArrowEntity; +import org.geysermc.geyser.entity.type.AbstractWindChargeEntity; +import org.geysermc.geyser.entity.type.AreaEffectCloudEntity; +import org.geysermc.geyser.entity.type.ArrowEntity; +import org.geysermc.geyser.entity.type.BoatEntity; +import org.geysermc.geyser.entity.type.ChestBoatEntity; +import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; +import org.geysermc.geyser.entity.type.DisplayBaseEntity; +import org.geysermc.geyser.entity.type.EnderCrystalEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.EvokerFangsEntity; +import org.geysermc.geyser.entity.type.ExpOrbEntity; +import org.geysermc.geyser.entity.type.FallingBlockEntity; +import org.geysermc.geyser.entity.type.FireballEntity; +import org.geysermc.geyser.entity.type.FireworkEntity; +import org.geysermc.geyser.entity.type.FishingHookEntity; +import org.geysermc.geyser.entity.type.FurnaceMinecartEntity; +import org.geysermc.geyser.entity.type.InteractionEntity; +import org.geysermc.geyser.entity.type.ItemEntity; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.entity.type.LeashKnotEntity; +import org.geysermc.geyser.entity.type.LightningEntity; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.entity.type.MinecartEntity; +import org.geysermc.geyser.entity.type.PaintingEntity; +import org.geysermc.geyser.entity.type.SpawnerMinecartEntity; +import org.geysermc.geyser.entity.type.TNTEntity; +import org.geysermc.geyser.entity.type.TextDisplayEntity; +import org.geysermc.geyser.entity.type.ThrowableEntity; +import org.geysermc.geyser.entity.type.ThrowableItemEntity; +import org.geysermc.geyser.entity.type.ThrownPotionEntity; +import org.geysermc.geyser.entity.type.TridentEntity; +import org.geysermc.geyser.entity.type.WitherSkullEntity; +import org.geysermc.geyser.entity.type.living.AbstractFishEntity; +import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.entity.type.living.AllayEntity; +import org.geysermc.geyser.entity.type.living.ArmorStandEntity; +import org.geysermc.geyser.entity.type.living.BatEntity; +import org.geysermc.geyser.entity.type.living.DolphinEntity; +import org.geysermc.geyser.entity.type.living.GlowSquidEntity; +import org.geysermc.geyser.entity.type.living.IronGolemEntity; +import org.geysermc.geyser.entity.type.living.MagmaCubeEntity; +import org.geysermc.geyser.entity.type.living.MobEntity; +import org.geysermc.geyser.entity.type.living.SlimeEntity; +import org.geysermc.geyser.entity.type.living.SnowGolemEntity; +import org.geysermc.geyser.entity.type.living.SquidEntity; +import org.geysermc.geyser.entity.type.living.TadpoleEntity; +import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity; +import org.geysermc.geyser.entity.type.living.animal.AxolotlEntity; +import org.geysermc.geyser.entity.type.living.animal.BeeEntity; +import org.geysermc.geyser.entity.type.living.animal.ChickenEntity; +import org.geysermc.geyser.entity.type.living.animal.CowEntity; +import org.geysermc.geyser.entity.type.living.animal.FoxEntity; +import org.geysermc.geyser.entity.type.living.animal.FrogEntity; +import org.geysermc.geyser.entity.type.living.animal.GoatEntity; +import org.geysermc.geyser.entity.type.living.animal.HoglinEntity; +import org.geysermc.geyser.entity.type.living.animal.MooshroomEntity; +import org.geysermc.geyser.entity.type.living.animal.OcelotEntity; +import org.geysermc.geyser.entity.type.living.animal.PandaEntity; +import org.geysermc.geyser.entity.type.living.animal.PigEntity; +import org.geysermc.geyser.entity.type.living.animal.PolarBearEntity; +import org.geysermc.geyser.entity.type.living.animal.PufferFishEntity; +import org.geysermc.geyser.entity.type.living.animal.RabbitEntity; +import org.geysermc.geyser.entity.type.living.animal.SheepEntity; +import org.geysermc.geyser.entity.type.living.animal.SnifferEntity; +import org.geysermc.geyser.entity.type.living.animal.StriderEntity; +import org.geysermc.geyser.entity.type.living.animal.TropicalFishEntity; +import org.geysermc.geyser.entity.type.living.animal.TurtleEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.ChestedHorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.SkeletonHorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.TraderLlamaEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.ZombieHorseEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.TameableEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; import org.geysermc.geyser.entity.type.living.merchant.AbstractMerchantEntity; import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; -import org.geysermc.geyser.entity.type.living.monster.*; +import org.geysermc.geyser.entity.type.living.monster.AbstractSkeletonEntity; +import org.geysermc.geyser.entity.type.living.monster.BasePiglinEntity; +import org.geysermc.geyser.entity.type.living.monster.BlazeEntity; +import org.geysermc.geyser.entity.type.living.monster.BoggedEntity; +import org.geysermc.geyser.entity.type.living.monster.BreezeEntity; +import org.geysermc.geyser.entity.type.living.monster.CreeperEntity; +import org.geysermc.geyser.entity.type.living.monster.ElderGuardianEntity; +import org.geysermc.geyser.entity.type.living.monster.EnderDragonEntity; +import org.geysermc.geyser.entity.type.living.monster.EnderDragonPartEntity; +import org.geysermc.geyser.entity.type.living.monster.EndermanEntity; +import org.geysermc.geyser.entity.type.living.monster.GhastEntity; +import org.geysermc.geyser.entity.type.living.monster.GiantEntity; +import org.geysermc.geyser.entity.type.living.monster.GuardianEntity; +import org.geysermc.geyser.entity.type.living.monster.MonsterEntity; +import org.geysermc.geyser.entity.type.living.monster.PhantomEntity; +import org.geysermc.geyser.entity.type.living.monster.PiglinEntity; +import org.geysermc.geyser.entity.type.living.monster.ShulkerEntity; +import org.geysermc.geyser.entity.type.living.monster.SkeletonEntity; +import org.geysermc.geyser.entity.type.living.monster.SpiderEntity; +import org.geysermc.geyser.entity.type.living.monster.VexEntity; +import org.geysermc.geyser.entity.type.living.monster.WardenEntity; +import org.geysermc.geyser.entity.type.living.monster.WitherEntity; +import org.geysermc.geyser.entity.type.living.monster.ZoglinEntity; +import org.geysermc.geyser.entity.type.living.monster.ZombieEntity; +import org.geysermc.geyser.entity.type.living.monster.ZombieVillagerEntity; +import org.geysermc.geyser.entity.type.living.monster.ZombifiedPiglinEntity; import org.geysermc.geyser.entity.type.living.monster.raid.PillagerEntity; import org.geysermc.geyser.entity.type.living.monster.raid.RaidParticipantEntity; +import org.geysermc.geyser.entity.type.living.monster.raid.RavagerEntity; import org.geysermc.geyser.entity.type.living.monster.raid.SpellcasterIllagerEntity; import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.EnvironmentUtils; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; public final class EntityDefinitions { public static final EntityDefinition ALLAY; @@ -327,6 +424,7 @@ public final class EntityDefinitions { TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase) .type(EntityType.TEXT_DISPLAY) .identifier("minecraft:armor_stand") + .offset(-0.5f) .addTranslator(MetadataType.CHAT, TextDisplayEntity::setText) .addTranslator(null) // Line width .addTranslator(null) // Background color @@ -887,7 +985,7 @@ public final class EntityDefinitions { .type(EntityType.PIG) .heightAndWidth(0.9f) .addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) - .addTranslator(null) // Boost time + .addTranslator(MetadataType.INT, PigEntity::setBoost) .build(); POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase) .type(EntityType.POLAR_BEAR) @@ -913,7 +1011,7 @@ public final class EntityDefinitions { STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase) .type(EntityType.STRIDER) .height(1.7f).width(0.9f) - .addTranslator(null) // Boost time + .addTranslator(MetadataType.INT, StriderEntity::setBoost) .addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold) .addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled) .build(); @@ -954,7 +1052,7 @@ public final class EntityDefinitions { .type(EntityType.CAMEL) .height(2.375f).width(1.7f) .addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing) - .addTranslator(null) // Last pose change tick + .addTranslator(MetadataType.LONG, CamelEntity::setLastPoseTick) .build(); HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase) .type(EntityType.HORSE) @@ -1024,7 +1122,10 @@ public final class EntityDefinitions { .identifier("minecraft:armor_stand") // Emulated .build(false); // Never sent over the network - Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network + // causes the registries to load + if (!EnvironmentUtils.isUnitTesting) { + Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network + } } public static void init() { diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java index c9ef7a2dd..6f8f2525f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java @@ -96,4 +96,9 @@ public class GeyserEntityData implements EntityData { public boolean isMovementLocked() { return !movementLockOwners.isEmpty(); } + + @Override + public void switchHands() { + session.requestOffhandSwap(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java index f19912a8c..1e050c840 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java @@ -49,11 +49,12 @@ public enum GeyserAttributeType { ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f), ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), - SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), // Unused. Do we need this? + SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), + BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f), // Bedrock Attributes ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), - EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f), + EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 20f, 0f), EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f), EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f), HEALTH(null, "minecraft:health", 0f, 1024f, 20f), diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 08e87dc03..a016916f0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -25,6 +25,12 @@ package org.geysermc.geyser.entity.type; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -35,12 +41,18 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket; +import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; +import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; 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.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; @@ -55,12 +67,9 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEnt import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; -import java.util.*; - @Getter @Setter public class Entity implements GeyserEntity { - private static final boolean PRINT_ENTITY_SPAWN_DEBUG = Boolean.parseBoolean(System.getProperty("Geyser.PrintEntitySpawnDebug", "false")); protected final GeyserSession session; @@ -68,6 +77,12 @@ public class Entity implements GeyserEntity { protected int entityId; protected final long geyserId; protected UUID uuid; + /** + * Do not call this setter directly! + * This will bypass the scoreboard and setting the metadata + */ + @Setter(AccessLevel.NONE) + protected String nametag = ""; protected Vector3f position; protected Vector3f motion; @@ -97,7 +112,7 @@ public class Entity implements GeyserEntity { @Setter(AccessLevel.NONE) private float boundingBoxWidth; @Setter(AccessLevel.NONE) - protected String nametag = ""; + private String displayName; @Setter(AccessLevel.NONE) protected boolean silent = false; /* Metadata end */ @@ -126,11 +141,12 @@ public class Entity implements GeyserEntity { public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { this.session = session; + this.definition = definition; + this.displayName = standardDisplayName(); this.entityId = entityId; this.geyserId = geyserId; this.uuid = uuid; - this.definition = definition; this.motion = motion; this.yaw = yaw; this.pitch = pitch; @@ -341,7 +357,7 @@ public class Entity implements GeyserEntity { * Sends the Bedrock metadata to the client */ public void updateBedrockMetadata() { - if (!valid) { + if (!isValid()) { return; } @@ -410,17 +426,84 @@ public class Entity implements GeyserEntity { return 300; } + public String teamIdentifier() { + // experience orbs are the only known entities that do not send an uuid (even though they do have one), + // but to be safe in the future it's done in the entity class itself instead of the entity specific one. + // All entities without an uuid cannot show up in the scoreboard! + return uuid != null ? uuid.toString() : null; + } + public void setDisplayName(EntityMetadata, ?> entityMetadata) { + // displayName is shown when always display name is enabled. Either with or without team. + // That's why there are both a displayName and a nametag variable. + // Displayname is ignored for players, and is always their username. Optional name = entityMetadata.getValue(); if (name.isPresent()) { - nametag = MessageTranslator.convertMessage(name.get(), session.locale()); - dirtyMetadata.put(EntityDataTypes.NAME, nametag); - } else if (!nametag.isEmpty()) { - // Clear nametag - dirtyMetadata.put(EntityDataTypes.NAME, ""); + String displayName = MessageTranslator.convertMessage(name.get(), session.locale()); + this.displayName = displayName; + setNametag(displayName, true); + return; } + + // if no displayName is set, use entity name (ENDER_DRAGON -> Ender Dragon) + // maybe we can/should use a translatable here instead? + this.displayName = standardDisplayName(); + setNametag(null, true); } + protected String standardDisplayName() { + return EntityUtils.translatedEntityName(definition.entityType(), session); + } + + protected void setNametag(@Nullable String nametag, boolean fromDisplayName) { + // ensure that the team format is used when nametag changes + if (nametag != null && fromDisplayName) { + var team = session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier()); + if (team != null) { + updateNametag(team); + return; + } + } + + if (nametag == null) { + nametag = ""; + } + boolean changed = !Objects.equals(this.nametag, nametag); + this.nametag = nametag; + // we only update metadata if the value has changed + if (!changed) { + return; + } + + dirtyMetadata.put(EntityDataTypes.NAME, nametag); + // if nametag (player with team) is hidden for player, so should the score (belowname) + scoreVisibility(!nametag.isEmpty()); + } + + public void updateNametag(@Nullable Team team) { + // allow LivingEntity+ to have a different visibility check + updateNametag(team, true); + } + + protected void updateNametag(@Nullable Team team, boolean visible) { + if (team != null) { + String newNametag; + // (team) visibility is LivingEntity+, team displayName is Entity+ + if (visible) { + newNametag = team.displayName(getDisplayName()); + } else { + // The name is not visible to the session player; clear name + newNametag = ""; + } + setNametag(newNametag, false); + return; + } + // The name has reset, if it was previously something else + setNametag(null, false); + } + + protected void scoreVisibility(boolean show) {} + public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) { dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 499084555..626ceca5c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -25,6 +25,11 @@ package org.geysermc.geyser.entity.type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -41,13 +46,16 @@ import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.MathUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; @@ -63,16 +71,14 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffect import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle; import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; -import java.util.*; - @Getter @Setter public class LivingEntity extends Entity { - protected ItemData helmet = ItemData.AIR; protected ItemData chestplate = ItemData.AIR; protected ItemData leggings = ItemData.AIR; protected ItemData boots = ItemData.AIR; + protected ItemData body = ItemData.AIR; protected ItemData hand = ItemData.AIR; protected ItemData offhand = ItemData.AIR; @@ -111,6 +117,10 @@ public class LivingEntity extends Entity { this.chestplate = ItemTranslator.translateToBedrock(session, stack); } + public void setBody(ItemStack stack) { + this.body = ItemTranslator.translateToBedrock(session, stack); + } + public void setLeggings(ItemStack stack) { this.leggings = ItemTranslator.translateToBedrock(session, stack); } @@ -143,6 +153,16 @@ public class LivingEntity extends Entity { dirtyMetadata.put(EntityDataTypes.STRUCTURAL_INTEGRITY, 1); } + @Override + public void updateNametag(@Nullable Team team) { + // if name not visible, don't mark it as visible + updateNametag(team, team == null || team.isVisibleFor(session.getPlayerEntity().getUsername())); + } + + public void hideNametag() { + setNametag("", false); + } + public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); @@ -252,7 +272,7 @@ public class LivingEntity extends Entity { } private void setAttributeScale(float scale) { - this.attributeScale = scale; + this.attributeScale = MathUtils.clamp(scale, GeyserAttributeType.SCALE.getMinimum(), GeyserAttributeType.SCALE.getMaximum()); applyScale(); } @@ -288,6 +308,36 @@ public class LivingEntity extends Entity { return super.interact(hand); } + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + if (this instanceof ClientVehicle clientVehicle) { + if (clientVehicle.isClientControlled()) { + return; + } + clientVehicle.getVehicleComponent().moveRelative(relX, relY, relZ); + } + + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + } + + @Override + public boolean setBoundingBoxHeight(float height) { + if (valid && this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setHeight(height); + } + + return super.setBoundingBoxHeight(height); + } + + @Override + public void setBoundingBoxWidth(float width) { + if (valid && this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setWidth(width); + } + + super.setBoundingBoxWidth(width); + } + /** * Checks to see if a nametag interaction would go through. */ @@ -322,6 +372,7 @@ public class LivingEntity extends Entity { armorEquipmentPacket.setChestplate(chestplate); armorEquipmentPacket.setLeggings(leggings); armorEquipmentPacket.setBoots(boots); + armorEquipmentPacket.setBody(body); session.sendUpstreamPacket(armorEquipmentPacket); } @@ -400,9 +451,25 @@ public class LivingEntity extends Entity { this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f); newAttributes.add(createHealthAttribute()); } + case GENERIC_MOVEMENT_SPEED -> { + AttributeData attributeData = calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED); + newAttributes.add(attributeData); + if (this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setMoveSpeed(attributeData.getValue()); + } + } + case GENERIC_STEP_HEIGHT -> { + if (this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setStepHeight((float) AttributeUtils.calculateValue(javaAttribute)); + } + } + case GENERIC_GRAVITY -> { + if (this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setGravity(AttributeUtils.calculateValue(javaAttribute)); + } + } case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED)); - case GENERIC_MOVEMENT_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED)); case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE)); case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE)); case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH)); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java index ff5604c19..8b47ce1ed 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java @@ -38,7 +38,17 @@ import java.util.UUID; // Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition public class TextDisplayEntity extends DisplayBaseEntity { public TextDisplayEntity(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); + super(session, entityId, geyserId, uuid, definition, position.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { + super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java index 446e3e109..2ec23d673 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java @@ -27,20 +27,30 @@ package org.geysermc.geyser.entity.type.living.animal; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; +import org.geysermc.geyser.entity.vehicle.VehicleComponent; import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.item.Items; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import java.util.UUID; -public class PigEntity extends AnimalEntity { +public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle { + private final BoostableVehicleComponent vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f); public PigEntity(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); @@ -84,4 +94,55 @@ public class PigEntity extends AnimalEntity { } } } + + public void setBoost(IntEntityMetadata entityMetadata) { + vehicleComponent.startBoost(entityMetadata.getPrimitiveValue()); + } + + @Override + public void tick() { + PlayerEntity player = getPlayerPassenger(); + if (player == null) { + return; + } + + if (player == session.getPlayerEntity()) { + if (session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK)) { + vehicleComponent.tickBoost(); + } + } else { // getHand() for session player seems to always return air + ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().carrotOnAStick().getBedrockDefinition(); + if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) { + vehicleComponent.tickBoost(); + } + } + } + + @Override + public VehicleComponent getVehicleComponent() { + return vehicleComponent; + } + + @Override + public Vector2f getAdjustedInput(Vector2f input) { + return Vector2f.UNIT_Y; + } + + @Override + public float getVehicleSpeed() { + return vehicleComponent.getMoveSpeed() * 0.225f * vehicleComponent.getBoostMultiplier(); + } + + private @Nullable PlayerEntity getPlayerPassenger() { + if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) { + return playerEntity; + } + + return null; + } + + @Override + public boolean isClientControlled() { + return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java index 0a108be73..fbfc5d40a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.entity.type.living.animal; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; @@ -32,11 +33,13 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; +import org.geysermc.geyser.util.EntityUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import java.util.UUID; public class RabbitEntity extends AnimalEntity { + private boolean isKillerBunny; public RabbitEntity(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); @@ -46,7 +49,7 @@ public class RabbitEntity extends AnimalEntity { int variant = entityMetadata.getPrimitiveValue(); // Change the killer bunny to display as white since it only exists on Java Edition - boolean isKillerBunny = variant == 99; + isKillerBunny = variant == 99; if (isKillerBunny) { variant = 1; } @@ -56,6 +59,14 @@ public class RabbitEntity extends AnimalEntity { dirtyMetadata.put(EntityDataTypes.VARIANT, variant); } + @Override + protected String standardDisplayName() { + if (isKillerBunny) { + return EntityUtils.translatedEntityName(Key.key("killer_bunny"), session); + } + return super.standardDisplayName(); + } + @Override protected float getAdultSize() { return 0.55f; @@ -71,4 +82,4 @@ public class RabbitEntity extends AnimalEntity { protected ItemTag getFoodTag() { return ItemTag.RABBIT_FOOD; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java index 0291f75d9..e06af2786 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java @@ -27,23 +27,33 @@ package org.geysermc.geyser.entity.type.living.animal; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; +import org.geysermc.geyser.entity.vehicle.VehicleComponent; import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.item.Items; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.util.EntityUtils; 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.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import java.util.UUID; -public class StriderEntity extends AnimalEntity { +public class StriderEntity extends AnimalEntity implements Tickable, ClientVehicle { + private final BoostableVehicleComponent vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f); private boolean isCold = false; public StriderEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { @@ -131,4 +141,60 @@ public class StriderEntity extends AnimalEntity { } } } + + public void setBoost(IntEntityMetadata entityMetadata) { + vehicleComponent.startBoost(entityMetadata.getPrimitiveValue()); + } + + @Override + public void tick() { + PlayerEntity player = getPlayerPassenger(); + if (player == null) { + return; + } + + if (player == session.getPlayerEntity()) { + if (session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK)) { + vehicleComponent.tickBoost(); + } + } else { // getHand() for session player seems to always return air + ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().warpedFungusOnAStick().getBedrockDefinition(); + if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) { + vehicleComponent.tickBoost(); + } + } + } + + @Override + public VehicleComponent getVehicleComponent() { + return vehicleComponent; + } + + @Override + public Vector2f getAdjustedInput(Vector2f input) { + return Vector2f.UNIT_Y; + } + + @Override + public float getVehicleSpeed() { + return vehicleComponent.getMoveSpeed() * (isCold ? 0.35f : 0.55f) * vehicleComponent.getBoostMultiplier(); + } + + private @Nullable PlayerEntity getPlayerPassenger() { + if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) { + return playerEntity; + } + + return null; + } + + @Override + public boolean isClientControlled() { + return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK); + } + + @Override + public boolean canWalkOnLava() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index 16901a844..ae7f2d2bd 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -55,6 +55,17 @@ public class TurtleEntity extends AnimalEntity { return ItemTag.TURTLE_FOOD; } + @Override + protected float getAdultSize() { + return super.getAdultSize() * 0.7f; + } + + @Override + protected float getBabySize() { + // 0.3f is Java scale, plus Bedrock difference + return 0.3f * 0.5f; + } + @Override public boolean canBeLeashed() { return false; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java index ee3b2be70..3c0bf1a70 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java @@ -25,26 +25,36 @@ package org.geysermc.geyser.entity.type.living.animal.horse; +import org.cloudburstmc.math.vector.Vector2f; 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.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.vehicle.CamelVehicleComponent; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; +import org.geysermc.geyser.entity.vehicle.VehicleComponent; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; +import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute; +import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.LongEntityMetadata; import java.util.UUID; -public class CamelEntity extends AbstractHorseEntity { - +public class CamelEntity extends AbstractHorseEntity implements ClientVehicle { public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F; + private final CamelVehicleComponent vehicleComponent = new CamelVehicleComponent(this); + public CamelEntity(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); @@ -111,5 +121,58 @@ public class CamelEntity extends AbstractHorseEntity { } public void setDashing(BooleanEntityMetadata entityMetadata) { + // Java sends true to show dash animation and start the dash cooldown, + // false ends the dash animation, not the cooldown. + // Bedrock shows dash animation if HAS_DASH_COOLDOWN is set and the camel is above ground + if (entityMetadata.getPrimitiveValue()) { + setFlag(EntityFlag.HAS_DASH_COOLDOWN, true); + vehicleComponent.startDashCooldown(); + } else if (!isClientControlled()) { // Don't remove dash cooldown prematurely if client is controlling + setFlag(EntityFlag.HAS_DASH_COOLDOWN, false); + } + } + + public void setLastPoseTick(LongEntityMetadata entityMetadata) { + // Tick is based on world time. If negative, the camel is sitting. + // Must be compared to world time to know if the camel is fully standing/sitting or transitioning. + vehicleComponent.setLastPoseTick(entityMetadata.getPrimitiveValue()); + } + + @Override + protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) { + AttributeData attributeData = super.calculateAttribute(javaAttribute, type); + if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_JUMP_STRENGTH) { + vehicleComponent.setHorseJumpStrength(attributeData.getValue()); + } + return attributeData; + } + + @Override + public VehicleComponent getVehicleComponent() { + return vehicleComponent; + } + + @Override + public Vector2f getAdjustedInput(Vector2f input) { + return input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f); + } + + @Override + public boolean isClientControlled() { + return getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) == session.getPlayerEntity(); + } + + @Override + public float getVehicleSpeed() { + float moveSpeed = vehicleComponent.getMoveSpeed(); + if (!getFlag(EntityFlag.HAS_DASH_COOLDOWN) && session.getPlayerEntity().getFlag(EntityFlag.SPRINTING)) { + return moveSpeed + 0.1f; + } + return moveSpeed; + } + + @Override + public boolean canClimb() { + return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java index 8baba6f00..69b19b1b9 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -31,7 +31,6 @@ 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.item.type.Item; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; @@ -39,13 +38,9 @@ import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; -import java.util.Set; import java.util.UUID; public class ParrotEntity extends TameableEntity { - // Note: is the same as chicken. Reuse? - private static final Set TAMING_FOOD = Set.of(Items.WHEAT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.BEETROOT_SEEDS); - public ParrotEntity(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); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index 0162d498e..04044fcb4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -30,7 +30,11 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.ParticleType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket; +import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; +import org.cloudburstmc.protocol.bedrock.packet.SpawnParticleEffectPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.living.MobEntity; @@ -260,7 +264,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { // so we need to manually spawn particles for (int i = 0; i < 8; i++) { SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket(); - spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session)); spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 4c67b882f..4e0de44ea 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -25,6 +25,12 @@ package org.geysermc.geyser.entity.type.player; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; @@ -32,49 +38,33 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.Ability; import org.cloudburstmc.protocol.bedrock.data.AbilityLayer; import org.cloudburstmc.protocol.bedrock.data.GameType; import org.cloudburstmc.protocol.bedrock.data.PlayerPermission; import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket; +import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; -import org.geysermc.geyser.scoreboard.Objective; -import org.geysermc.geyser.scoreboard.Score; -import org.geysermc.geyser.scoreboard.Team; -import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.mcprotocollib.protocol.codec.NbtComponentSerializer; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.BlankFormat; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.FixedFormat; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.StyledFormat; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; @Getter @Setter public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { @@ -92,6 +82,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { private String username; + private String cachedScore = ""; + private boolean scoreVisible = true; + /** * The textures property from the GameProfile. */ @@ -119,6 +112,20 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { this.texturesProperty = texturesProperty; } + /** + * Do not use! For testing purposes only + */ + public PlayerEntity(GeyserSession session, long geyserId, UUID uuid, String username) { + super(session, -1, geyserId, uuid, EntityDefinitions.PLAYER, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + this.username = username; + this.nametag = username; + this.texturesProperty = null; + + // clear initial metadata + dirtyMetadata.apply(new EntityDataMap()); + setFlagsDirty(false); + } + @Override protected void initializeMetadata() { super.initializeMetadata(); @@ -128,17 +135,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { @Override public void spawnEntity() { - // Check to see if the player should have a belowname counterpart added - Objective objective = session.getWorldCache().getScoreboard().getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); - if (objective != null) { - setBelowNameText(objective); - } - - // Update in case this entity has been despawned, then respawned - this.nametag = this.username; - // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now - updateDisplayName(session.getWorldCache().getScoreboard().getTeamFor(username)); - AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); addPlayerPacket.setUuid(uuid); addPlayerPacket.setUsername(username); @@ -173,6 +169,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { // Since we re-use player entities: Clear flags, held item, etc this.resetMetadata(); + this.nametag = username; this.hand = ItemData.AIR; this.offhand = ItemData.AIR; this.boots = ItemData.AIR; @@ -278,7 +275,13 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { @Override public void setPosition(Vector3f position) { - super.setPosition(position.add(0, definition.offset(), 0)); + if (this.bedPosition != null) { + // As of Bedrock 1.21.22 and Fabric 1.21.1 + // Messes with Bedrock if we send this to the client itself, though. + super.setPosition(position.up(0.2f)); + } else { + super.setPosition(position.add(0, definition.offset(), 0)); + } } @Override @@ -376,38 +379,30 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { } } + @Override + public String getDisplayName() { + return username; + } + @Override public void setDisplayName(EntityMetadata, ?> entityMetadata) { // Doesn't do anything for players } - //todo this will become common entity logic once UUID support is implemented for them - public void updateDisplayName(@Nullable Team team) { - boolean needsUpdate; - if (team != null) { - String newDisplayName; - if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - TeamColor color = team.getColor(); - String chatColor = MessageTranslator.toChatColor(color); - // We have to emulate what modern Java text already does for us and add the color to each section - String prefix = team.getCurrentData().getPrefix(); - String suffix = team.getCurrentData().getSuffix(); - newDisplayName = chatColor + prefix + chatColor + this.username + chatColor + suffix; - } else { - // The name is not visible to the session player; clear name - newDisplayName = ""; - } - needsUpdate = !newDisplayName.equals(this.nametag); - this.nametag = newDisplayName; - } else { - // The name has reset, if it was previously something else - needsUpdate = !this.nametag.equals(this.username); - this.nametag = this.username; - } + @Override + public String teamIdentifier() { + return username; + } - if (needsUpdate) { - dirtyMetadata.put(EntityDataTypes.NAME, this.nametag); + @Override + protected void setNametag(@Nullable String nametag, boolean fromDisplayName) { + // when fromDisplayName, LivingEntity will call scoreboard code. After that + // setNametag is called again with fromDisplayName on false + if (nametag == null && !fromDisplayName) { + // nametag = null means reset, so reset it back to username + nametag = username; } + super.setNametag(nametag, fromDisplayName); } @Override @@ -415,6 +410,33 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { // Doesn't do anything for players } + public void setBelowNameText(String text) { + if (text == null) { + text = ""; + } + + boolean changed = !Objects.equals(cachedScore, text); + cachedScore = text; + if (isScoreVisible() && changed) { + dirtyMetadata.put(EntityDataTypes.SCORE, text); + } + } + + @Override + protected void scoreVisibility(boolean show) { + boolean visibilityChanged = scoreVisible != show; + scoreVisible = show; + if (!visibilityChanged) { + return; + } + // if the player has no cachedScore, we never have to change the score. + // hide = set to "" (does nothing), show = change from "" (does nothing) + if (cachedScore.isEmpty()) { + return; + } + dirtyMetadata.put(EntityDataTypes.SCORE, show ? cachedScore : ""); + } + @Override protected void setDimensions(Pose pose) { float height; @@ -441,64 +463,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { setBoundingBoxHeight(height); } - public void setBelowNameText(Objective objective) { - if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) { - Score score = objective.getScores().get(username); - String numberString; - NumberFormat numberFormat; - int amount; - if (score != null) { - amount = score.getScore(); - numberFormat = score.getNumberFormat(); - if (numberFormat == null) { - numberFormat = objective.getNumberFormat(); - } - } else { - amount = 0; - numberFormat = objective.getNumberFormat(); - } - - if (numberFormat instanceof BlankFormat) { - numberString = ""; - } else if (numberFormat instanceof FixedFormat fixedFormat) { - numberString = MessageTranslator.convertMessage(fixedFormat.getValue()); - } else if (numberFormat instanceof StyledFormat styledFormat) { - NbtMapBuilder styledAmount = styledFormat.getStyle().toBuilder(); - styledAmount.putString("text", String.valueOf(amount)); - - numberString = MessageTranslator.convertJsonMessage( - NbtComponentSerializer.tagComponentToJson(styledAmount.build()).toString(), session.locale()); - } else { - numberString = String.valueOf(amount); - } - - String displayString = numberString + " " + ChatColor.RESET + objective.getDisplayName(); - - if (valid) { - // Already spawned - we still need to run the rest of this code because the spawn packet will be - // providing the information - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.setRuntimeEntityId(geyserId); - packet.getMetadata().put(EntityDataTypes.SCORE, displayString); - session.sendUpstreamPacket(packet); - } else { - // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity - dirtyMetadata.put(EntityDataTypes.SCORE, displayString); - } - } else { - if (valid) { - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.setRuntimeEntityId(geyserId); - packet.getMetadata().put(EntityDataTypes.SCORE, ""); - session.sendUpstreamPacket(packet); - } else { - // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity - dirtyMetadata.put(EntityDataTypes.SCORE, ""); - } - } - - } - /** * @return the UUID that should be used when dealing with Bedrock's tab list. */ diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 45fea4d48..f427b001a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -29,6 +29,7 @@ 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.Vector2f; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.AttributeData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; @@ -42,6 +43,7 @@ import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.DimensionUtils; +import org.geysermc.geyser.util.MathUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos; @@ -64,11 +66,26 @@ public class SessionPlayerEntity extends PlayerEntity { */ @Getter protected final Map attributes = new Object2ObjectOpenHashMap<>(); + /** + * Java-only attribute + */ + @Getter + private double blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue(); /** * Used in PlayerInputTranslator for movement checks. */ @Getter private boolean isRidingInFront; + /** + * Used when emulating client-side vehicles + */ + @Getter + private Vector2f vehicleInput = Vector2f.ZERO; + /** + * Used when emulating client-side vehicles + */ + @Getter + private int vehicleJumpStrength; private int lastAirSupply = getMaxAir(); @@ -123,7 +140,7 @@ public class SessionPlayerEntity extends PlayerEntity { if (valid) { // Don't update during session init session.getCollisionManager().updatePlayerBoundingBox(position); } - super.setPosition(position); + this.position = position.add(0, definition.offset(), 0); } /** @@ -232,6 +249,8 @@ public class SessionPlayerEntity extends PlayerEntity { protected void updateAttribute(Attribute javaAttribute, List newAttributes) { if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) { session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute)); + } else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) { + this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute); } else { super.updateAttribute(javaAttribute, newAttributes); } @@ -295,6 +314,7 @@ public class SessionPlayerEntity extends PlayerEntity { public void resetAttributes() { attributes.clear(); maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue(); + blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(geyserId); @@ -307,13 +327,24 @@ public class SessionPlayerEntity extends PlayerEntity { this.setAirSupply(getMaxAir()); } + public void setVehicleInput(Vector2f vehicleInput) { + this.vehicleInput = Vector2f.from( + MathUtils.clamp(vehicleInput.getX(), -1.0f, 1.0f), + MathUtils.clamp(vehicleInput.getY(), -1.0f, 1.0f) + ); + } + + public void setVehicleJumpStrength(int vehicleJumpStrength) { + this.vehicleJumpStrength = MathUtils.constrain(vehicleJumpStrength, 0, 100); + } + 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(); + BedrockDimension bedrockDimension = session.getBedrockDimension(); return bedrockDimension.minY() - 40; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoostableVehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoostableVehicleComponent.java new file mode 100644 index 000000000..41224012d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoostableVehicleComponent.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2023 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.vehicle; + +import org.cloudburstmc.math.TrigMath; +import org.geysermc.geyser.entity.type.LivingEntity; + +public class BoostableVehicleComponent extends VehicleComponent { + private int boostLength; + private int boostTicks = 1; + + public BoostableVehicleComponent(T vehicle, float stepHeight) { + super(vehicle, stepHeight); + } + + public void startBoost(int boostLength) { + this.boostLength = boostLength; + this.boostTicks = 1; + } + + public float getBoostMultiplier() { + if (isBoosting()) { + return 1.0f + 1.15f * TrigMath.sin((float) boostTicks / (float) boostLength * TrigMath.PI); + } + return 1.0f; + } + + public boolean isBoosting() { + return boostTicks <= boostLength; + } + + public void tickBoost() { + if (isBoosting()) { + boostTicks++; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/CamelVehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/CamelVehicleComponent.java new file mode 100644 index 000000000..7d022ed7c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/CamelVehicleComponent.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019-2023 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.vehicle; + +import lombok.Setter; +import org.cloudburstmc.math.vector.Vector2f; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; + +public class CamelVehicleComponent extends VehicleComponent { + private static final int STANDING_TICKS = 52; + private static final int DASH_TICKS = 55; + + @Setter + private float horseJumpStrength = 0.42f; // Not sent by vanilla Java server when spawned + + @Setter + private long lastPoseTick; + + private int dashTick; + private int effectJumpBoost; + + public CamelVehicleComponent(CamelEntity vehicle) { + super(vehicle, 1.5f); + } + + public void startDashCooldown() { + // tickVehicle is only called while the vehicle is mounted. Use session ticks to keep + // track of time instead of counting down + this.dashTick = vehicle.getSession().getTicks() + DASH_TICKS; + } + + @Override + public void tickVehicle() { + if (this.dashTick != 0) { + if (vehicle.getSession().getTicks() > this.dashTick) { + vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false); + this.dashTick = 0; + } else { + vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, true); + } + } + + vehicle.setFlag(EntityFlag.CAN_DASH, vehicle.getFlag(EntityFlag.SADDLED) && !isStationary()); + vehicle.updateBedrockMetadata(); + super.tickVehicle(); + } + + @Override + public void onDismount() { + // Prevent camel from getting stuck in dash animation + vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false); + vehicle.updateBedrockMetadata(); + super.onDismount(); + } + + @Override + protected boolean travel(VehicleContext ctx, float speed) { + if (vehicle.isOnGround() && isStationary()) { + vehicle.setMotion(vehicle.getMotion().mul(0, 1, 0)); + } + + return super.travel(ctx, speed); + } + + @Override + protected Vector3f getInputVelocity(VehicleContext ctx, float speed) { + if (isStationary()) { + return Vector3f.ZERO; + } + + SessionPlayerEntity player = vehicle.getSession().getPlayerEntity(); + Vector3f inputVelocity = super.getInputVelocity(ctx, speed); + float jumpStrength = player.getVehicleJumpStrength(); + + if (jumpStrength > 0) { + player.setVehicleJumpStrength(0); + + if (jumpStrength >= 90) { + jumpStrength = 1.0f; + } else { + jumpStrength = 0.4f + 0.4f * jumpStrength / 90.0f; + } + + return inputVelocity.add(Vector3f.createDirectionDeg(0, -player.getYaw()) + .mul(22.2222f * jumpStrength * this.moveSpeed * getVelocityMultiplier(ctx)) + .up(1.4285f * jumpStrength * (this.horseJumpStrength * getJumpVelocityMultiplier(ctx) + (this.effectJumpBoost * 0.1f)))); + } + + return inputVelocity; + } + + @Override + protected Vector2f getVehicleRotation() { + if (isStationary()) { + return Vector2f.from(vehicle.getYaw(), vehicle.getPitch()); + } + return super.getVehicleRotation(); + } + + /** + * Checks if the camel is sitting + * or transitioning to standing pose. + */ + private boolean isStationary() { + // Java checks if sitting using lastPoseTick + return this.lastPoseTick < 0 || vehicle.getSession().getWorldTicks() < this.lastPoseTick + STANDING_TICKS; + } + + @Override + public void setEffect(Effect effect, int effectAmplifier) { + if (effect == Effect.JUMP_BOOST) { + effectJumpBoost = effectAmplifier + 1; + } else { + super.setEffect(effect, effectAmplifier); + } + } + + @Override + public void removeEffect(Effect effect) { + if (effect == Effect.JUMP_BOOST) { + effectJumpBoost = 0; + } else { + super.removeEffect(effect); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java new file mode 100644 index 000000000..e6aaf1daa --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2023 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.vehicle; + +import org.cloudburstmc.math.vector.Vector2f; + +public interface ClientVehicle { + VehicleComponent getVehicleComponent(); + + Vector2f getAdjustedInput(Vector2f input); + + float getVehicleSpeed(); + + boolean isClientControlled(); + + default boolean canWalkOnLava() { + return false; + } + + default boolean canClimb() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java new file mode 100644 index 000000000..db703a3cb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2019-2023 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.vehicle; + +import it.unimi.dsi.fastutil.objects.ObjectDoublePair; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.TrigMath; +import org.cloudburstmc.math.vector.Vector2f; +import org.cloudburstmc.math.vector.Vector3d; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; +import org.geysermc.erosion.util.BlockPositionIterator; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.level.block.Fluid; +import org.geysermc.geyser.level.block.property.Properties; +import org.geysermc.geyser.level.block.type.BedBlock; +import org.geysermc.geyser.level.block.type.Block; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.level.block.type.TrapDoorBlock; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.session.cache.tags.BlockTag; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.collision.SolidCollision; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.MathUtils; +import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; +import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; +import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket; + +public class VehicleComponent { + private static final ObjectDoublePair EMPTY_FLUID_PAIR = ObjectDoublePair.of(Fluid.EMPTY, 0.0); + private static final float MAX_LOGICAL_FLUID_HEIGHT = 8.0f / BlockStateValues.NUM_FLUID_LEVELS; + private static final float BASE_SLIPPERINESS_CUBED = 0.6f * 0.6f * 0.6f; + private static final float MIN_VELOCITY = 0.003f; + + protected final T vehicle; + protected final BoundingBox boundingBox; + + protected float stepHeight; + protected float moveSpeed; + protected double gravity; + protected int effectLevitation; + protected boolean effectSlowFalling; + protected boolean effectWeaving; + + public VehicleComponent(T vehicle, float stepHeight) { + this.vehicle = vehicle; + this.stepHeight = stepHeight; + this.moveSpeed = (float) AttributeType.Builtin.GENERIC_MOVEMENT_SPEED.getDef(); + this.gravity = AttributeType.Builtin.GENERIC_GRAVITY.getDef(); + + double width = vehicle.getBoundingBoxWidth(); + double height = vehicle.getBoundingBoxHeight(); + this.boundingBox = new BoundingBox( + vehicle.getPosition().getX(), + vehicle.getPosition().getY() + height / 2, + vehicle.getPosition().getZ(), + width, height, width + ); + } + + public void setWidth(float width) { + boundingBox.setSizeX(width); + boundingBox.setSizeZ(width); + } + + public void setHeight(float height) { + boundingBox.translate(0, (height - boundingBox.getSizeY()) / 2, 0); + boundingBox.setSizeY(height); + } + + public void moveAbsolute(double x, double y, double z) { + boundingBox.setMiddleX(x); + boundingBox.setMiddleY(y + boundingBox.getSizeY() / 2); + boundingBox.setMiddleZ(z); + } + + public void moveRelative(double x, double y, double z) { + boundingBox.translate(x, y, z); + } + + public void moveRelative(Vector3d vec) { + boundingBox.translate(vec); + } + + public BoundingBox getBoundingBox() { + return this.boundingBox; + } + + public void setEffect(Effect effect, int effectAmplifier) { + switch (effect) { + case LEVITATION -> effectLevitation = effectAmplifier + 1; + case SLOW_FALLING -> effectSlowFalling = true; + case WEAVING -> effectWeaving = true; + } + } + + public void removeEffect(Effect effect) { + switch (effect) { + case LEVITATION -> effectLevitation = 0; + case SLOW_FALLING -> effectSlowFalling = false; + case WEAVING -> effectWeaving = false; + } + } + + public void setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public void setStepHeight(float stepHeight) { + this.stepHeight = MathUtils.clamp(stepHeight, 1.0f, 10.0f); + } + + public void setGravity(double gravity) { + this.gravity = MathUtils.constrain(gravity, -1.0, 1.0); + } + + public Vector3d correctMovement(Vector3d movement) { + return vehicle.getSession().getCollisionManager().correctMovement( + movement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava() + ); + } + + public void onMount() { + vehicle.getSession().getPlayerEntity().setVehicleInput(Vector2f.ZERO); + vehicle.getSession().getPlayerEntity().setVehicleJumpStrength(0); + } + + public void onDismount() { + // + } + + /** + * Called every session tick while the player is mounted on the vehicle. + */ + public void tickVehicle() { + if (!vehicle.isClientControlled()) { + return; + } + + VehicleContext ctx = new VehicleContext(); + ctx.loadSurroundingBlocks(); + + ObjectDoublePair fluidHeight = updateFluidMovement(ctx); + switch (fluidHeight.left()) { + case WATER -> waterMovement(ctx); + case LAVA -> { + if (vehicle.canWalkOnLava() && ctx.centerBlock().is(Blocks.LAVA)) { + landMovement(ctx); + } else { + lavaMovement(ctx, fluidHeight.rightDouble()); + } + } + case EMPTY -> landMovement(ctx); + } + } + + /** + * Adds velocity of all colliding fluids to the vehicle, and returns the height of the fluid to use for movement. + * + * @param ctx context + * @return type and height of fluid to use for movement + */ + protected ObjectDoublePair updateFluidMovement(VehicleContext ctx) { + BoundingBox box = boundingBox.clone(); + box.expand(-0.001); + + Vector3d min = box.getMin(); + Vector3d max = box.getMax(); + + BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ()); + + double waterHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.WATER, 0.014, min.getY()); + double lavaHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.LAVA, vehicle.getSession().getDimensionType().ultrawarm() ? 0.007 : 0.007 / 3, min.getY()); + + // Apply upward motion if the vehicle is a Strider, and it is submerged in lava + if (lavaHeight > 0 && vehicle.getDefinition().entityType() == EntityType.STRIDER) { + Vector3i blockPos = ctx.centerPos().toInt(); + if (!CollisionManager.FLUID_COLLISION.isBelow(blockPos.getY(), boundingBox) || ctx.getBlock(blockPos.up()).is(Blocks.LAVA)) { + vehicle.setMotion(vehicle.getMotion().mul(0.5f).add(0, 0.05f, 0)); + } else { + vehicle.setOnGround(true); + } + } + + // Water movement has priority over lava movement + if (waterHeight > 0) { + return ObjectDoublePair.of(Fluid.WATER, waterHeight); + } + + if (lavaHeight > 0) { + return ObjectDoublePair.of(Fluid.LAVA, lavaHeight); + } + + return EMPTY_FLUID_PAIR; + } + + /** + * Calculates how deep the vehicle is in a fluid, and applies its velocity. + * + * @param ctx context + * @param iter iterator of colliding blocks + * @param fluid type of fluid + * @param speed multiplier for fluid motion + * @param minY minY of the bounding box used to check for fluid collision; not exactly the same as the vehicle's bounding box + * @return height of fluid compared to minY + */ + protected double getFluidHeightAndApplyMovement(VehicleContext ctx, BlockPositionIterator iter, Fluid fluid, double speed, double minY) { + Vector3d totalVelocity = Vector3d.ZERO; + double maxFluidHeight = 0; + int fluidBlocks = 0; + + for (iter.reset(); iter.hasNext(); iter.next()) { + int blockId = ctx.getBlockId(iter); + if (BlockStateValues.getFluid(blockId) != fluid) { + continue; + } + + Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ()); + float worldFluidHeight = getWorldFluidHeight(fluid, blockId); + + double vehicleFluidHeight = blockPos.getY() + worldFluidHeight - minY; + if (vehicleFluidHeight < 0) { + // Vehicle is not submerged in this fluid block + continue; + } + + // flowBlocked is only used when determining if a falling fluid should drag the vehicle downwards. + // If this block is not a falling fluid, set to true to avoid unnecessary checks. + boolean flowBlocked = worldFluidHeight != 1; + + Vector3d velocity = Vector3d.ZERO; + for (Direction direction : Direction.HORIZONTAL) { + Vector3i adjacentBlockPos = blockPos.add(direction.getUnitVector()); + int adjacentBlockId = ctx.getBlockId(adjacentBlockPos); + Fluid adjacentFluid = BlockStateValues.getFluid(adjacentBlockId); + + float fluidHeightDiff = 0; + if (adjacentFluid == fluid) { + fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - getLogicalFluidHeight(fluid, adjacentBlockId); + } else if (adjacentFluid == Fluid.EMPTY) { + // If the adjacent block is not a fluid and does not have collision, + // check if there is a fluid under it + BlockCollision adjacentBlockCollision = BlockUtils.getCollision(adjacentBlockId); + if (adjacentBlockCollision == null) { + float adjacentFluidHeight = getLogicalFluidHeight(fluid, ctx.getBlockId(adjacentBlockPos.add(Direction.DOWN.getUnitVector()))); + if (adjacentFluidHeight != -1) { // Only care about same type of fluid + fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - (adjacentFluidHeight - MAX_LOGICAL_FLUID_HEIGHT); + } + } else if (!flowBlocked) { + // No need to check if flow is already blocked from another direction, or if this isn't a falling fluid. + flowBlocked = isFlowBlocked(fluid, adjacentBlockId); + } + } + + if (fluidHeightDiff != 0) { + velocity = velocity.add(direction.getUnitVector().toDouble().mul(fluidHeightDiff)); + } + } + + if (worldFluidHeight == 1) { // If falling fluid + // If flow is not blocked, check if it is blocked for the fluid above + if (!flowBlocked) { + Vector3i blockPosUp = blockPos.up(); + for (Direction direction : Direction.HORIZONTAL) { + flowBlocked = isFlowBlocked(fluid, ctx.getBlockId(blockPosUp.add(direction.getUnitVector()))); + if (flowBlocked) { + break; + } + } + } + + if (flowBlocked) { + velocity = javaNormalize(velocity).add(0.0, -6.0, 0.0); + } + } + + velocity = javaNormalize(velocity); + + maxFluidHeight = Math.max(vehicleFluidHeight, maxFluidHeight); + if (maxFluidHeight < 0.4) { + velocity = velocity.mul(maxFluidHeight); + } + + totalVelocity = totalVelocity.add(velocity); + fluidBlocks++; + } + + if (!totalVelocity.equals(Vector3d.ZERO)) { + Vector3f motion = vehicle.getMotion(); + + totalVelocity = javaNormalize(totalVelocity.mul(1.0 / fluidBlocks)); + totalVelocity = totalVelocity.mul(speed); + + if (totalVelocity.length() < 0.0045 && Math.abs(motion.getX()) < MIN_VELOCITY && Math.abs(motion.getZ()) < MIN_VELOCITY) { + totalVelocity = javaNormalize(totalVelocity).mul(0.0045); + } + + vehicle.setMotion(motion.add(totalVelocity.toFloat())); + } + + return maxFluidHeight; + } + + /** + * Java edition returns the zero vector if the length of the input vector is less than 0.0001 + */ + protected Vector3d javaNormalize(Vector3d vec) { + double len = vec.length(); + return len < 1.0E-4 ? Vector3d.ZERO : Vector3d.from(vec.getX() / len, vec.getY() / len, vec.getZ() / len); + } + + protected float getWorldFluidHeight(Fluid fluidType, int blockId) { + return (float) switch (fluidType) { + case WATER -> BlockStateValues.getWaterHeight(blockId); + case LAVA -> BlockStateValues.getLavaHeight(blockId); + case EMPTY -> -1; + }; + } + + protected float getLogicalFluidHeight(Fluid fluidType, int blockId) { + return Math.min(getWorldFluidHeight(fluidType, blockId), MAX_LOGICAL_FLUID_HEIGHT); + } + + protected boolean isFlowBlocked(Fluid fluid, int adjacentBlockId) { + if (BlockState.of(adjacentBlockId).is(Blocks.ICE)) { + return false; + } + + if (BlockStateValues.getFluid(adjacentBlockId) == fluid) { + return false; + } + + // TODO: supposed to check if the opposite face of the block touching the fluid is solid, instead of SolidCollision + return BlockUtils.getCollision(adjacentBlockId) instanceof SolidCollision; + } + + protected void waterMovement(VehicleContext ctx) { + double gravity = getGravity(); + float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier + double originalY = ctx.centerPos().getY(); + boolean falling = vehicle.getMotion().getY() <= 0; + + // NOT IMPLEMENTED: depth strider and dolphins grace + + boolean horizontalCollision = travel(ctx, 0.02f); + + if (horizontalCollision && isClimbing(ctx)) { + vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.2f, vehicle.getMotion().getZ())); + } + + vehicle.setMotion(vehicle.getMotion().mul(drag, 0.8f, drag)); + vehicle.setMotion(getFluidGravity(gravity, falling)); + + if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) { + vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ())); + } + } + + protected void lavaMovement(VehicleContext ctx, double lavaHeight) { + double gravity = getGravity(); + double originalY = ctx.centerPos().getY(); + boolean falling = vehicle.getMotion().getY() <= 0; + + boolean horizontalCollision = travel(ctx, 0.02f); + + if (lavaHeight <= (boundingBox.getSizeY() * 0.85 < 0.4 ? 0.0 : 0.4)) { // Swim height + vehicle.setMotion(vehicle.getMotion().mul(0.5f, 0.8f, 0.5f)); + vehicle.setMotion(getFluidGravity(gravity, falling)); + } else { + vehicle.setMotion(vehicle.getMotion().mul(0.5f)); + } + + vehicle.setMotion(vehicle.getMotion().down((float) (gravity / 4.0))); + + if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) { + vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ())); + } + } + + protected void landMovement(VehicleContext ctx) { + double gravity = getGravity(); + float slipperiness = BlockStateValues.getSlipperiness(getVelocityBlock(ctx)); + float drag = vehicle.isOnGround() ? 0.91f * slipperiness : 0.91f; + float speed = vehicle.getVehicleSpeed() * (vehicle.isOnGround() ? BASE_SLIPPERINESS_CUBED / (slipperiness * slipperiness * slipperiness) : 0.1f); + + boolean horizontalCollision = travel(ctx, speed); + + if (isClimbing(ctx)) { + Vector3f motion = vehicle.getMotion(); + vehicle.setMotion( + Vector3f.from( + MathUtils.clamp(motion.getX(), -0.15f, 0.15f), + horizontalCollision ? 0.2f : Math.max(motion.getY(), -0.15f), + MathUtils.clamp(motion.getZ(), -0.15f, 0.15f) + ) + ); + // NOT IMPLEMENTED: climbing in powdered snow + } + + if (effectLevitation > 0) { + vehicle.setMotion(vehicle.getMotion().up((0.05f * effectLevitation - vehicle.getMotion().getY()) * 0.2f)); + } else { + vehicle.setMotion(vehicle.getMotion().down((float) gravity)); + // NOT IMPLEMENTED: slow fall when in unloaded chunk + } + + vehicle.setMotion(vehicle.getMotion().mul(drag, 0.98f, drag)); + } + + protected boolean shouldApplyFluidJumpBoost(VehicleContext ctx, double originalY) { + BoundingBox box = boundingBox.clone(); + box.translate(vehicle.getMotion().toDouble().up(0.6f - ctx.centerPos().getY() + originalY)); + box.expand(-1.0E-7); + + BlockPositionIterator iter = vehicle.getSession().getCollisionManager().collidableBlocksIterator(box); + for (iter.reset(); iter.hasNext(); iter.next()) { + int blockId = ctx.getBlockId(iter); + + // Also check for fluids + BlockCollision blockCollision = BlockUtils.getCollision(blockId); + if (blockCollision == null && BlockStateValues.getFluid(blockId) != Fluid.EMPTY) { + blockCollision = CollisionManager.SOLID_COLLISION; + } + + if (blockCollision != null && blockCollision.checkIntersection(iter.getX(), iter.getY(), iter.getZ(), box)) { + return false; + } + } + + return true; + } + + protected Vector3f getFluidGravity(double gravity, boolean falling) { + Vector3f motion = vehicle.getMotion(); + if (gravity != 0 && !vehicle.getFlag(EntityFlag.SPRINTING)) { + float newY = (float) (motion.getY() - gravity / 16); + if (falling && Math.abs(motion.getY() - 0.005f) >= MIN_VELOCITY && Math.abs(newY) < MIN_VELOCITY) { + newY = -MIN_VELOCITY; + } + return Vector3f.from(motion.getX(), newY, motion.getZ()); + } + return motion; + } + + /** + * Check if any blocks the vehicle is colliding with should multiply movement. (Cobweb, powder snow, berry bush) + *

+ * This is different from the speed factor of a block the vehicle is standing on, such as soul sand. + * + * @param ctx context + * @return the multiplier + */ + protected @Nullable Vector3f getBlockMovementMultiplier(VehicleContext ctx) { + BoundingBox box = boundingBox.clone(); + box.expand(-1.0E-7); + + Vector3i min = box.getMin().toInt(); + Vector3i max = box.getMax().toInt(); + + // Iterate xyz backwards + // Minecraft iterates forwards but only the last multiplier affects movement + for (int x = max.getX(); x >= min.getX(); x--) { + for (int y = max.getY(); y >= min.getY(); y--) { + for (int z = max.getZ(); z >= min.getZ(); z--) { + Block block = ctx.getBlock(x, y, z).block(); + Vector3f multiplier = null; + + if (block == Blocks.COBWEB) { + if (effectWeaving) { + multiplier = Vector3f.from(0.5, 0.25, 0.5); + } else { + multiplier = Vector3f.from(0.25, 0.05f, 0.25); + } + } else if (block == Blocks.POWDER_SNOW) { + multiplier = Vector3f.from(0.9f, 1.5, 0.9f); + } else if (block == Blocks.SWEET_BERRY_BUSH) { + multiplier = Vector3f.from(0.8f, 0.75, 0.8f); + } + + if (multiplier != null) { + return multiplier; + } + } + } + } + + return null; + } + + protected void applyBlockCollisionEffects(VehicleContext ctx) { + BoundingBox box = boundingBox.clone(); + box.expand(-1.0E-7); + + Vector3i min = box.getMin().toInt(); + Vector3i max = box.getMax().toInt(); + + BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ()); + for (iter.reset(); iter.hasNext(); iter.next()) { + BlockState blockState = ctx.getBlock(iter); + + if (blockState.is(Blocks.HONEY_BLOCK)) { + onHoneyBlockCollision(); + } else if (blockState.is(Blocks.BUBBLE_COLUMN)) { + onBubbleColumnCollision(blockState.getValue(Properties.DRAG)); + } + } + } + + protected void onHoneyBlockCollision() { + if (vehicle.isOnGround() || vehicle.getMotion().getY() >= -0.08f) { + return; + } + + // NOT IMPLEMENTED: don't slide if inside the honey block + Vector3f motion = vehicle.getMotion(); + float mul = motion.getY() < -0.13f ? -0.05f / motion.getY() : 1; + vehicle.setMotion(Vector3f.from(motion.getX() * mul, -0.05f, motion.getZ() * mul)); + } + + protected void onBubbleColumnCollision(boolean drag) { + Vector3f motion = vehicle.getMotion(); + vehicle.setMotion(Vector3f.from( + motion.getX(), + drag ? Math.max(-0.3f, motion.getY() - 0.03f) : Math.min(0.7f, motion.getY() + 0.06f), + motion.getZ() + )); + } + + /** + * Calculates the next position of the vehicle while checking for collision and adjusting velocity. + * + * @return true if there was a horizontal collision + */ + protected boolean travel(VehicleContext ctx, float speed) { + Vector3f motion = vehicle.getMotion(); + + // Java only does this client side + motion = motion.mul(0.98f); + + motion = Vector3f.from( + Math.abs(motion.getX()) < MIN_VELOCITY ? 0 : motion.getX(), + Math.abs(motion.getY()) < MIN_VELOCITY ? 0 : motion.getY(), + Math.abs(motion.getZ()) < MIN_VELOCITY ? 0 : motion.getZ() + ); + + // !isImmobile + if (vehicle.isAlive()) { + motion = motion.add(getInputVelocity(ctx, speed)); + } + + Vector3f movementMultiplier = getBlockMovementMultiplier(ctx); + if (movementMultiplier != null) { + motion = motion.mul(movementMultiplier); + } + + // Check world border before blocks + Vector3d correctedMovement = vehicle.getSession().getWorldBorder().correctMovement(boundingBox, motion.toDouble()); + correctedMovement = vehicle.getSession().getCollisionManager().correctMovement( + correctedMovement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava() + ); + + boundingBox.translate(correctedMovement); + ctx.loadSurroundingBlocks(); // Context must be reloaded after vehicle is moved + + // Non-zero values indicate a collision on that axis + Vector3d moveDiff = motion.toDouble().sub(correctedMovement); + + vehicle.setOnGround(moveDiff.getY() != 0 && motion.getY() < 0); + boolean horizontalCollision = moveDiff.getX() != 0 || moveDiff.getZ() != 0; + + boolean bounced = false; + if (vehicle.isOnGround()) { + Block landingBlock = getLandingBlock(ctx).block(); + + if (landingBlock == Blocks.SLIME_BLOCK) { + motion = Vector3f.from(motion.getX(), -motion.getY(), motion.getZ()); + bounced = true; + + // Slow horizontal movement + float absY = Math.abs(motion.getY()); + if (absY < 0.1f) { + float mul = 0.4f + absY * 0.2f; + motion = motion.mul(mul, 1.0f, mul); + } + } else if (landingBlock instanceof BedBlock) { + motion = Vector3f.from(motion.getX(), -motion.getY() * 0.66f, motion.getZ()); + bounced = true; + } + } + + // Set motion to 0 if a movement multiplier was used, else set to 0 on each axis with a collision + if (movementMultiplier != null) { + motion = Vector3f.ZERO; + } else { + motion = motion.mul( + moveDiff.getX() == 0 ? 1 : 0, + moveDiff.getY() == 0 || bounced ? 1 : 0, + moveDiff.getZ() == 0 ? 1 : 0 + ); + } + + // Send the new position to the bedrock client and java server + moveVehicle(ctx.centerPos()); + vehicle.setMotion(motion); + + applyBlockCollisionEffects(ctx); + + float velocityMultiplier = getVelocityMultiplier(ctx); + vehicle.setMotion(vehicle.getMotion().mul(velocityMultiplier, 1.0f, velocityMultiplier)); + + return horizontalCollision; + } + + protected boolean isClimbing(VehicleContext ctx) { + if (!vehicle.canClimb()) { + return false; + } + + BlockState blockState = ctx.centerBlock(); + if (vehicle.getSession().getTagCache().is(BlockTag.CLIMBABLE, blockState.block())) { + return true; + } + + // Check if the vehicle is in an open trapdoor with a ladder of the same direction under it + if (blockState.block() instanceof TrapDoorBlock && blockState.getValue(Properties.OPEN)) { + BlockState ladderState = ctx.getBlock(ctx.centerPos().toInt().down()); + return ladderState.is(Blocks.LADDER) && + ladderState.getValue(Properties.HORIZONTAL_FACING) == blockState.getValue(Properties.HORIZONTAL_FACING); + } + + return false; + } + + /** + * Translates the player's input into velocity. + * + * @param ctx context + * @param speed multiplier for input + * @return velocity + */ + protected Vector3f getInputVelocity(VehicleContext ctx, float speed) { + Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput(); + input = input.mul(0.98f); + input = vehicle.getAdjustedInput(input); + input = normalizeInput(input); + input = input.mul(speed); + + // Match player rotation + float yaw = vehicle.getSession().getPlayerEntity().getYaw(); + float sin = TrigMath.sin(yaw * TrigMath.DEG_TO_RAD); + float cos = TrigMath.cos(yaw * TrigMath.DEG_TO_RAD); + return Vector3f.from(input.getX() * cos - input.getY() * sin, 0, input.getY() * cos + input.getX() * sin); + } + + protected Vector2f normalizeInput(Vector2f input) { + float lenSquared = input.lengthSquared(); + if (lenSquared < 1.0E-7) { + return Vector2f.ZERO; + } else if (lenSquared > 1.0) { + return input.normalize(); + } + return input; + } + + /** + * Gets the rotation to use for the vehicle. This is based on the player's head rotation. + */ + protected Vector2f getVehicleRotation() { + LivingEntity player = vehicle.getSession().getPlayerEntity(); + return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f); + } + + /** + * Sets the new position for the vehicle and sends packets to both the java server and bedrock client. + *

+ * This also updates the session's last vehicle move timestamp. + * @param javaPos the new java position of the vehicle + */ + protected void moveVehicle(Vector3d javaPos) { + Vector3f bedrockPos = javaPos.toFloat(); + Vector2f rotation = getVehicleRotation(); + + MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); + moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId()); + + if (vehicle.isOnGround()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); + } + + if (vehicle.getPosition().getX() != bedrockPos.getX()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); + moveEntityDeltaPacket.setX(bedrockPos.getX()); + } + if (vehicle.getPosition().getY() != bedrockPos.getY()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); + moveEntityDeltaPacket.setY(bedrockPos.getY()); + } + if (vehicle.getPosition().getZ() != bedrockPos.getZ()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); + moveEntityDeltaPacket.setZ(bedrockPos.getZ()); + } + vehicle.setPosition(bedrockPos); + + if (vehicle.getYaw() != rotation.getX()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); + moveEntityDeltaPacket.setYaw(rotation.getX()); + vehicle.setYaw(rotation.getX()); + } + if (vehicle.getPitch() != rotation.getY()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); + moveEntityDeltaPacket.setPitch(rotation.getY()); + vehicle.setPitch(rotation.getY()); + } + if (vehicle.getHeadYaw() != rotation.getX()) { // Same as yaw + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); + moveEntityDeltaPacket.setHeadYaw(rotation.getX()); + vehicle.setHeadYaw(rotation.getX()); + } + + if (!moveEntityDeltaPacket.getFlags().isEmpty()) { + vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket); + } + + ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), rotation.getX(), rotation.getY()); + vehicle.getSession().sendDownstreamPacket(moveVehiclePacket); + vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis()); + } + + protected double getGravity() { + if (!vehicle.getFlag(EntityFlag.HAS_GRAVITY)) { + return 0; + } + + if (vehicle.getMotion().getY() <= 0 && effectSlowFalling) { + return Math.min(0.01, this.gravity); + } + + return this.gravity; + } + + /** + * Finds the position of the main block supporting the vehicle. + * Used when determining slipperiness, speed, etc. + *

+ * Should use {@link VehicleContext#supportingBlockPos()}, instead of calling this directly. + * + * @param ctx context + * @return position of the main block supporting this entity + */ + private @Nullable Vector3i getSupportingBlockPos(VehicleContext ctx) { + Vector3i result = null; + + if (vehicle.isOnGround()) { + BoundingBox box = boundingBox.clone(); + box.extend(0, -1.0E-6, 0); // Extend slightly down + + Vector3i min = box.getMin().toInt(); + Vector3i max = box.getMax().toInt(); + + // Use minY as maxY + BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), min.getY(), max.getZ()); + + double minDistance = Double.MAX_VALUE; + for (iter.reset(); iter.hasNext(); iter.next()) { + Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ()); + int blockId = ctx.getBlockId(iter); + + BlockCollision blockCollision; + if (vehicle.canWalkOnLava()) { + blockCollision = vehicle.getSession().getCollisionManager().getCollisionLavaWalking(blockId, blockPos.getY(), boundingBox); + } else { + blockCollision = BlockUtils.getCollision(blockId); + } + + if (blockCollision != null && blockCollision.checkIntersection(blockPos, box)) { + double distance = ctx.centerPos().distanceSquared(blockPos.toDouble().add(0.5f, 0.5f, 0.5f)); + if (distance <= minDistance) { + minDistance = distance; + result = blockPos; + } + } + } + } + + return result; + } + + /** + * Returns the block that is x amount of blocks under the main supporting block. + */ + protected BlockState getBlockUnderSupport(VehicleContext ctx, float dist) { + Vector3i supportingBlockPos = ctx.supportingBlockPos(); + + Vector3i blockPos; + if (supportingBlockPos != null) { + blockPos = Vector3i.from(supportingBlockPos.getX(), Math.floor(ctx.centerPos().getY() - dist), supportingBlockPos.getZ()); + } else { + blockPos = ctx.centerPos().sub(0, dist, 0).toInt(); + } + + return ctx.getBlock(blockPos); + } + + /** + * The block to use when determining if the vehicle should bounce after landing. Currently just slime and bed blocks. + */ + protected BlockState getLandingBlock(VehicleContext ctx) { + return getBlockUnderSupport(ctx, 0.2f); + } + + /** + * The block to use when calculating slipperiness and speed. If on a slab, this will be the block under the slab. + */ + protected BlockState getVelocityBlock(VehicleContext ctx) { + return getBlockUnderSupport(ctx, 0.500001f); + } + + protected float getVelocityMultiplier(VehicleContext ctx) { + Block block = ctx.centerBlock().block(); + if (block == Blocks.WATER || block == Blocks.BUBBLE_COLUMN) { + return 1.0f; + } + + if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) { + return 0.4f; + } + + block = getVelocityBlock(ctx).block(); + if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) { + return 0.4f; + } + + return 1.0f; + } + + protected float getJumpVelocityMultiplier(VehicleContext ctx) { + Block block = ctx.centerBlock().block(); + if (block == Blocks.HONEY_BLOCK) { + return 0.5f; + } + + block = getVelocityBlock(ctx).block(); + if (block == Blocks.HONEY_BLOCK) { + return 0.5f; + } + + return 1.0f; + } + + protected class VehicleContext { + private Vector3d centerPos; + private Vector3d cachePos; + private BlockState centerBlock; + private Vector3i supportingBlockPos; + private BlockPositionIterator blockIter; + private int[] blocks; + + /** + * Cache frequently used data and blocks used in movement calculations. + *

+ * Can be called multiple times, and must be called at least once before using the VehicleContext. + */ + protected void loadSurroundingBlocks() { + this.centerPos = boundingBox.getBottomCenter(); + + // Reuse block cache if vehicle moved less than 1 block + if (this.cachePos == null || this.cachePos.distanceSquared(this.centerPos) > 1) { + BoundingBox box = boundingBox.clone(); + box.expand(2); + + Vector3i min = box.getMin().toInt(); + Vector3i max = box.getMax().toInt(); + this.blockIter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ()); + this.blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), this.blockIter); + + this.cachePos = this.centerPos; + } + + this.centerBlock = getBlock(this.centerPos.toInt()); + this.supportingBlockPos = null; + } + + protected Vector3d centerPos() { + return this.centerPos; + } + + protected BlockState centerBlock() { + return this.centerBlock; + } + + protected Vector3i supportingBlockPos() { + if (this.supportingBlockPos == null) { + this.supportingBlockPos = getSupportingBlockPos(this); + } + + return this.supportingBlockPos; + } + + protected int getBlockId(int x, int y, int z) { + int index = this.blockIter.getIndex(x, y, z); + if (index == -1) { + vehicle.getSession().getGeyser().getLogger().debug("[client-vehicle] Block cache miss"); + return vehicle.getSession().getGeyser().getWorldManager().getBlockAt(vehicle.getSession(), x, y, z); + } + + return blocks[index]; + } + + protected int getBlockId(Vector3i pos) { + return getBlockId(pos.getX(), pos.getY(), pos.getZ()); + } + + protected int getBlockId(BlockPositionIterator iter) { + return getBlockId(iter.getX(), iter.getY(), iter.getZ()); + } + + protected BlockState getBlock(int x, int y, int z) { + return BlockState.of(getBlockId(x, y, z)); + } + + protected BlockState getBlock(Vector3i pos) { + return BlockState.of(getBlockId(pos.getX(), pos.getY(), pos.getZ())); + } + + protected BlockState getBlock(BlockPositionIterator iter) { + return BlockState.of(getBlockId(iter.getX(), iter.getY(), iter.getZ())); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/erosion/ErosionCancellationException.java b/core/src/main/java/org/geysermc/geyser/erosion/ErosionCancellationException.java new file mode 100644 index 000000000..ae283895b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/erosion/ErosionCancellationException.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.erosion; + +import java.io.Serial; +import java.util.concurrent.CancellationException; + +public class ErosionCancellationException extends CancellationException { + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundHandshakePacketHandler.java b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundHandshakePacketHandler.java index 0b4f03643..88a7e0cd3 100644 --- a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundHandshakePacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundHandshakePacketHandler.java @@ -42,7 +42,6 @@ public final class GeyserboundHandshakePacketHandler extends AbstractGeyserbound public void handleHandshake(GeyserboundHandshakePacket packet) { boolean useTcp = packet.getTransportType().getSocketAddress() == null; GeyserboundPacketHandlerImpl handler = new GeyserboundPacketHandlerImpl(session, useTcp ? new GeyserErosionPacketSender(session) : new NettyPacketSender<>()); - session.setErosionHandler(handler); if (!useTcp) { if (session.getGeyser().getErosionUnixListener() == null) { session.disconnect("Erosion configurations using Unix socket handling are not supported on this hardware!"); @@ -52,6 +51,7 @@ public final class GeyserboundHandshakePacketHandler extends AbstractGeyserbound } else { handler.onConnect(); } + session.setErosionHandler(handler); session.ensureInEventLoop(() -> session.getChunkCache().clear()); } diff --git a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java index c8cbe384b..7202db449 100644 --- a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java +++ b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java @@ -171,10 +171,10 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke @Override public void handleHandshake(GeyserboundHandshakePacket packet) { - this.close(); var handler = new GeyserboundHandshakePacketHandler(this.session); session.setErosionHandler(handler); handler.handleHandshake(packet); + this.close(); } @Override @@ -198,6 +198,17 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke public void close() { this.packetSender.close(); + + if (pendingLookup != null) { + pendingLookup.completeExceptionally(new ErosionCancellationException()); + } + if (pendingBatchLookup != null) { + pendingBatchLookup.completeExceptionally(new ErosionCancellationException()); + } + if (pickBlockLookup != null) { + pickBlockLookup.completeExceptionally(new ErosionCancellationException()); + } + asyncPendingLookups.forEach(($, future) -> future.completeExceptionally(new ErosionCancellationException())); } public int getNextTransactionId() { diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java index e07a62d8a..4a6efbbd4 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java @@ -35,12 +35,12 @@ import java.util.Map; public abstract class GeyserDefineCommandsEventImpl implements GeyserDefineCommandsEvent { private final Map commands; - public GeyserDefineCommandsEventImpl(Map commands) { - this.commands = commands; + public GeyserDefineCommandsEventImpl(Map commands) { + this.commands = Collections.unmodifiableMap(commands); } @Override public @NonNull Map commands() { - return Collections.unmodifiableMap(this.commands); + return this.commands; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 239ffc450..a84f12813 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -43,9 +43,9 @@ import java.util.regex.Pattern; public record GeyserExtensionDescription(@NonNull String id, @NonNull String name, @NonNull String main, + int humanApiVersion, int majorApiVersion, int minorApiVersion, - int patchApiVersion, @NonNull String version, @NonNull List authors) implements ExtensionDescription { @@ -82,9 +82,9 @@ public record GeyserExtensionDescription(@NonNull String id, throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion)); } String[] api = apiVersion.split("\\."); - int majorApi = Integer.parseUnsignedInt(api[0]); - int minorApi = Integer.parseUnsignedInt(api[1]); - int patchApi = Integer.parseUnsignedInt(api[2]); + int humanApi = Integer.parseUnsignedInt(api[0]); + int majorApi = Integer.parseUnsignedInt(api[1]); + int minorApi = Integer.parseUnsignedInt(api[2]); List authors = new ArrayList<>(); if (source.author != null) { @@ -94,7 +94,7 @@ public record GeyserExtensionDescription(@NonNull String id, authors.addAll(source.authors); } - return new GeyserExtensionDescription(id, name, main, majorApi, minorApi, patchApi, version, authors); + return new GeyserExtensionDescription(id, name, main, humanApi, majorApi, minorApi, version, authors); } @NonNull diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 2f0ff1580..a56e00671 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -29,10 +29,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.api.Geyser; +import org.geysermc.api.util.ApiVersion; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.ExtensionEventBus; -import org.geysermc.geyser.api.extension.*; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.api.extension.ExtensionLogger; +import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import org.geysermc.geyser.extension.event.GeyserExtensionEventBus; @@ -40,7 +45,12 @@ import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; import java.io.Reader; -import java.nio.file.*; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -176,16 +186,22 @@ public class GeyserExtensionLoader extends ExtensionLoader { return; } - // Completely different API version - if (description.majorApiVersion() != Geyser.api().majorApiVersion()) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); - return; - } + // Check whether an extensions' requested api version is compatible + ApiVersion.Compatibility compatibility = GeyserApi.api().geyserApiVersion().supportsRequestedVersion( + description.humanApiVersion(), + description.majorApiVersion(), + description.minorApiVersion() + ); - // If the extension requires new API features, being backwards compatible - if (description.minorApiVersion() > Geyser.api().minorApiVersion()) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); - return; + if (compatibility != ApiVersion.Compatibility.COMPATIBLE) { + // Workaround for the switch to the Geyser API version instead of the Base API version in extensions + if (compatibility == ApiVersion.Compatibility.HUMAN_DIFFER && description.humanApiVersion() == 1) { + GeyserImpl.getInstance().getLogger().warning("The extension %s requested the Base API version %s, which is deprecated in favor of specifying the Geyser API version. Please update the extension, or contact its developer." + .formatted(name, description.apiVersion())); + } else { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); + return; + } } GeyserExtensionContainer container = this.loadExtension(path, description); diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 4a7830c90..0dc49f5c1 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -25,19 +25,215 @@ package org.geysermc.geyser.extension.command; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandExecutor; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.CommandDescription; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.incendo.cloud.parser.standard.StringParser.greedyStringParser; public abstract class GeyserExtensionCommand extends GeyserCommand { + private final Extension extension; + private final String rootCommand; - public GeyserExtensionCommand(Extension extension, String name, String description, String permission) { - super(name, description, permission); + public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @NonNull String description, + @NonNull String permission, @Nullable TriState permissionDefault, + boolean playerOnly, boolean bedrockOnly) { + super(name, description, permission, permissionDefault, playerOnly, bedrockOnly); this.extension = extension; + this.rootCommand = Objects.requireNonNull(extension.rootCommand()); + + if (this.rootCommand.isBlank()) { + throw new IllegalStateException("rootCommand of extension " + extension.name() + " may not be blank"); + } } - public Extension extension() { + public final Extension extension() { return this.extension; } + + @Override + public final String rootCommand() { + return this.rootCommand; + } + + public static class Builder implements Command.Builder { + @NonNull private final Extension extension; + @Nullable private Class sourceType; + @Nullable private String name; + @NonNull private String description = ""; + @NonNull private String permission = ""; + @Nullable private TriState permissionDefault; + @Nullable private List aliases; + private boolean suggestedOpOnly = false; // deprecated for removal + private boolean playerOnly = false; + private boolean bedrockOnly = false; + @Nullable private CommandExecutor executor; + + public Builder(@NonNull Extension extension) { + this.extension = Objects.requireNonNull(extension); + } + + @Override + public Command.Builder source(@NonNull Class sourceType) { + this.sourceType = Objects.requireNonNull(sourceType, "command source type"); + return this; + } + + @Override + public Builder name(@NonNull String name) { + this.name = Objects.requireNonNull(name, "command name"); + return this; + } + + @Override + public Builder description(@NonNull String description) { + this.description = Objects.requireNonNull(description, "command description"); + return this; + } + + @Override + public Builder permission(@NonNull String permission) { + this.permission = Objects.requireNonNull(permission, "command permission"); + return this; + } + + @Override + public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { + this.permission = Objects.requireNonNull(permission, "command permission"); + this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue"); + return this; + } + + @Override + public Builder aliases(@NonNull List aliases) { + this.aliases = Objects.requireNonNull(aliases, "command aliases"); + return this; + } + + @SuppressWarnings("removal") // this is our doing + @Override + public Builder suggestedOpOnly(boolean suggestedOpOnly) { + this.suggestedOpOnly = suggestedOpOnly; + if (suggestedOpOnly) { + // the most amount of legacy/deprecated behaviour I'm willing to support + this.permissionDefault = TriState.NOT_SET; + } + return this; + } + + @SuppressWarnings("removal") // this is our doing + @Override + public Builder executableOnConsole(boolean executableOnConsole) { + this.playerOnly = !executableOnConsole; + return this; + } + + @Override + public Command.Builder playerOnly(boolean playerOnly) { + this.playerOnly = playerOnly; + return this; + } + + @Override + public Builder bedrockOnly(boolean bedrockOnly) { + this.bedrockOnly = bedrockOnly; + return this; + } + + @Override + public Builder executor(@NonNull CommandExecutor executor) { + this.executor = Objects.requireNonNull(executor, "command executor"); + return this; + } + + @NonNull + @Override + public GeyserExtensionCommand build() { + // These are captured in the anonymous lambda below and shouldn't change even if the builder does + final Class sourceType = this.sourceType; + final boolean suggestedOpOnly = this.suggestedOpOnly; + final CommandExecutor executor = this.executor; + + if (name == null) { + throw new IllegalArgumentException("name was not provided for a command in extension " + extension.name()); + } + if (sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name()); + } + if (executor == null) { + throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); + } + + // if the source type is a GeyserConnection then it is inherently bedrockOnly + final boolean bedrockOnly = this.bedrockOnly || GeyserConnection.class.isAssignableFrom(sourceType); + // a similar check would exist for executableOnConsole, but there is not a logger type exposed in the api + + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, playerOnly, bedrockOnly) { + + @Override + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) + .optional("args", greedyStringParser()) + .handler(this::execute)); + } + + @Override + protected org.incendo.cloud.Command.Builder.Applicable meta() { + // We don't want to localize the extension command description + return builder -> builder.commandDescription(CommandDescription.commandDescription(description)); + } + + @SuppressWarnings("unchecked") + @Override + public void execute(CommandContext context) { + GeyserCommandSource source = context.sender(); + String[] args = context.getOrDefault("args", " ").split(" "); + + if (sourceType.isInstance(source)) { + executor.execute((T) source, this, args); + return; + } + + @Nullable GeyserSession session = source.connection(); + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } + + // currently, the only subclass of CommandSource exposed in the api is GeyserConnection. + // when this command was registered, we enabled bedrockOnly if the sourceType was a GeyserConnection. + // as a result, the permission checker should handle that case and this method shouldn't even be reached. + source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command."); + } + + @SuppressWarnings("removal") // this is our doing + @Override + public boolean isSuggestedOpOnly() { + return suggestedOpOnly; + } + }; + + if (aliases != null) { + command.aliases = new ArrayList<>(aliases); + } + return command; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java index 80564bdf3..1cf6a794e 100644 --- a/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java @@ -43,13 +43,13 @@ public class CameraDefinitions { static { CAMERA_PRESETS = List.of( - new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), - new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, OptionalBoolean.empty()), - new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), - new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, OptionalBoolean.empty()), - new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)), - new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)), - new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true))); + new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null), + new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null), + new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null), + new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null), + new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.empty(), null, OptionalBoolean.of(false), null), + new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.empty(), null, OptionalBoolean.of(true), null), + new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.empty(), null, OptionalBoolean.of(true), null)); SimpleDefinitionRegistry.Builder builder = SimpleDefinitionRegistry.builder(); for (int i = 0; i < CAMERA_PRESETS.size(); i++) { diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java index 9bef4b08e..3ea9cd112 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -29,6 +29,7 @@ import lombok.Getter; import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.jetbrains.annotations.Range; @@ -61,6 +62,16 @@ public class PlayerInventory extends Inventory { cursor = newCursor; } + /** + * Checks if the player is holding the specified item in either hand + * + * @param item The item to look for + * @return If the player is holding the item in either hand + */ + public boolean isHolding(@NonNull Item item) { + return getItemInHand().asItem() == item || getOffhand().asItem() == item; + } + public GeyserItemStack getItemInHand(@NonNull Hand hand) { return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand(); } @@ -73,6 +84,10 @@ public class PlayerInventory extends Inventory { return items[36 + heldItemSlot]; } + public boolean eitherHandMatchesItem(@NonNull Item item) { + return getItemInHand().asItem() == item || getItemInHand(Hand.OFF_HAND).asItem() == item; + } + public void setItemInHand(@NonNull GeyserItemStack item) { if (36 + heldItemSlot > this.size) { GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index 53b02ef88..9d6f4d3e3 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -162,6 +162,27 @@ public final class ClickPlan { finished = true; } + public Inventory getInventory() { + return inventory; + } + + /** + * Test if the item stacks with another item in the specified slot. + * This will check the simulated inventory without copying. + */ + public boolean canStack(int slot, GeyserItemStack item) { + GeyserItemStack slotItem = simulatedItems.getOrDefault(slot, inventory.getItem(slot)); + return InventoryUtils.canStack(slotItem, item); + } + + /** + * Test if the specified slot is empty. + * This will check the simulated inventory without copying. + */ + public boolean isEmpty(int slot) { + return simulatedItems.getOrDefault(slot, inventory.getItem(slot)).isEmpty(); + } + public GeyserItemStack getItem(int slot) { return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index 05f6ba6cc..a8a711cc2 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -43,6 +43,7 @@ public class StoredItemMappings { private final ItemMapping banner; private final ItemMapping barrier; private final ItemMapping bow; + private final ItemMapping carrotOnAStick; private final ItemMapping compass; private final ItemMapping crossbow; private final ItemMapping egg; @@ -50,7 +51,9 @@ public class StoredItemMappings { private final ItemMapping milkBucket; private final ItemMapping powderSnowBucket; private final ItemMapping shield; + private final ItemMapping totem; private final ItemMapping upgradeTemplate; + private final ItemMapping warpedFungusOnAStick; private final ItemMapping wheat; private final ItemMapping writableBook; private final ItemMapping writtenBook; @@ -59,6 +62,7 @@ public class StoredItemMappings { this.banner = load(itemMappings, Items.WHITE_BANNER); // As of 1.17.10, all banners have the same Bedrock ID this.barrier = load(itemMappings, Items.BARRIER); this.bow = load(itemMappings, Items.BOW); + this.carrotOnAStick = load(itemMappings, Items.CARROT_ON_A_STICK); this.compass = load(itemMappings, Items.COMPASS); this.crossbow = load(itemMappings, Items.CROSSBOW); this.egg = load(itemMappings, Items.EGG); @@ -66,7 +70,9 @@ public class StoredItemMappings { this.milkBucket = load(itemMappings, Items.MILK_BUCKET); this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET); this.shield = load(itemMappings, Items.SHIELD); + this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING); this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE); + this.warpedFungusOnAStick = load(itemMappings, Items.WARPED_FUNGUS_ON_A_STICK); this.wheat = load(itemMappings, Items.WHEAT); this.writableBook = load(itemMappings, Items.WRITABLE_BOOK); this.writtenBook = load(itemMappings, Items.WRITTEN_BOOK); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index 7afd31cc9..2e0c75708 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -32,6 +32,8 @@ import net.kyori.adventure.text.Component; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.GeyserImpl; @@ -78,6 +80,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(bedrockSlot); slotPacket.setItem(inventory.getItem(i).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } } @@ -98,6 +101,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } else if (lastTargetSlot != javaSlot) { // Update the previous target slot to remove repair cost changes @@ -105,6 +109,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(lastTargetSlot)); slotPacket.setItem(inventory.getItem(lastTargetSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } @@ -168,6 +173,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(slot)); slotPacket.setItem(itemData); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java index 5d6214871..9f3d00c57 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.inventory.updater; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -61,6 +63,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(bedrockItems); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); } @@ -73,6 +76,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(inventory.getBedrockId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java index c9f313f2a..3d372c083 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.inventory.updater; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -49,6 +51,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); } @@ -61,6 +64,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(inventory.getBedrockId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/CrafterInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/CrafterInventoryUpdater.java index 4474d420c..315b84c6d 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/CrafterInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/CrafterInventoryUpdater.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.inventory.updater; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -56,6 +58,7 @@ public class CrafterInventoryUpdater extends InventoryUpdater { contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); // inventory and hotbar @@ -67,6 +70,7 @@ public class CrafterInventoryUpdater extends InventoryUpdater { contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); // Crafter result - it doesn't come after the grid, as explained elsewhere. @@ -88,6 +92,7 @@ public class CrafterInventoryUpdater extends InventoryUpdater { packet.setContainerId(containerId); packet.setSlot(translator.javaSlotToBedrock(javaSlot)); packet.setItem(inventory.getItem(javaSlot).getItemData(session)); + packet.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(packet); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java index 7441e66d0..1a46fc02a 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.inventory.updater; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -49,6 +51,7 @@ public class HorseInventoryUpdater extends InventoryUpdater { InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); } @@ -61,6 +64,7 @@ public class HorseInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(4); // Horse GUI? slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java index 68ee334ba..b7ef4720f 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.inventory.updater; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; @@ -45,6 +47,7 @@ public class InventoryUpdater { InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); } @@ -54,6 +57,7 @@ public class InventoryUpdater { slotPacket.setContainerId(ContainerId.INVENTORY); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java index a23385b53..f4f40d6ce 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.inventory.updater; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; @@ -46,6 +48,7 @@ public class UIInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(bedrockSlot); slotPacket.setItem(inventory.getItem(i).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } } @@ -59,6 +62,7 @@ public class UIInventoryUpdater extends InventoryUpdater { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java index 3c0caa60c..301f69a5f 100644 --- a/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java +++ b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java @@ -25,8 +25,7 @@ package org.geysermc.geyser.item.enchantment; -import java.util.List; -import java.util.function.Function; +import it.unimi.dsi.fastutil.ints.IntArrays; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; @@ -35,11 +34,14 @@ import org.geysermc.geyser.item.Items; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet; +import java.util.function.ToIntFunction; /** * @param description only populated if {@link #bedrockEnchantment()} is not null. @@ -69,7 +71,7 @@ public record Enchantment(String identifier, // TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java, // but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name. - String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null; + String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(context.session(), data) : null; return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel, description, anvilCost, exclusiveSet, bedrockEnchantment); @@ -86,21 +88,21 @@ public record Enchantment(String identifier, } // TODO holder set util? - private static HolderSet readHolderSet(@Nullable Object holderSet, Function keyIdMapping) { + private static HolderSet readHolderSet(@Nullable Object holderSet, ToIntFunction keyIdMapping) { if (holderSet == null) { - return new HolderSet(new int[]{}); + return new HolderSet(IntArrays.EMPTY_ARRAY); } if (holderSet instanceof String stringTag) { // Tag if (stringTag.startsWith("#")) { - return new HolderSet(Key.key(stringTag.substring(1))); // Remove '#' at beginning that indicates tag + return new HolderSet(MinecraftKey.key(stringTag.substring(1))); // Remove '#' at beginning that indicates tag } else { - return new HolderSet(new int[]{keyIdMapping.apply(Key.key(stringTag))}); + return new HolderSet(new int[]{keyIdMapping.applyAsInt(MinecraftKey.key(stringTag))}); } } else if (holderSet instanceof List list) { // Assume the list is a list of strings - return new HolderSet(list.stream().map(o -> (String) o).map(Key::key).map(keyIdMapping).mapToInt(Integer::intValue).toArray()); + return new HolderSet(list.stream().map(o -> (String) o).map(Key::key).mapToInt(keyIdMapping).toArray()); } throw new IllegalArgumentException("Holder set must either be a tag, a string ID or a list of string IDs"); } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java b/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java new file mode 100644 index 000000000..c41d14396 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java @@ -0,0 +1,36 @@ +/* + * 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.Nullable; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +public interface BedrockRequiresTagItem { + + void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder); +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/BlockItem.java b/core/src/main/java/org/geysermc/geyser/item/type/BlockItem.java index 30a31a100..c57e58469 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/BlockItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/BlockItem.java @@ -28,6 +28,9 @@ package org.geysermc.geyser.item.type; import org.geysermc.geyser.level.block.type.Block; public class BlockItem extends Item { + // If item is instanceof ItemNameBlockItem + private final boolean treatLikeBlock; + public BlockItem(Builder builder, Block block, Block... otherBlocks) { super(block.javaIdentifier().value(), builder); @@ -36,6 +39,7 @@ public class BlockItem extends Item { for (Block otherBlock : otherBlocks) { registerBlock(otherBlock, this); } + treatLikeBlock = true; } // Use this constructor if the item name is not the same as its primary block @@ -46,5 +50,14 @@ public class BlockItem extends Item { for (Block otherBlock : otherBlocks) { registerBlock(otherBlock, this); } + treatLikeBlock = false; + } + + @Override + public String translationKey() { + if (!treatLikeBlock) { + return super.translationKey(); + } + return "block." + this.javaIdentifier.namespace() + "." + this.javaIdentifier.value(); } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java index 9c637afde..2e7848318 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java @@ -27,6 +27,8 @@ package org.geysermc.geyser.item.type; import it.unimi.dsi.fastutil.ints.IntArrays; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtList; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtType; @@ -41,7 +43,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks; import java.util.ArrayList; import java.util.List; -public class FireworkRocketItem extends Item { +public class FireworkRocketItem extends Item implements BedrockRequiresTagItem { public FireworkRocketItem(String javaIdentifier, Builder builder) { super(javaIdentifier, builder); } @@ -58,14 +60,16 @@ public class FireworkRocketItem extends Item { fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration()); List explosions = fireworks.getExplosions(); - if (explosions.isEmpty()) { - return; + if (!explosions.isEmpty()) { + List explosionNbt = new ArrayList<>(); + for (Fireworks.FireworkExplosion explosion : explosions) { + explosionNbt.add(translateExplosionToBedrock(explosion)); + } + fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt); + } else { + // This is the default firework + fireworksNbt.put("Explosions", NbtList.EMPTY); } - List explosionNbt = new ArrayList<>(); - for (Fireworks.FireworkExplosion explosion : explosions) { - explosionNbt.add(translateExplosionToBedrock(explosion)); - } - fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt); builder.putCompound("Fireworks", fireworksNbt.build()); } @@ -138,4 +142,20 @@ public class FireworkRocketItem extends Item { return null; } } + + @Override + public void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder) { + if (components != null) { + Fireworks fireworks = components.get(DataComponentType.FIREWORKS); + if (fireworks != null) { + // Already translated + return; + } + } + + NbtMapBuilder fireworksNbt = NbtMap.builder(); + fireworksNbt.putByte("Flight", (byte) 1); + fireworksNbt.put("Explosions", NbtList.EMPTY); + builder.putCompound("Fireworks", fireworksNbt.build()); + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 57538565a..a8a477025 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.item.type; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -59,7 +60,7 @@ import java.util.Map; public class Item { private static final Map BLOCK_TO_ITEM = new HashMap<>(); - private final String javaIdentifier; + protected final Key javaIdentifier; private int javaId = -1; private final int stackSize; private final int attackDamage; @@ -68,7 +69,7 @@ public class Item { private final boolean glint; public Item(String javaIdentifier, Builder builder) { - this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern(); + this.javaIdentifier = MinecraftKey.key(javaIdentifier); this.stackSize = builder.stackSize; this.maxDamage = builder.maxDamage; this.attackDamage = builder.attackDamage; @@ -77,7 +78,7 @@ public class Item { } public String javaIdentifier() { - return javaIdentifier; + return javaIdentifier.asString(); } public int javaId() { @@ -108,6 +109,10 @@ public class Item { return false; } + public String translationKey() { + return "item." + javaIdentifier.namespace() + "." + javaIdentifier.value(); + } + /* Translation methods to Bedrock and back */ public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { @@ -170,6 +175,11 @@ public class Item { builder.putInt("RepairCost", repairCost); } + // If the tag exists, it's unbreakable; the value is just weather to show the tooltip. As of Java 1.21 + if (components.getDataComponents().containsKey(DataComponentType.UNBREAKABLE)) { + builder.putByte("Unbreakable", (byte) 1); + } + // Prevents the client from trying to stack items with untranslated components // Relies on correct hash code implementation, and some luck builder.putInt("GeyserHash", components.hashCode()); // TODO: don't rely on this diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java index 86572d60c..d4999c80d 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.item.type; -import com.github.steveice10.mc.auth.data.GameProfile; +import org.geysermc.mcprotocollib.auth.GameProfile; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.session.GeyserSession; diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java index 250c0f7a4..d62a17232 100644 --- a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java @@ -25,17 +25,84 @@ package org.geysermc.geyser.level; +import lombok.ToString; + /** * A data structure to represent what Bedrock believes are the height requirements for a specific dimension. * As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash * the client. - * - * @param minY The minimum height Bedrock Edition will accept. - * @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. - * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. */ -public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) { - public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); - public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); - public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true); +@ToString +public class BedrockDimension { + + public static final int OVERWORLD_ID = 0; + public static final int DEFAULT_NETHER_ID = 1; + public static final int END_ID = 2; + + // Changes if the above-bedrock Nether building workaround is applied + public static int BEDROCK_NETHER_ID = DEFAULT_NETHER_ID; + + public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true, OVERWORLD_ID); + public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false, -1) { + @Override + public int bedrockId() { + return BEDROCK_NETHER_ID; + } + }; + public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true, END_ID); + public static final String NETHER_IDENTIFIER = "minecraft:the_nether"; + + private final int minY; + private final int height; + private final boolean doUpperHeightWarn; + private final int bedrockId; + + /** + * @param minY The minimum height Bedrock Edition will accept. + * @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. + * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. + * @param bedrockId the Bedrock dimension ID of this dimension. + */ + public BedrockDimension(int minY, int height, boolean doUpperHeightWarn, int bedrockId) { + this.minY = minY; + this.height = height; + this.doUpperHeightWarn = doUpperHeightWarn; + this.bedrockId = bedrockId; + } + + /** + * The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension. + * This workaround sets the Nether as the End dimension to ignore this limit. + * + * @param isAboveNetherBedrockBuilding true if we should apply The End workaround + */ + public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) { + // Change dimension ID to the End to allow for building above Bedrock + BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? END_ID : DEFAULT_NETHER_ID; + } + + public static boolean isCustomBedrockNetherId() { + return BEDROCK_NETHER_ID == END_ID; + } + + public int maxY() { + return minY + height; + } + + public int minY() { + return minY; + } + + public int height() { + return height; + } + + public boolean doUpperHeightWarn() { + return doUpperHeightWarn; + } + + public int bedrockId() { + return bedrockId; + } + } diff --git a/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java b/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java index 7d48b90af..7dad1639b 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java @@ -82,11 +82,15 @@ public class GeyserAdvancement { this.rootId = this.advancement.getId(); } else { // Go through our cache, and descend until we find the root ID - GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId()); - if (advancement.getParentId() == null) { - this.rootId = advancement.getId(); + GeyserAdvancement parent = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId()); + if (parent == null) { + // Parent doesn't exist, is invalid, or couldn't be found for another reason + // So assuming there is no parent and this is the root + this.rootId = this.advancement.getId(); + } else if (parent.getParentId() == null) { + this.rootId = parent.getId(); } else { - this.rootId = advancement.getRootId(advancementsCache); + this.rootId = parent.getRootId(advancementsCache); } } } diff --git a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java index 9faa7424c..befcfa4b7 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java @@ -35,6 +35,7 @@ import org.geysermc.erosion.packet.backendbound.BackendboundBatchBlockRequestPac import org.geysermc.erosion.packet.backendbound.BackendboundBlockRequestPacket; import org.geysermc.erosion.packet.backendbound.BackendboundPickBlockPacket; import org.geysermc.erosion.util.BlockPositionIterator; +import org.geysermc.geyser.erosion.ErosionCancellationException; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -49,6 +50,8 @@ public class GeyserWorldManager extends WorldManager { var erosionHandler = session.getErosionHandler().getAsActive(); if (erosionHandler == null) { return session.getChunkCache().getBlockAt(x, y, z); + } else if (session.isClosed()) { + throw new ErosionCancellationException(); } CompletableFuture future = new CompletableFuture<>(); // Boxes erosionHandler.setPendingLookup(future); @@ -61,6 +64,8 @@ public class GeyserWorldManager extends WorldManager { var erosionHandler = session.getErosionHandler().getAsActive(); if (erosionHandler == null) { return super.getBlockAtAsync(session, x, y, z); + } else if (session.isClosed()) { + return CompletableFuture.failedFuture(new ErosionCancellationException()); } CompletableFuture future = new CompletableFuture<>(); // Boxes int transactionId = erosionHandler.getNextTransactionId(); @@ -74,6 +79,8 @@ public class GeyserWorldManager extends WorldManager { var erosionHandler = session.getErosionHandler().getAsActive(); if (erosionHandler == null) { return super.getBlocksAt(session, iter); + } else if (session.isClosed()) { + throw new ErosionCancellationException(); } CompletableFuture future = new CompletableFuture<>(); erosionHandler.setPendingBatchLookup(future); @@ -118,17 +125,14 @@ public class GeyserWorldManager extends WorldManager { return GameMode.SURVIVAL; } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - return false; - } - @NonNull @Override public CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) { var erosionHandler = session.getErosionHandler().getAsActive(); if (erosionHandler == null) { return super.getPickItemComponents(session, x, y, z, addNbtData); + } else if (session.isClosed()) { + return CompletableFuture.failedFuture(new ErosionCancellationException()); } CompletableFuture> future = new CompletableFuture<>(); erosionHandler.setPickBlockLookup(future); diff --git a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java index dd0f4215e..c4592517c 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java @@ -25,15 +25,22 @@ package org.geysermc.geyser.level; +import net.kyori.adventure.key.Key; import org.cloudburstmc.nbt.NbtMap; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; +import org.geysermc.geyser.util.DimensionUtils; /** * Represents the information we store from the current Java dimension * @param piglinSafe Whether piglins and hoglins are safe from conversion in this dimension. * This controls if they have the shaking effect applied in the dimension. + * @param ultrawarm If this dimension is ultrawarm. + * Used when calculating movement in lava for client-side vehicles. + * @param bedrockId the Bedrock dimension ID of this dimension. + * As a Java dimension can be null in some login cases (e.g. GeyserConnect), make sure the player + * is logged in before utilizing this field. */ -public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale) { +public record JavaDimension(int minY, int maxY, boolean piglinSafe, boolean ultrawarm, double worldCoordinateScale, int bedrockId, boolean isNetherLike) { public static JavaDimension read(RegistryEntryContext entry) { NbtMap dimension = entry.data(); @@ -43,9 +50,34 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world // Set if piglins/hoglins should shake boolean piglinSafe = dimension.getBoolean("piglin_safe"); + // Entities in lava move faster in ultrawarm dimensions + boolean ultrawarm = dimension.getBoolean("ultrawarm"); // Load world coordinate scale for the world border - double coordinateScale = dimension.getDouble("coordinate_scale"); + double coordinateScale = dimension.getNumber("coordinate_scale").doubleValue(); // FIXME see if we can change this in the NBT library itself. - return new JavaDimension(minY, maxY, piglinSafe, coordinateScale); + boolean isNetherLike; + // Cache the Bedrock version of this dimension, and base it off the ID - THE ID CAN CHANGE!!! + // https://github.com/GeyserMC/Geyser/issues/4837 + int bedrockId; + Key id = entry.id(); + if ("minecraft".equals(id.namespace())) { + String identifier = id.asString(); + bedrockId = DimensionUtils.javaToBedrock(identifier); + isNetherLike = BedrockDimension.NETHER_IDENTIFIER.equals(identifier); + } else { + // Effects should give is a clue on how this (custom) dimension is supposed to look like + String effects = dimension.getString("effects"); + bedrockId = DimensionUtils.javaToBedrock(effects); + isNetherLike = BedrockDimension.NETHER_IDENTIFIER.equals(effects); + } + + if (minY % 16 != 0) { + throw new RuntimeException("Minimum Y must be a multiple of 16!"); + } + if (maxY % 16 != 0) { + throw new RuntimeException("Maximum Y must be a multiple of 16!"); + } + + return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale, bedrockId, isNetherLike); } } diff --git a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java index b00dc9f98..86d66e209 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java +++ b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java @@ -44,7 +44,7 @@ public record JukeboxSong(String soundEvent, String description) { soundEvent = ""; GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject); } - String description = MessageTranslator.deserializeDescription(data); + String description = MessageTranslator.deserializeDescription(context.session(), data); return new JukeboxSong(soundEvent, description); } } diff --git a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index 4a20771f2..6baf9c2b4 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -185,15 +185,6 @@ public abstract class WorldManager { session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT)); } - /** - * Checks if the given session's player has a permission - * - * @param session The session of the player to check the permission of - * @param permission The permission node to check - * @return True if the player has the requested permission, false if not - */ - public abstract boolean hasPermission(GeyserSession session, String permission); - /** * Returns a list of biome identifiers available on the server. */ diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 01e95fc7a..36e437026 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -36,7 +36,7 @@ import org.geysermc.geyser.registry.BlockRegistries; * Used for block entities if the Java block state contains Bedrock block information. */ public final class BlockStateValues { - public static final int NUM_WATER_LEVELS = 9; + public static final int NUM_FLUID_LEVELS = 9; /** * Checks if a block sticks to other blocks @@ -99,6 +99,25 @@ public final class BlockStateValues { }; } + /** + * Get the type of fluid from the block state, including waterlogged blocks. + * + * @param state BlockState of the block + * @return The type of fluid + */ + public static Fluid getFluid(int state) { + BlockState blockState = BlockState.of(state); + if (blockState.is(Blocks.WATER) || BlockRegistries.WATERLOGGED.get().get(state)) { + return Fluid.WATER; + } + + if (blockState.is(Blocks.LAVA)) { + return Fluid.LAVA; + } + + return Fluid.EMPTY; + } + /** * Get the level of water from the block state. * @@ -127,7 +146,7 @@ public final class BlockStateValues { waterLevel = 0; } if (waterLevel >= 0) { - double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_WATER_LEVELS); + double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_FLUID_LEVELS); // Falling water is a full block if (waterLevel >= 8) { waterHeight = 1; @@ -137,6 +156,39 @@ public final class BlockStateValues { return -1; } + /** + * Get the level of lava from the block state. + * + * @param state BlockState of the block + * @return The lava level or -1 if the block isn't lava + */ + public static int getLavaLevel(int state) { + BlockState blockState = BlockState.of(state); + if (!blockState.is(Blocks.LAVA)) { + return -1; + } + return blockState.getValue(Properties.LEVEL); + } + + /** + * Get the height of lava from the block state + * + * @param state BlockState of the block + * @return The lava height or -1 if the block does not contain lava + */ + public static double getLavaHeight(int state) { + int lavaLevel = BlockStateValues.getLavaLevel(state); + if (lavaLevel >= 0) { + double lavaHeight = 1 - (lavaLevel + 1) / ((double) NUM_FLUID_LEVELS); + // Falling lava is a full block + if (lavaLevel >= 8) { + lavaHeight = 1; + } + return lavaHeight; + } + return -1; + } + /** * Get the slipperiness of a block. * This is used in ItemEntity to calculate the friction on an item as it slides across the ground diff --git a/core/src/main/java/org/geysermc/geyser/level/block/Fluid.java b/core/src/main/java/org/geysermc/geyser/level/block/Fluid.java new file mode 100644 index 000000000..a9693bbf4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/block/Fluid.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2023 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.block; + +public enum Fluid { + WATER, + LAVA, + EMPTY +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/SkullBlock.java b/core/src/main/java/org/geysermc/geyser/level/block/type/SkullBlock.java index c4aae46a2..6e791d674 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/SkullBlock.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/type/SkullBlock.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.level.block.type; -import com.github.steveice10.mc.auth.data.GameProfile; +import org.geysermc.mcprotocollib.auth.GameProfile; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java index b1a93d8ee..395467c02 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java @@ -33,6 +33,8 @@ import org.cloudburstmc.math.vector.Vector3d; @Data @AllArgsConstructor public class BoundingBox implements Cloneable { + private static final double EPSILON = 1.0E-7; + private double middleX; private double middleY; private double middleZ; @@ -57,10 +59,24 @@ public class BoundingBox implements Cloneable { sizeZ += Math.abs(z); } + public void expand(double x, double y, double z) { + sizeX += x; + sizeY += y; + sizeZ += z; + } + + public void translate(Vector3d translate) { + translate(translate.getX(), translate.getY(), translate.getZ()); + } + public void extend(Vector3d extend) { extend(extend.getX(), extend.getY(), extend.getZ()); } + public void expand(double expand) { + expand(expand, expand, expand); + } + public boolean checkIntersection(double offsetX, double offsetY, double offsetZ, BoundingBox otherBox) { return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) && (Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) && @@ -78,6 +94,14 @@ public class BoundingBox implements Cloneable { return Vector3d.from(x, y, z); } + public double getMin(Axis axis) { + return switch (axis) { + case X -> middleX - sizeX / 2; + case Y -> middleY - sizeY / 2; + case Z -> middleZ - sizeZ / 2; + }; + } + public Vector3d getMax() { double x = middleX + sizeX / 2; double y = middleY + sizeY / 2; @@ -85,15 +109,23 @@ public class BoundingBox implements Cloneable { return Vector3d.from(x, y, z); } + public double getMax(Axis axis) { + return switch (axis) { + case X -> middleX + sizeX / 2; + case Y -> middleY + sizeY / 2; + case Z -> middleZ + sizeZ / 2; + }; + } + public Vector3d getBottomCenter() { return Vector3d.from(middleX, middleY - sizeY / 2, middleZ); } private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) { return switch (axis) { - case X -> Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX()); - case Y -> Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY()); - case Z -> Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ()); + case X -> (sizeX + otherBox.getSizeX()) - Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 > EPSILON; + case Y -> (sizeY + otherBox.getSizeY()) - Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 > EPSILON; + case Z -> (sizeZ + otherBox.getSizeZ()) - Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 > EPSILON; }; } diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index 2be4e7a38..a0fb312b4 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -38,6 +38,7 @@ import org.geysermc.erosion.util.BlockPositionIterator; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.property.Properties; @@ -45,7 +46,9 @@ import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.PistonCache; import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.collision.OtherCollision; import org.geysermc.geyser.translator.collision.ScaffoldingCollision; +import org.geysermc.geyser.translator.collision.SolidCollision; import org.geysermc.geyser.util.BlockUtils; import java.text.DecimalFormat; @@ -53,6 +56,8 @@ import java.text.DecimalFormatSymbols; import java.util.Locale; public class CollisionManager { + public static final BlockCollision SOLID_COLLISION = new SolidCollision(null); + public static final BlockCollision FLUID_COLLISION = new OtherCollision(new BoundingBox[]{new BoundingBox(0.5, 0.25, 0.5, 1, 0.5, 1)}); private final GeyserSession session; @@ -128,6 +133,21 @@ public class CollisionManager { playerBoundingBox.setSizeY(playerHeight); } + /** + * Gets the bounding box to use for player movement. + *

+ * This will return either the bounding box of a {@link ClientVehicle}, or the player's own bounding box. + * + * @return the bounding box to use for movement calculations + */ + public BoundingBox getActiveBoundingBox() { + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + return clientVehicle.getVehicleComponent().getBoundingBox(); + } + + return playerBoundingBox; + } + /** * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between * the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons. @@ -150,6 +170,15 @@ public class CollisionManager { Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, Double.parseDouble(Float.toString(bedrockPosition.getZ()))); + // Don't correct position if controlling a vehicle + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + playerBoundingBox.setMiddleX(position.getX()); + playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2); + playerBoundingBox.setMiddleZ(position.getZ()); + + return playerBoundingBox.getBottomCenter(); + } + Vector3d startingPos = playerBoundingBox.getBottomCenter(); Vector3d movement = position.sub(startingPos); Vector3d adjustedMovement = correctPlayerMovement(movement, false, teleported); @@ -173,7 +202,8 @@ public class CollisionManager { // Send corrected position to Bedrock if they differ by too much to prevent de-syncs if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) { PlayerEntity playerEntity = session.getPlayerEntity(); - if (pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { + // Client will dismount if on a vehicle + if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true); } } @@ -268,13 +298,13 @@ public class CollisionManager { if (teleported || (!checkWorld && session.getPistonCache().getPistons().isEmpty())) { // There is nothing to check return movement; } - return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld); + return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld, false); } - public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld) { + public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld, boolean walkOnLava) { Vector3d adjustedMovement = movement; if (!movement.equals(Vector3d.ZERO)) { - adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld); + adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld, walkOnLava); } boolean verticalCollision = adjustedMovement.getY() != movement.getY(); @@ -283,26 +313,27 @@ public class CollisionManager { onGround = onGround || (verticalCollision && falling); if (onGround && horizontalCollision) { Vector3d horizontalMovement = Vector3d.from(movement.getX(), 0, movement.getZ()); - Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld); + Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld, walkOnLava); BoundingBox stretchedBoundingBox = boundingBox.clone(); stretchedBoundingBox.extend(horizontalMovement); - double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld).getY(); + double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld, walkOnLava).getY(); if (maxStepUp < stepUp) { // The player collided with a block above them - boundingBox.translate(0, maxStepUp, 0); - Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, boundingBox, checkWorld); - boundingBox.translate(0, -maxStepUp, 0); + BoundingBox stepUpBoundingBox = boundingBox.clone(); + stepUpBoundingBox.translate(0, maxStepUp, 0); + Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, stepUpBoundingBox, checkWorld, walkOnLava); if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) { stepUpMovement = adjustedStepUpMovement.up(maxStepUp); } } if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) { - boundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ()); + BoundingBox stepUpBoundingBox = boundingBox.clone(); + stepUpBoundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ()); + // Apply the player's remaining vertical movement - double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), boundingBox, checkWorld).getY(); - boundingBox.translate(-stepUpMovement.getX(), -stepUpMovement.getY(), -stepUpMovement.getZ()); + double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), stepUpBoundingBox, checkWorld, walkOnLava).getY(); stepUpMovement = stepUpMovement.up(verticalMovement); adjustedMovement = stepUpMovement; @@ -315,43 +346,53 @@ public class CollisionManager { return vector.getX() * vector.getX() + vector.getZ() * vector.getZ(); } - private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld) { + private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld, boolean walkOnLava) { double movementX = movement.getX(); double movementY = movement.getY(); double movementZ = movement.getZ(); + // Position might change slightly due to floating point error + double originalX = boundingBox.getMiddleX(); + double originalY = boundingBox.getMiddleY(); + double originalZ = boundingBox.getMiddleZ(); + BoundingBox movementBoundingBox = boundingBox.clone(); movementBoundingBox.extend(movement); BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox); if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) { - movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld); + movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld, walkOnLava); boundingBox.translate(0, movementY, 0); } boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX); if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { - movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld); + movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava); boundingBox.translate(0, 0, movementZ); } if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) { - movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld); + movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld, walkOnLava); boundingBox.translate(movementX, 0, 0); } if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { - movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld); + movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava); boundingBox.translate(0, 0, movementZ); } - boundingBox.translate(-movementX, -movementY, -movementZ); + boundingBox.setMiddleX(originalX); + boundingBox.setMiddleY(originalY); + boundingBox.setMiddleZ(originalZ); + return Vector3d.from(movementX, movementY, movementZ); } - private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld) { + private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld, boolean walkOnLava) { for (iter.reset(); iter.hasNext(); iter.next()) { int x = iter.getX(); int y = iter.getY(); int z = iter.getZ(); if (checkWorld) { - BlockCollision blockCollision = BlockUtils.getCollisionAt(session, x, y, z); + int blockId = session.getGeyser().getWorldManager().getBlockAt(session, x, y, z); + + BlockCollision blockCollision = walkOnLava ? getCollisionLavaWalking(blockId, y, boundingBox) : BlockUtils.getCollision(blockId); if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) { offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset); } @@ -364,6 +405,16 @@ public class CollisionManager { return offset; } + /** + * @return the block collision appropriate for entities that can walk on lava (Strider) + */ + public BlockCollision getCollisionLavaWalking(int blockId, int blockY, BoundingBox boundingBox) { + if (BlockStateValues.getLavaLevel(blockId) == 0 && FLUID_COLLISION.isBelow(blockY, boundingBox)) { + return FLUID_COLLISION; + } + return BlockUtils.getCollision(blockId); + } + /** * @return true if the block located at the player's floor position plus 1 would intersect with the player, * were they not sneaking @@ -417,7 +468,7 @@ public class CollisionManager { double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight(); double eyeZ = playerBoundingBox.getMiddleZ(); - eyeY -= 1 / ((double) BlockStateValues.NUM_WATER_LEVELS); // Subtract the height of one water layer + eyeY -= 1 / ((double) BlockStateValues.NUM_FLUID_LEVELS); // Subtract the height of one water layer int blockID = session.getGeyser().getWorldManager().getBlockAt(session, GenericMath.floor(eyeX), GenericMath.floor(eyeY), GenericMath.floor(eyeZ)); double waterHeight = BlockStateValues.getWaterHeight(blockID); diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java b/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java index f14a46999..4821734f3 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java @@ -38,6 +38,7 @@ public enum Direction { EAST(4, Vector3i.UNIT_X, Axis.X, org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction.EAST); public static final Direction[] VALUES = values(); + public static final Direction[] HORIZONTAL = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; private final int reversedId; @Getter diff --git a/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java b/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java index e7cf81d47..741369c46 100644 --- a/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java +++ b/core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java @@ -33,13 +33,17 @@ import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobArmorEquipment import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobEquipmentSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.PlayerHotbarSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityLinkSerializer_v291; -import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityMotionSerializer_v291; import org.cloudburstmc.protocol.bedrock.codec.v390.serializer.PlayerSkinSerializer_v390; import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventoryContentSerializer_v407; import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventorySlotSerializer_v407; import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486; import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557; import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662; +import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.InventoryContentSerializer_v712; +import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.InventorySlotSerializer_v712; +import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.MobArmorEquipmentSerializer_v712; +import org.cloudburstmc.protocol.bedrock.codec.v729.serializer.InventoryContentSerializer_v729; +import org.cloudburstmc.protocol.bedrock.codec.v729.serializer.InventorySlotSerializer_v729; import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket; @@ -119,7 +123,24 @@ class CodecProcessor { /** * Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used. */ - private static final BedrockPacketSerializer INVENTORY_CONTENT_SERIALIZER = new InventoryContentSerializer_v407() { + private static final BedrockPacketSerializer INVENTORY_CONTENT_SERIALIZER_V407 = new InventoryContentSerializer_v407() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) { + throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!"); + } + }; + + /** + * Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used. + */ + private static final BedrockPacketSerializer INVENTORY_CONTENT_SERIALIZER_V712 = new InventoryContentSerializer_v712() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) { + throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!"); + } + }; + + private static final BedrockPacketSerializer INVENTORY_CONTENT_SERIALIZER_V729 = new InventoryContentSerializer_v729() { @Override public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) { throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!"); @@ -129,7 +150,24 @@ class CodecProcessor { /** * Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used. */ - private static final BedrockPacketSerializer INVENTORY_SLOT_SERIALIZER = new InventorySlotSerializer_v407() { + private static final BedrockPacketSerializer INVENTORY_SLOT_SERIALIZER_V407 = new InventorySlotSerializer_v407() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) { + throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!"); + } + }; + + /* + * Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used. + */ + private static final BedrockPacketSerializer INVENTORY_SLOT_SERIALIZER_V712 = new InventorySlotSerializer_v712() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) { + throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!"); + } + }; + + private static final BedrockPacketSerializer INVENTORY_SLOT_SERIALIZER_V729 = new InventorySlotSerializer_v729() { @Override public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) { throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!"); @@ -148,7 +186,16 @@ class CodecProcessor { /** * Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client. */ - private static final BedrockPacketSerializer MOB_ARMOR_EQUIPMENT_SERIALIZER = new MobArmorEquipmentSerializer_v291() { + private static final BedrockPacketSerializer MOB_ARMOR_EQUIPMENT_SERIALIZER_V291 = new MobArmorEquipmentSerializer_v291() { + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) { + } + }; + + /** + * Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client. + */ + private static final BedrockPacketSerializer MOB_ARMOR_EQUIPMENT_SERIALIZER_V712 = new MobArmorEquipmentSerializer_v712() { @Override public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) { } @@ -182,18 +229,9 @@ class CodecProcessor { }; /** - * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v291. + * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client. */ - private static final BedrockPacketSerializer SET_ENTITY_MOTION_SERIALIZER_V291 = new SetEntityMotionSerializer_v291() { - @Override - public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) { - } - }; - - /** - * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662. - */ - private static final BedrockPacketSerializer SET_ENTITY_MOTION_SERIALIZER_V662 = new SetEntityMotionSerializer_v662() { + private static final BedrockPacketSerializer SET_ENTITY_MOTION_SERIALIZER = new SetEntityMotionSerializer_v662() { @Override public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) { } @@ -224,6 +262,27 @@ class CodecProcessor { @SuppressWarnings("unchecked") static BedrockCodec processCodec(BedrockCodec codec) { + boolean is729OrAbove = codec.getProtocolVersion() >= 729; + boolean is712OrAbove = codec.getProtocolVersion() >= 712; + + BedrockPacketSerializer inventoryContentSerializer; + if (is729OrAbove) { + inventoryContentSerializer = INVENTORY_CONTENT_SERIALIZER_V729; + } else if (is712OrAbove) { + inventoryContentSerializer = INVENTORY_CONTENT_SERIALIZER_V712; + } else { + inventoryContentSerializer = INVENTORY_CONTENT_SERIALIZER_V407; + } + + BedrockPacketSerializer inventorySlotSerializer; + if (is729OrAbove) { + inventorySlotSerializer = INVENTORY_SLOT_SERIALIZER_V729; + } else if (is712OrAbove) { + inventorySlotSerializer = INVENTORY_SLOT_SERIALIZER_V712; + } else { + inventorySlotSerializer = INVENTORY_SLOT_SERIALIZER_V407; + } + BedrockCodec.Builder codecBuilder = codec.toBuilder() // Illegal unused serverbound EDU packets .updateSerializer(PhotoTransferPacket.class, ILLEGAL_SERIALIZER) @@ -252,15 +311,15 @@ class CodecProcessor { .updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER) .updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER) // Illegal when serverbound due to Geyser specific setup - .updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER) - .updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER) + .updateSerializer(InventoryContentPacket.class, inventoryContentSerializer) + .updateSerializer(InventorySlotPacket.class, inventorySlotSerializer) // Ignored only when serverbound .updateSerializer(BossEventPacket.class, BOSS_EVENT_SERIALIZER) - .updateSerializer(MobArmorEquipmentPacket.class, MOB_ARMOR_EQUIPMENT_SERIALIZER) + .updateSerializer(MobArmorEquipmentPacket.class, is712OrAbove ? MOB_ARMOR_EQUIPMENT_SERIALIZER_V712 : MOB_ARMOR_EQUIPMENT_SERIALIZER_V291) .updateSerializer(PlayerHotbarPacket.class, PLAYER_HOTBAR_SERIALIZER) .updateSerializer(PlayerSkinPacket.class, PLAYER_SKIN_SERIALIZER) .updateSerializer(SetEntityDataPacket.class, SET_ENTITY_DATA_SERIALIZER) - .updateSerializer(SetEntityMotionPacket.class, SET_ENTITY_MOTION_SERIALIZER_V662) + .updateSerializer(SetEntityMotionPacket.class, SET_ENTITY_MOTION_SERIALIZER) .updateSerializer(SetEntityLinkPacket.class, SET_ENTITY_LINK_SERIALIZER) // Valid serverbound packets where reading of some fields can be skipped .updateSerializer(MobEquipmentPacket.class, MOB_EQUIPMENT_SERIALIZER) diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index c79ef365d..c188e92bb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -29,6 +29,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671; import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685; +import org.cloudburstmc.protocol.bedrock.codec.v686.Bedrock_v686; +import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712; +import org.cloudburstmc.protocol.bedrock.codec.v729.Bedrock_v729; import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec; @@ -43,17 +46,13 @@ import java.util.StringJoiner; */ public final class GameProtocol { - // Surprise protocol bump WOW - private static final BedrockCodec BEDROCK_V686 = Bedrock_v685.CODEC.toBuilder() - .protocolVersion(686) - .minecraftVersion("1.21.2") - .build(); - /** * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(BEDROCK_V686); + public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v729.CODEC.toBuilder() + .minecraftVersion("1.21.31") + .build()); /** * A list of all supported Bedrock versions that can join Geyser @@ -73,7 +72,15 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() .minecraftVersion("1.21.0/1.21.1") .build())); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v686.CODEC.toBuilder() + .minecraftVersion("1.21.2/1.21.3") + .build())); + SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v712.CODEC.toBuilder() + .minecraftVersion("1.21.20 - 1.21.23") + .build())); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.21.30/1.21.31") + .build()); } /** @@ -96,6 +103,10 @@ public final class GameProtocol { return session.getUpstream().getProtocolVersion() < Bedrock_v685.CODEC.getProtocolVersion(); } + public static boolean isPre1_21_2(GeyserSession session) { + return session.getUpstream().getProtocolVersion() < Bedrock_v686.CODEC.getProtocolVersion(); + } + /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index c7aabb806..5c48df1f9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled; import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.compat.BedrockCompat; -import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622; import org.cloudburstmc.protocol.bedrock.data.ExperimentData; import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm; import org.cloudburstmc.protocol.bedrock.data.ResourcePackType; @@ -120,10 +119,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.disconnect(disconnectMessage); return false; } else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - if (protocolVersion < Bedrock_v622.CODEC.getProtocolVersion()) { - // https://github.com/GeyserMC/Geyser/issues/4378 - session.getUpstream().getSession().setCodec(BedrockCompat.CODEC_LEGACY); - } + // A note on the following line: various older client versions have different forms of DisconnectPacket. + // Using only the latest BedrockCompat for such clients leads to inaccurate disconnect messages: https://github.com/GeyserMC/Geyser/issues/4378 + // This updates the BedrockCompat protocol if necessary: + session.getUpstream().getSession().setCodec(BedrockCompat.disconnectCompat(protocolVersion)); + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return false; } else { @@ -209,7 +209,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePackManifest.Header header = pack.manifest().header(); resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(), - "", header.uuid().toString(), false, false)); + "", header.uuid().toString(), false, false, false)); } resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); @@ -274,10 +274,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { private boolean couldLoginUserByName(String bedrockUsername) { if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { - String refreshToken = geyser.refreshTokenFor(bedrockUsername); - if (refreshToken != null) { + String authChain = geyser.authChainFor(bedrockUsername); + if (authChain != null) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); - session.authenticateWithRefreshToken(refreshToken); + session.authenticateWithAuthChain(authChain); return true; } } diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index a67bd8a32..efbd8bdff 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -84,10 +84,11 @@ public final class GeyserServer { /* The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client */ - private static final int MINECRAFT_VERSION_BYTES_LENGTH = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length; + private static final String PING_VERSION = pingVersion(); + private static final int PING_VERSION_BYTES_LENGTH = PING_VERSION.getBytes(StandardCharsets.UTF_8).length; private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length; /** - * The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length. + * The MOTD, sub-MOTD and Minecraft version ({@link #PING_VERSION_BYTES_LENGTH}) combined cannot reach this length. */ private static final int MAGIC_RAKNET_LENGTH = 338; @@ -143,11 +144,6 @@ public final class GeyserServer { this.proxiedAddresses = null; } - // It's set to 0 only if no system property or manual config value was set - if (geyser.getConfig().getBedrock().broadcastPort() == 0) { - geyser.getConfig().getBedrock().setBroadcastPort(geyser.getConfig().getBedrock().port()); - } - this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort(); } @@ -316,7 +312,7 @@ public final class GeyserServer { .gameType("Survival") // Can only be Survival or Creative as of 1.16.210.59 .nintendoLimited(false) .protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. + .version(PING_VERSION) .ipv4Port(this.broadcastPort) .ipv6Port(this.broadcastPort) .serverId(channel.config().getOption(RakChannelOption.RAK_GUID)); @@ -367,15 +363,15 @@ public final class GeyserServer { // We don't know why, though byte[] motdArray = pong.motd().getBytes(StandardCharsets.UTF_8); int subMotdLength = pong.subMotd().getBytes(StandardCharsets.UTF_8).length; - if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) { + if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH)) { // Shorten the sub-MOTD first since that only appears locally if (subMotdLength > BRAND_BYTES_LENGTH) { pong.subMotd(GeyserImpl.NAME); subMotdLength = BRAND_BYTES_LENGTH; } - if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) { + if (motdArray.length > (MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH - subMotdLength)) { // If the top MOTD is still too long, we chop it down - byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength]; + byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH - subMotdLength]; System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length); pong.motd(new String(newMotdArray, StandardCharsets.UTF_8)); } @@ -390,6 +386,17 @@ public final class GeyserServer { return pong; } + private static String pingVersion() { + // BedrockPong version is required to not be empty as of 1.16.210.59. + // Can only contain . and numbers, so use the latest version instead of sending all + var version = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); + var versionSplit = version.split("/"); + if (versionSplit.length > 1) { + version = versionSplit[versionSplit.length - 1]; + } + return version; + } + /** * @return the throwable from the given supplier, or the throwable caught while calling the supplier. */ diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java index 958e88288..739c1c25e 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java @@ -27,15 +27,30 @@ package org.geysermc.geyser.network.netty; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; -import io.netty.channel.*; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.unix.PreferredDirectByteBufAllocator; -import io.netty.handler.codec.haproxy.*; +import io.netty.handler.codec.haproxy.HAProxyCommand; +import io.netty.handler.codec.haproxy.HAProxyMessage; +import io.netty.handler.codec.haproxy.HAProxyMessageEncoder; +import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; +import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; import io.netty.util.concurrent.DefaultThreadFactory; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.mcprotocollib.network.BuiltinFlags; import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper; import org.geysermc.mcprotocollib.network.packet.PacketProtocol; +import org.geysermc.mcprotocollib.network.tcp.FlushHandler; +import org.geysermc.mcprotocollib.network.tcp.TcpFlowControlHandler; import org.geysermc.mcprotocollib.network.tcp.TcpPacketCodec; +import org.geysermc.mcprotocollib.network.tcp.TcpPacketCompression; +import org.geysermc.mcprotocollib.network.tcp.TcpPacketEncryptor; import org.geysermc.mcprotocollib.network.tcp.TcpPacketSizer; import org.geysermc.mcprotocollib.network.tcp.TcpSession; import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper; @@ -43,6 +58,7 @@ import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; /** @@ -72,44 +88,53 @@ public final class LocalSession extends TcpSession { if (DEFAULT_EVENT_LOOP_GROUP == null) { DEFAULT_EVENT_LOOP_GROUP = new DefaultEventLoopGroup(new DefaultThreadFactory(this.getClass(), true)); Runtime.getRuntime().addShutdownHook(new Thread( - () -> DEFAULT_EVENT_LOOP_GROUP.shutdownGracefully(100, 500, TimeUnit.MILLISECONDS))); + () -> DEFAULT_EVENT_LOOP_GROUP.shutdownGracefully(100, 500, TimeUnit.MILLISECONDS))); } - try { - final Bootstrap bootstrap = new Bootstrap(); - bootstrap.channel(LocalChannelWithRemoteAddress.class); - bootstrap.handler(new ChannelInitializer() { - @Override - public void initChannel(@NonNull LocalChannelWithRemoteAddress channel) { - channel.spoofedRemoteAddress(new InetSocketAddress(clientIp, 0)); - PacketProtocol protocol = getPacketProtocol(); - protocol.newClientSession(LocalSession.this, transferring); + final Bootstrap bootstrap = new Bootstrap(); + bootstrap.channel(LocalChannelWithRemoteAddress.class); + bootstrap.handler(new ChannelInitializer() { + @Override + public void initChannel(@NonNull LocalChannelWithRemoteAddress channel) { + channel.spoofedRemoteAddress(new InetSocketAddress(clientIp, 0)); + PacketProtocol protocol = getPacketProtocol(); + protocol.newClientSession(LocalSession.this, transferring); - refreshReadTimeoutHandler(channel); - refreshWriteTimeoutHandler(channel); + ChannelPipeline pipeline = channel.pipeline(); - ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast("sizer", new TcpPacketSizer(LocalSession.this, protocol.getPacketHeader().getLengthSize())); - pipeline.addLast("codec", new TcpPacketCodec(LocalSession.this, true)); - pipeline.addLast("manager", LocalSession.this); + addHAProxySupport(pipeline); - addHAProxySupport(pipeline); - } - }).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000); + pipeline.addLast("read-timeout", new ReadTimeoutHandler(getFlag(BuiltinFlags.READ_TIMEOUT, 30))); + pipeline.addLast("write-timeout", new WriteTimeoutHandler(getFlag(BuiltinFlags.WRITE_TIMEOUT, 0))); - if (PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR != null) { - bootstrap.option(ChannelOption.ALLOCATOR, PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR); + pipeline.addLast("encryption", new TcpPacketEncryptor()); + pipeline.addLast("sizer", new TcpPacketSizer(protocol.getPacketHeader(), getCodecHelper())); + pipeline.addLast("compression", new TcpPacketCompression(getCodecHelper())); + + pipeline.addLast("flow-control", new TcpFlowControlHandler()); + pipeline.addLast("codec", new TcpPacketCodec(LocalSession.this, true)); + pipeline.addLast("flush-handler", new FlushHandler()); + pipeline.addLast("manager", LocalSession.this); + } + }).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getFlag(BuiltinFlags.CLIENT_CONNECT_TIMEOUT, 30) * 1000); + + if (PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR != null) { + bootstrap.option(ChannelOption.ALLOCATOR, PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR); + } + + bootstrap.remoteAddress(targetAddress); + + CompletableFuture handleFuture = new CompletableFuture<>(); + bootstrap.connect().addListener((futureListener) -> { + if (!futureListener.isSuccess()) { + exceptionCaught(null, futureListener.cause()); } - bootstrap.remoteAddress(targetAddress); + handleFuture.complete(null); + }); - bootstrap.connect().addListener((future) -> { - if (!future.isSuccess()) { - exceptionCaught(null, future.cause()); - } - }); - } catch (Throwable t) { - exceptionCaught(null, t); + if (wait) { + handleFuture.join(); } } @@ -121,7 +146,7 @@ public final class LocalSession extends TcpSession { // TODO duplicate code private void addHAProxySupport(ChannelPipeline pipeline) { InetSocketAddress clientAddress = getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS); - if (getFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, false) && clientAddress != null) { + if (clientAddress != null) { pipeline.addFirst("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() { @Override public void channelActive(@NonNull ChannelHandlerContext ctx) throws Exception { @@ -133,9 +158,9 @@ public final class LocalSession extends TcpSession { remoteAddress = new InetSocketAddress(host, port); } ctx.channel().writeAndFlush(new HAProxyMessage( - HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol, - clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(), - clientAddress.getPort(), remoteAddress.getPort() + HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol, + clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(), + clientAddress.getPort(), remoteAddress.getPort() )); ctx.pipeline().remove(this); ctx.pipeline().remove("proxy-protocol-encoder"); @@ -144,7 +169,7 @@ public final class LocalSession extends TcpSession { }); pipeline.addFirst("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE); } - } + } /** * Should only be called when direct ByteBufs should be preferred. At this moment, this should only be called on BungeeCord. diff --git a/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java index 69ac974cc..4e60d60e4 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java @@ -35,10 +35,10 @@ import java.net.InetSocketAddress; public interface IGeyserPingPassthrough { /** - * Get the MOTD of the server displayed on the multiplayer screen + * Gets the ping information, including the MOTD and player count, from the server * * @param inetSocketAddress the ip address of the client pinging the server - * @return string of the MOTD + * @return the ping information */ @Nullable GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress); diff --git a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java index 9a5b43816..b31f2b4f0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -31,6 +31,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.Clien import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket; import io.netty.channel.EventLoop; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.erosion.ErosionCancellationException; import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -87,6 +88,8 @@ public class PacketTranslatorRegistry extends AbstractMappedRegistry, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { // misc - providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); + providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0])); providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index d7dc989da..bface58da 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -34,10 +34,20 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; -import it.unimi.dsi.fastutil.objects.*; -import org.cloudburstmc.nbt.*; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.cloudburstmc.nbt.NBTInputStream; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +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.codec.v712.Bedrock_v712; +import org.cloudburstmc.protocol.bedrock.codec.v729.Bedrock_v729; import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.geysermc.geyser.GeyserImpl; @@ -61,7 +71,15 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.io.DataInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; @@ -108,7 +126,9 @@ public final class BlockRegistryPopulator { private static void registerBedrockBlocks() { var blockMappers = ImmutableMap., Remapper>builder() .put(ObjectIntPair.of("1_20_80", Bedrock_v671.CODEC.getProtocolVersion()), Conversion685_671::remapBlock) - .put(ObjectIntPair.of("1_21_0", Bedrock_v685.CODEC.getProtocolVersion()), tag -> tag) + .put(ObjectIntPair.of("1_21_0", Bedrock_v685.CODEC.getProtocolVersion()), Conversion712_685::remapBlock) + .put(ObjectIntPair.of("1_21_20", Bedrock_v712.CODEC.getProtocolVersion()), Conversion729_712::remapBlock) + .put(ObjectIntPair.of("1_21_30", Bedrock_v729.CODEC.getProtocolVersion()), tag -> tag) .build(); // We can keep this strong as nothing should be garbage collected @@ -129,7 +149,7 @@ public final class BlockRegistryPopulator { NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder(); builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states builder.remove("name_hash"); // Quick workaround - was added in 1.19.20 - builder.remove("network_id"); // Added in 1.19.80 - ???? + builder.remove("network_id"); // Added in 1.19.80 builder.remove("block_id"); // Added in 1.20.60 //noinspection UnstableApiUsage builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states"))); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java index 58886ca57..0c7f540bf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion685_671.java @@ -45,6 +45,8 @@ public class Conversion685_671 { private static final List NEW_MUSIC_DISCS = List.of(Items.MUSIC_DISC_CREATOR, Items.MUSIC_DISC_CREATOR_MUSIC_BOX, Items.MUSIC_DISC_PRECIPICE); static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) { + mapping = Conversion712_685.remapItem(item, mapping); + String identifer = mapping.getBedrockIdentifier(); if (NEW_MUSIC_DISCS.contains(item)) { @@ -111,6 +113,8 @@ public class Conversion685_671 { } static NbtMap remapBlock(NbtMap tag) { + tag = Conversion712_685.remapBlock(tag); + final String name = tag.getString("name"); if (!MODIFIED_BLOCKS.contains(name)) { @@ -130,7 +134,7 @@ public class Conversion685_671 { String coralColor; boolean deadBit = name.startsWith("minecraft:dead_"); - switch(name) { + switch (name) { case "minecraft:tube_coral_block", "minecraft:dead_tube_coral_block" -> coralColor = "blue"; case "minecraft:brain_coral_block", "minecraft:dead_brain_coral_block" -> coralColor = "pink"; case "minecraft:bubble_coral_block", "minecraft:dead_bubble_coral_block" -> coralColor = "purple"; @@ -152,7 +156,7 @@ public class Conversion685_671 { replacement = "minecraft:double_plant"; String doublePlantType; - switch(name) { + switch (name) { case "minecraft:sunflower" -> doublePlantType = "sunflower"; case "minecraft:lilac" -> doublePlantType = "syringa"; case "minecraft:tall_grass" -> doublePlantType = "grass"; @@ -174,7 +178,7 @@ public class Conversion685_671 { replacement = "minecraft:stone_block_slab"; String stoneSlabType; - switch(name) { + switch (name) { case "minecraft:smooth_stone_slab" -> stoneSlabType = "smooth_stone"; case "minecraft:sandstone_slab" -> stoneSlabType = "sandstone"; case "minecraft:petrified_oak_slab" -> stoneSlabType = "wood"; @@ -198,7 +202,7 @@ public class Conversion685_671 { replacement = "minecraft:tallgrass"; String tallGrassType; - switch(name) { + switch (name) { case "minecraft:short_grass" -> tallGrassType = "tall"; case "minecraft:fern" -> tallGrassType = "fern"; default -> throw new IllegalStateException("Unexpected value: " + name); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion712_685.java b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion712_685.java new file mode 100644 index 000000000..db715e015 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion712_685.java @@ -0,0 +1,440 @@ +package org.geysermc.geyser.registry.populator; + +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.registry.type.GeyserMappingItem; + +import java.util.List; +import java.util.stream.Stream; + +public class Conversion712_685 { + private static final List NEW_STONE_BLOCK_SLABS_2 = List.of("minecraft:prismarine_slab", "minecraft:dark_prismarine_slab", "minecraft:smooth_sandstone_slab", "minecraft:purpur_slab", "minecraft:red_nether_brick_slab", "minecraft:prismarine_brick_slab", "minecraft:mossy_cobblestone_slab", "minecraft:red_sandstone_slab"); + private static final List NEW_STONE_BLOCK_SLABS_3 = List.of("minecraft:smooth_red_sandstone_slab", "minecraft:polished_granite_slab", "minecraft:granite_slab", "minecraft:polished_diorite_slab", "minecraft:andesite_slab", "minecraft:polished_andesite_slab", "minecraft:diorite_slab", "minecraft:end_stone_brick_slab"); + private static final List NEW_STONE_BLOCK_SLABS_4 = List.of("minecraft:smooth_quartz_slab", "minecraft:cut_sandstone_slab", "minecraft:cut_red_sandstone_slab", "minecraft:normal_stone_slab", "minecraft:mossy_stone_brick_slab"); + private static final List NEW_DOUBLE_STONE_BLOCK_SLABS = List.of("minecraft:quartz_double_slab", "minecraft:petrified_oak_double_slab", "minecraft:stone_brick_double_slab", "minecraft:brick_double_slab", "minecraft:sandstone_double_slab", "minecraft:nether_brick_double_slab", "minecraft:cobblestone_double_slab", "minecraft:smooth_stone_double_slab"); + private static final List NEW_DOUBLE_STONE_BLOCK_SLABS_2 = List.of("minecraft:prismarine_double_slab", "minecraft:dark_prismarine_double_slab", "minecraft:smooth_sandstone_double_slab", "minecraft:purpur_double_slab", "minecraft:red_nether_brick_double_slab", "minecraft:prismarine_brick_double_slab", "minecraft:mossy_cobblestone_double_slab", "minecraft:red_sandstone_double_slab"); + private static final List NEW_DOUBLE_STONE_BLOCK_SLABS_3 = List.of("minecraft:smooth_red_sandstone_double_slab", "minecraft:polished_granite_double_slab", "minecraft:granite_double_slab", "minecraft:polished_diorite_double_slab", "minecraft:andesite_double_slab", "minecraft:polished_andesite_double_slab", "minecraft:diorite_double_slab", "minecraft:end_stone_brick_double_slab"); + private static final List NEW_DOUBLE_STONE_BLOCK_SLABS_4 = List.of("minecraft:smooth_quartz_double_slab", "minecraft:cut_sandstone_double_slab", "minecraft:cut_red_sandstone_double_slab", "minecraft:normal_stone_double_slab", "minecraft:mossy_stone_brick_double_slab"); + private static final List NEW_PRISMARINE_BLOCKS = List.of("minecraft:prismarine_bricks", "minecraft:dark_prismarine", "minecraft:prismarine"); + private static final List NEW_CORAL_FAN_HANGS = List.of("minecraft:tube_coral_wall_fan", "minecraft:brain_coral_wall_fan", "minecraft:dead_tube_coral_wall_fan", "minecraft:dead_brain_coral_wall_fan"); + private static final List NEW_CORAL_FAN_HANGS_2 = List.of("minecraft:bubble_coral_wall_fan", "minecraft:fire_coral_wall_fan", "minecraft:dead_bubble_coral_wall_fan", "minecraft:dead_fire_coral_wall_fan"); + private static final List NEW_CORAL_FAN_HANGS_3 = List.of("minecraft:horn_coral_wall_fan", "minecraft:dead_horn_coral_wall_fan"); + private static final List NEW_MONSTER_EGGS = List.of("minecraft:infested_cobblestone", "minecraft:infested_stone_bricks", "minecraft:infested_mossy_stone_bricks", "minecraft:infested_cracked_stone_bricks", "minecraft:infested_chiseled_stone_bricks", "minecraft:infested_stone"); + private static final List NEW_STONEBRICK_BLOCKS = List.of("minecraft:mossy_stone_bricks", "minecraft:cracked_stone_bricks", "minecraft:chiseled_stone_bricks", "minecraft:smooth_stone_bricks", "minecraft:stone_bricks"); + private static final List NEW_LIGHT_BLOCKS = List.of("minecraft:light_block_0", "minecraft:light_block_1", "minecraft:light_block_2", "minecraft:light_block_3", "minecraft:light_block_4", "minecraft:light_block_5", "minecraft:light_block_6", "minecraft:light_block_7", "minecraft:light_block_8", "minecraft:light_block_9", "minecraft:light_block_10", "minecraft:light_block_11", "minecraft:light_block_12", "minecraft:light_block_13", "minecraft:light_block_14", "minecraft:light_block_15"); + private static final List NEW_SANDSTONE_BLOCKS = List.of("minecraft:cut_sandstone", "minecraft:chiseled_sandstone", "minecraft:smooth_sandstone", "minecraft:sandstone"); + private static final List NEW_QUARTZ_BLOCKS = List.of("minecraft:chiseled_quartz_block", "minecraft:quartz_pillar", "minecraft:smooth_quartz", "minecraft:quartz_block"); + private static final List NEW_RED_SANDSTONE_BLOCKS = List.of("minecraft:cut_red_sandstone", "minecraft:chiseled_red_sandstone", "minecraft:smooth_red_sandstone", "minecraft:red_sandstone"); + private static final List NEW_SAND_BLOCKS = List.of("minecraft:red_sand", "minecraft:sand"); + private static final List NEW_DIRT_BLOCKS = List.of("minecraft:coarse_dirt", "minecraft:dirt"); + private static final List NEW_ANVILS = List.of("minecraft:damaged_anvil", "minecraft:chipped_anvil", "minecraft:deprecated_anvil", "minecraft:anvil"); + private static final List NEW_YELLOW_FLOWERS = List.of("minecraft:dandelion"); + private static final List NEW_BLOCKS = Stream.of(NEW_STONE_BLOCK_SLABS_2, NEW_STONE_BLOCK_SLABS_3, NEW_STONE_BLOCK_SLABS_4, NEW_DOUBLE_STONE_BLOCK_SLABS, NEW_DOUBLE_STONE_BLOCK_SLABS_2, NEW_DOUBLE_STONE_BLOCK_SLABS_3, NEW_DOUBLE_STONE_BLOCK_SLABS_4, NEW_PRISMARINE_BLOCKS, NEW_CORAL_FAN_HANGS, NEW_CORAL_FAN_HANGS_2, NEW_CORAL_FAN_HANGS_3, NEW_MONSTER_EGGS, NEW_STONEBRICK_BLOCKS, NEW_LIGHT_BLOCKS, NEW_SANDSTONE_BLOCKS, NEW_QUARTZ_BLOCKS, NEW_RED_SANDSTONE_BLOCKS, NEW_SAND_BLOCKS, NEW_DIRT_BLOCKS, NEW_ANVILS, NEW_YELLOW_FLOWERS).flatMap(List::stream).toList(); + + static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) { + mapping = Conversion729_712.remapItem(item, mapping); + + String identifer = mapping.getBedrockIdentifier(); + + if (!NEW_BLOCKS.contains(identifer)) { + return mapping; + } + + if (identifer.equals("minecraft:coarse_dirt")) { + return mapping.withBedrockIdentifier("minecraft:dirt").withBedrockData(1); + } + + if (identifer.equals("minecraft:dandelion")) { + return mapping.withBedrockIdentifier("minecraft:yellow_flower").withBedrockData(0); + } + + if (identifer.equals("minecraft:red_sand")) { + return mapping.withBedrockIdentifier("minecraft:sand").withBedrockData(1); + } + + if (NEW_PRISMARINE_BLOCKS.contains(identifer)) { + switch (identifer) { + case "minecraft:prismarine" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(0); } + case "minecraft:dark_prismarine" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(1); } + case "minecraft:prismarine_bricks" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(2); } + } + } + + if (NEW_SANDSTONE_BLOCKS.contains(identifer)) { + switch (identifer) { + case "minecraft:sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(0); } + case "minecraft:chiseled_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(1); } + case "minecraft:cut_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(2); } + case "minecraft:smooth_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(3); } + } + } + + if (NEW_RED_SANDSTONE_BLOCKS.contains(identifer)) { + switch (identifer) { + case "minecraft:red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(0); } + case "minecraft:chiseled_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(1); } + case "minecraft:cut_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(2); } + case "minecraft:smooth_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(3); } + } + } + + if (NEW_QUARTZ_BLOCKS.contains(identifer)) { + switch (identifer) { + case "minecraft:quartz_block" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(0); } + case "minecraft:chiseled_quartz_block" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(1); } + case "minecraft:quartz_pillar" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(2); } + case "minecraft:smooth_quartz" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(3); } + } + } + + if (NEW_STONE_BLOCK_SLABS_2.contains(identifer)) { + switch (identifer) { + case "minecraft:red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(0); } + case "minecraft:purpur_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(1); } + case "minecraft:prismarine_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(2); } + case "minecraft:dark_prismarine_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(3); } + case "minecraft:prismarine_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(4); } + case "minecraft:mossy_cobblestone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(5); } + case "minecraft:smooth_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(6); } + case "minecraft:red_nether_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(7); } + } + } + + if (NEW_STONE_BLOCK_SLABS_3.contains(identifer)) { + switch (identifer) { + case "minecraft:end_stone_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(0); } + case "minecraft:smooth_red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(1); } + case "minecraft:polished_andesite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(2); } + case "minecraft:andesite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(3); } + case "minecraft:diorite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(4); } + case "minecraft:polished_diorite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(5); } + case "minecraft:granite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(6); } + case "minecraft:polished_granite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(7); } + } + } + + if (NEW_STONE_BLOCK_SLABS_4.contains(identifer)) { + switch (identifer) { + case "minecraft:mossy_stone_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(0); } + case "minecraft:smooth_quartz_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(1); } + case "minecraft:normal_stone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(2); } + case "minecraft:cut_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(3); } + case "minecraft:cut_red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(4); } + } + } + + if (NEW_MONSTER_EGGS.contains(identifer)) { + switch (identifer) { + case "minecraft:infested_stone" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(0); } + case "minecraft:infested_cobblestone" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(1); } + case "minecraft:infested_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(2); } + case "minecraft:infested_mossy_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(3); } + case "minecraft:infested_cracked_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(4); } + case "minecraft:infested_chiseled_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(5); } + } + } + + if (NEW_STONEBRICK_BLOCKS.contains(identifer)) { + switch (identifer) { + case "minecraft:stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(0); } + case "minecraft:mossy_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(1); } + case "minecraft:cracked_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(2); } + case "minecraft:chiseled_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(3); } + } + } + + if (NEW_ANVILS.contains(identifer)) { + switch (identifer) { + case "minecraft:anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(0); } + case "minecraft:chipped_anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(4); } + case "minecraft:damaged_anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(8); } + } + } + + return mapping; + } + + static NbtMap remapBlock(NbtMap tag) { + tag = Conversion729_712.remapBlock(tag); + + final String name = tag.getString("name"); + + if (!NEW_BLOCKS.contains(name)) { + return tag; + } + + String replacement; + + if (NEW_DOUBLE_STONE_BLOCK_SLABS.contains(name)) { + replacement = "minecraft:double_stone_block_slab"; + String stoneSlabType; + + switch (name) { + case "minecraft:quartz_double_slab" -> stoneSlabType = "quartz"; + case "minecraft:petrified_oak_double_slab" -> stoneSlabType = "wood"; + case "minecraft:stone_brick_double_slab" -> stoneSlabType = "stone_brick"; + case "minecraft:brick_double_slab" -> stoneSlabType = "brick"; + case "minecraft:sandstone_double_slab" -> stoneSlabType = "sandstone"; + case "minecraft:nether_brick_double_slab" -> stoneSlabType = "nether_brick"; + case "minecraft:cobblestone_double_slab" -> stoneSlabType = "cobblestone"; + case "minecraft:smooth_stone_double_slab" -> stoneSlabType = "smooth_stone"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("stone_slab_type", stoneSlabType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_STONE_BLOCK_SLABS_2.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_2.contains(name)) { + replacement = NEW_STONE_BLOCK_SLABS_2.contains(name) ? "minecraft:stone_block_slab2" : "minecraft:double_stone_block_slab2"; + String stoneSlabType2; + + switch (name) { + case "minecraft:prismarine_slab", "minecraft:prismarine_double_slab" -> stoneSlabType2 = "prismarine_rough"; + case "minecraft:dark_prismarine_slab", "minecraft:dark_prismarine_double_slab" -> stoneSlabType2 = "prismarine_dark"; + case "minecraft:smooth_sandstone_slab", "minecraft:smooth_sandstone_double_slab" -> stoneSlabType2 = "smooth_sandstone"; + case "minecraft:purpur_slab", "minecraft:purpur_double_slab" -> stoneSlabType2 = "purpur"; + case "minecraft:red_nether_brick_slab", "minecraft:red_nether_brick_double_slab" -> stoneSlabType2 = "red_nether_brick"; + case "minecraft:prismarine_brick_slab", "minecraft:prismarine_brick_double_slab" -> stoneSlabType2 = "prismarine_brick"; + case "minecraft:mossy_cobblestone_slab", "minecraft:mossy_cobblestone_double_slab" -> stoneSlabType2 = "mossy_cobblestone"; + case "minecraft:red_sandstone_slab", "minecraft:red_sandstone_double_slab" -> stoneSlabType2 = "red_sandstone"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("stone_slab_type_2", stoneSlabType2) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_STONE_BLOCK_SLABS_3.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_3.contains(name)) { + replacement = NEW_STONE_BLOCK_SLABS_3.contains(name) ? "minecraft:stone_block_slab3" : "minecraft:double_stone_block_slab3"; + String stoneSlabType3; + + switch (name) { + case "minecraft:smooth_red_sandstone_slab", "minecraft:smooth_red_sandstone_double_slab" -> stoneSlabType3 = "smooth_red_sandstone"; + case "minecraft:polished_granite_slab", "minecraft:polished_granite_double_slab" -> stoneSlabType3 = "polished_granite"; + case "minecraft:granite_slab", "minecraft:granite_double_slab" -> stoneSlabType3 = "granite"; + case "minecraft:polished_diorite_slab", "minecraft:polished_diorite_double_slab" -> stoneSlabType3 = "polished_diorite"; + case "minecraft:andesite_slab", "minecraft:andesite_double_slab" -> stoneSlabType3 = "andesite"; + case "minecraft:polished_andesite_slab", "minecraft:polished_andesite_double_slab" -> stoneSlabType3 = "polished_andesite"; + case "minecraft:diorite_slab", "minecraft:diorite_double_slab" -> stoneSlabType3 = "diorite"; + case "minecraft:end_stone_brick_slab", "minecraft:end_stone_brick_double_slab" -> stoneSlabType3 = "end_stone_brick"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("stone_slab_type_3", stoneSlabType3) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_STONE_BLOCK_SLABS_4.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_4.contains(name)) { + replacement = NEW_STONE_BLOCK_SLABS_4.contains(name) ? "minecraft:stone_block_slab4" : "minecraft:double_stone_block_slab4"; + String stoneSlabType4; + + switch (name) { + case "minecraft:smooth_quartz_slab", "minecraft:smooth_quartz_double_slab" -> stoneSlabType4 = "smooth_quartz"; + case "minecraft:cut_sandstone_slab", "minecraft:cut_sandstone_double_slab" -> stoneSlabType4 = "cut_sandstone"; + case "minecraft:cut_red_sandstone_slab", "minecraft:cut_red_sandstone_double_slab" -> stoneSlabType4 = "cut_red_sandstone"; + case "minecraft:normal_stone_slab", "minecraft:normal_stone_double_slab" -> stoneSlabType4 = "stone"; + case "minecraft:mossy_stone_brick_slab", "minecraft:mossy_stone_brick_double_slab" -> stoneSlabType4 = "mossy_stone_brick"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("stone_slab_type_4", stoneSlabType4) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_PRISMARINE_BLOCKS.contains(name)) { + replacement = "minecraft:prismarine"; + String prismarineBlockType; + + switch (name) { + case "minecraft:prismarine_bricks" -> prismarineBlockType = "bricks"; + case "minecraft:dark_prismarine" -> prismarineBlockType = "dark"; + case "minecraft:prismarine" -> prismarineBlockType = "default"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("prismarine_block_type", prismarineBlockType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_CORAL_FAN_HANGS.contains(name) || NEW_CORAL_FAN_HANGS_2.contains(name) || NEW_CORAL_FAN_HANGS_3.contains(name)) { + replacement = NEW_CORAL_FAN_HANGS.contains(name) ? "minecraft:coral_fan_hang" : NEW_CORAL_FAN_HANGS_2.contains(name) ? "minecraft:coral_fan_hang2" : "minecraft:coral_fan_hang3"; + boolean deadBit = name.startsWith("minecraft:dead_"); + boolean coralHangTypeBit = name.contains("brain") || name.contains("fire"); + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putBoolean("coral_hang_type_bit", coralHangTypeBit) + .putBoolean("dead_bit", deadBit) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_MONSTER_EGGS.contains(name)) { + replacement = "minecraft:monster_egg"; + String monsterEggStoneType; + + switch (name) { + case "minecraft:infested_cobblestone" -> monsterEggStoneType = "cobblestone"; + case "minecraft:infested_stone_bricks" -> monsterEggStoneType = "stone_brick"; + case "minecraft:infested_mossy_stone_bricks" -> monsterEggStoneType = "mossy_stone_brick"; + case "minecraft:infested_cracked_stone_bricks" -> monsterEggStoneType = "cracked_stone_brick"; + case "minecraft:infested_chiseled_stone_bricks" -> monsterEggStoneType = "chiseled_stone_brick"; + case "minecraft:infested_stone" -> monsterEggStoneType = "stone"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("monster_egg_stone_type", monsterEggStoneType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_STONEBRICK_BLOCKS.contains(name)) { + replacement = "minecraft:stonebrick"; + String stoneBrickType; + + switch (name) { + case "minecraft:mossy_stone_bricks" -> stoneBrickType = "mossy"; + case "minecraft:cracked_stone_bricks" -> stoneBrickType = "cracked"; + case "minecraft:chiseled_stone_bricks" -> stoneBrickType = "chiseled"; + case "minecraft:smooth_stone_bricks" -> stoneBrickType = "smooth"; + case "minecraft:stone_bricks" -> stoneBrickType = "default"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("stone_brick_type", stoneBrickType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_LIGHT_BLOCKS.contains(name)) { + replacement = "minecraft:light_block"; + int blockLightLevel = Integer.parseInt(name.split("_")[2]); + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putInt("block_light_level", blockLightLevel) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_SANDSTONE_BLOCKS.contains(name) || NEW_RED_SANDSTONE_BLOCKS.contains(name)) { + replacement = NEW_SANDSTONE_BLOCKS.contains(name) ? "minecraft:sandstone" : "minecraft:red_sandstone"; + String sandStoneType; + + switch (name) { + case "minecraft:cut_sandstone", "minecraft:cut_red_sandstone" -> sandStoneType = "cut"; + case "minecraft:chiseled_sandstone", "minecraft:chiseled_red_sandstone" -> sandStoneType = "heiroglyphs"; + case "minecraft:smooth_sandstone", "minecraft:smooth_red_sandstone" -> sandStoneType = "smooth"; + case "minecraft:sandstone", "minecraft:red_sandstone" -> sandStoneType = "default"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("sand_stone_type", sandStoneType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_QUARTZ_BLOCKS.contains(name)) { + replacement = "minecraft:quartz_block"; + String chiselType; + + switch (name) { + case "minecraft:chiseled_quartz_block" -> chiselType = "chiseled"; + case "minecraft:quartz_pillar" -> chiselType = "lines"; + case "minecraft:smooth_quartz" -> chiselType = "smooth"; + case "minecraft:quartz_block" -> chiselType = "default"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("chisel_type", chiselType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_SAND_BLOCKS.contains(name)) { + replacement = "minecraft:sand"; + String sandType = name.equals("minecraft:red_sand") ? "red" : "normal"; + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("sand_type", sandType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_DIRT_BLOCKS.contains(name)) { + replacement = "minecraft:dirt"; + String dirtType = name.equals("minecraft:coarse_dirt") ? "coarse" : "normal"; + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("dirt_type", dirtType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_ANVILS.contains(name)) { + replacement = "minecraft:anvil"; + String damage; + + switch (name) { + case "minecraft:damaged_anvil" -> damage = "broken"; + case "minecraft:chipped_anvil" -> damage = "slightly_damaged"; + case "minecraft:deprecated_anvil" -> damage = "very_damaged"; + case "minecraft:anvil" -> damage = "undamaged"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("damage", damage) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_YELLOW_FLOWERS.contains(name)) { + replacement = "minecraft:yellow_flower"; + return tag.toBuilder().putString("name", replacement).build(); + } + + return tag; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion729_712.java b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion729_712.java new file mode 100644 index 000000000..5d4ebdc47 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/Conversion729_712.java @@ -0,0 +1,151 @@ +package org.geysermc.geyser.registry.populator; + +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.registry.type.GeyserMappingItem; + +import java.util.List; +import java.util.stream.Stream; + +public class Conversion729_712 { + private static final List NEW_PURPUR_BLOCKS = List.of("minecraft:purpur_block", "minecraft:purpur_pillar"); + private static final List NEW_WALL_BLOCKS = List.of("minecraft:cobblestone_wall", "minecraft:mossy_cobblestone_wall", "minecraft:granite_wall", "minecraft:diorite_wall", "minecraft:andesite_wall", "minecraft:sandstone_wall", "minecraft:brick_wall", "minecraft:stone_brick_wall", "minecraft:mossy_stone_brick_wall", "minecraft:nether_brick_wall", "minecraft:end_stone_brick_wall", "minecraft:prismarine_wall", "minecraft:red_sandstone_wall", "minecraft:red_nether_brick_wall"); + private static final List NEW_SPONGE_BLOCKS = List.of("minecraft:sponge", "minecraft:wet_sponge"); + private static final List NEW_TNT_BLOCKS = List.of("minecraft:tnt", "minecraft:underwater_tnt"); + private static final List STRUCTURE_VOID = List.of("minecraft:structure_void"); + private static final List NEW_BLOCKS = Stream.of(NEW_PURPUR_BLOCKS, NEW_WALL_BLOCKS, NEW_SPONGE_BLOCKS, NEW_TNT_BLOCKS, STRUCTURE_VOID).flatMap(List::stream).toList(); + + static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) { + String identifier = mapping.getBedrockIdentifier(); + + if (!NEW_BLOCKS.contains(identifier)) { + return mapping; + } + + if (identifier.equals("minecraft:underwater_tnt")) { + return mapping.withBedrockIdentifier("minecraft:tnt").withBedrockData(1); + } + + if (NEW_PURPUR_BLOCKS.contains(identifier)) { + switch (identifier) { + case "minecraft:purpur_block" -> { return mapping.withBedrockIdentifier("minecraft:purpur_block").withBedrockData(0); } + case "minecraft:purpur_pillar" -> { return mapping.withBedrockIdentifier("minecraft:purpur_block").withBedrockData(1); } + } + } + + if (NEW_WALL_BLOCKS.contains(identifier)) { + switch (identifier) { + case "minecraft:cobblestone_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(0); } + case "minecraft:mossy_cobblestone_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(1); } + case "minecraft:granite_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(2); } + case "minecraft:diorite_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(3); } + case "minecraft:andesite_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(4); } + case "minecraft:sandstone_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(5); } + case "minecraft:brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(6); } + case "minecraft:stone_brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(7); } + case "minecraft:mossy_stone_brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(8); } + case "minecraft:nether_brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(9); } + case "minecraft:end_stone_brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(10); } + case "minecraft:prismarine_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(11); } + case "minecraft:red_sandstone_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(12); } + case "minecraft:red_nether_brick_wall" -> { return mapping.withBedrockIdentifier("minecraft:cobblestone_wall").withBedrockData(13); } + } + } + + if (NEW_SPONGE_BLOCKS.contains(identifier)) { + switch (identifier) { + case "minecraft:sponge" -> { return mapping.withBedrockIdentifier("minecraft:sponge").withBedrockData(0); } + case "minecraft:wet_sponge" -> { return mapping.withBedrockIdentifier("minecraft:sponge").withBedrockData(1); } + } + } + + return mapping; + } + + static NbtMap remapBlock(NbtMap tag) { + final String name = tag.getString("name"); + + if (!NEW_BLOCKS.contains(name)) { + return tag; + } + + String replacement; + + if (NEW_PURPUR_BLOCKS.contains(name)) { + replacement = "minecraft:purpur_block"; + String purpurType = name.equals("minecraft:purpur_pillar") ? "lines" : "default"; + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("chisel_type", purpurType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_WALL_BLOCKS.contains(name)) { + replacement = "minecraft:cobblestone_wall"; + String wallType; + + switch (name) { + case "minecraft:cobblestone_wall" -> wallType = "cobblestone"; + case "minecraft:mossy_cobblestone_wall" -> wallType = "mossy_cobblestone"; + case "minecraft:granite_wall" -> wallType = "granite"; + case "minecraft:diorite_wall" -> wallType = "diorite"; + case "minecraft:andesite_wall" -> wallType = "andesite"; + case "minecraft:sandstone_wall" -> wallType = "sandstone"; + case "minecraft:brick_wall" -> wallType = "brick"; + case "minecraft:stone_brick_wall" -> wallType = "stone_brick"; + case "minecraft:mossy_stone_brick_wall" -> wallType = "mossy_stone_brick"; + case "minecraft:nether_brick_wall" -> wallType = "nether_brick"; + case "minecraft:end_stone_brick_wall" -> wallType = "end_brick"; + case "minecraft:prismarine_wall" -> wallType = "prismarine"; + case "minecraft:red_sandstone_wall" -> wallType = "red_sandstone"; + case "minecraft:red_nether_brick_wall" -> wallType = "red_nether_brick"; + default -> throw new IllegalStateException("Unexpected value: " + name); + } + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("wall_block_type", wallType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_SPONGE_BLOCKS.contains(name)) { + replacement = "minecraft:sponge"; + String spongeType = name.equals("minecraft:wet_sponge") ? "wet" : "dry"; + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("sponge_type", spongeType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (NEW_TNT_BLOCKS.contains(name)) { + replacement = "minecraft:tnt"; + byte tntType = (byte) (name.equals("minecraft:underwater_tnt") ? 1 : 0); + + NbtMap states = tag.getCompound("states") + .toBuilder() + .putByte("allow_underwater_bit", tntType) + .build(); + + return tag.toBuilder().putString("name", replacement).putCompound("states", states).build(); + } + + if (STRUCTURE_VOID.contains(name)) { + NbtMap states = tag.getCompound("states") + .toBuilder() + .putString("structure_void_type", "air") + .build(); + + return tag.toBuilder().putCompound("states", states).build(); + } + + return tag; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java index 2c033edc7..8e42887ff 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java @@ -80,7 +80,6 @@ public class CreativeItemRegistryPopulator { private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map definitions) { int count = 1; int damage = 0; - int bedrockBlockRuntimeId; NbtMap tag = null; String identifier = itemNode.get("id").textValue(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index a6fa164c1..0a9c93980 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -199,7 +199,13 @@ public class CustomItemRegistryPopulator { computeThrowableProperties(componentBuilder); } - computeRenderOffsets(false, customItemData, componentBuilder); + // Hardcoded on Java, and should extend to the custom item + boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) + || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) + || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) + || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) + ); + computeRenderOffsets(isHat, customItemData, componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index aad5e494d..bea213aa4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -41,6 +41,8 @@ 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.codec.v712.Bedrock_v712; +import org.cloudburstmc.protocol.bedrock.codec.v729.Bedrock_v729; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; @@ -90,7 +92,9 @@ public class ItemRegistryPopulator { public static void populate() { List paletteVersions = new ArrayList<>(3); paletteVersions.add(new PaletteVersion("1_20_80", Bedrock_v671.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion685_671::remapItem)); - paletteVersions.add(new PaletteVersion("1_21_0", Bedrock_v685.CODEC.getProtocolVersion())); + paletteVersions.add(new PaletteVersion("1_21_0", Bedrock_v685.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion712_685::remapItem)); + paletteVersions.add(new PaletteVersion("1_21_20", Bedrock_v712.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion729_712::remapItem)); + paletteVersions.add(new PaletteVersion("1_21_30", Bedrock_v729.CODEC.getProtocolVersion())); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); @@ -167,6 +171,12 @@ public class ItemRegistryPopulator { Map javaItemToMapping = new Object2ObjectOpenHashMap<>(); List creativeItems = new ArrayList<>(); + Set noBlockDefinitions = new ObjectOpenHashSet<>(); + + // Fix: Usage of structure blocks/voids in recipes + // https://github.com/GeyserMC/Geyser/issues/2890 + noBlockDefinitions.add("minecraft:structure_block"); + noBlockDefinitions.add("minecraft:structure_void"); AtomicInteger creativeNetId = new AtomicInteger(); CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> { @@ -187,6 +197,9 @@ public class ItemRegistryPopulator { bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition()); } } + } else { + // Item mappings should also NOT have a block definition for these. + noBlockDefinitions.add(item.getDefinition().getIdentifier()); } }); @@ -254,7 +267,12 @@ public class ItemRegistryPopulator { } else { // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1); - if (aValidBedrockBlockId != -1 || customBlockItemOverride != null) { + if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) { + // Fallback + if (!noBlockDefinitions.contains(entry.getValue().getBedrockIdentifier())) { + bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId); + } + } else { // As of 1.16.220, every item requires a block runtime ID attached to it. // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls. // However, in order for some visuals and crafting to work, we need to send the first matching block state diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index 437b8223a..8a2c77f28 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.registry.type; import it.unimi.dsi.fastutil.Pair; import lombok.Builder; import lombok.EqualsAndHashCode; +import lombok.ToString; import lombok.Value; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; @@ -42,6 +43,7 @@ import java.util.List; @Value @Builder @EqualsAndHashCode +@ToString public class ItemMapping { public static final ItemMapping AIR = new ItemMapping( "minecraft:air", diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java index 6c1389ef5..f3b7f20d2 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java @@ -25,185 +25,100 @@ package org.geysermc.geyser.scoreboard; -import lombok.Getter; -import lombok.Setter; -import net.kyori.adventure.text.Component; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; - +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; @Getter public final class Objective { private final Scoreboard scoreboard; - private final long id; - private boolean active = true; + private final List activeSlots = new ArrayList<>(); - @Setter - private UpdateType updateType = UpdateType.ADD; + private final String objectiveName; + private final Map scores = new ConcurrentHashMap<>(); - private String objectiveName; - private ScoreboardPosition displaySlot; - private String displaySlotName; - private String displayName = "unknown"; + private String displayName; private NumberFormat numberFormat; - private int type = 0; // 0 = integer, 1 = heart + private ScoreType type; - private Map scores = new ConcurrentHashMap<>(); - - private Objective(Scoreboard scoreboard) { - this.id = scoreboard.getNextId().getAndIncrement(); - this.scoreboard = scoreboard; - } - - /** - * /!\ This method is made for temporary objectives until the real objective is received - * - * @param scoreboard the scoreboard - * @param objectiveName the name of the objective - */ public Objective(Scoreboard scoreboard, String objectiveName) { - this(scoreboard); + this.scoreboard = scoreboard; this.objectiveName = objectiveName; - this.active = false; - } - - public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { - this(scoreboard); - this.objectiveName = objectiveName; - this.displaySlot = displaySlot; - this.displaySlotName = translateDisplaySlot(displaySlot); - this.displayName = displayName; - this.type = type; - } - - private static String translateDisplaySlot(ScoreboardPosition displaySlot) { - return switch (displaySlot) { - case BELOW_NAME -> "belowname"; - case PLAYER_LIST -> "list"; - default -> "sidebar"; - }; } public void registerScore(String id, int score, Component displayName, NumberFormat numberFormat) { - if (!scores.containsKey(id)) { - long scoreId = scoreboard.getNextId().getAndIncrement(); - Score scoreObject = new Score(scoreId, id) - .setScore(score) - .setTeam(scoreboard.getTeamFor(id)) - .setDisplayName(displayName) - .setNumberFormat(numberFormat) - .setUpdateType(UpdateType.ADD); - scores.put(id, scoreObject); + if (scores.containsKey(id)) { + return; + } + var reference = new ScoreReference(scoreboard, id, score, displayName, numberFormat); + scores.put(id, reference); + + for (var slot : activeSlots) { + slot.addScore(reference); } } public void setScore(String id, int score, Component displayName, NumberFormat numberFormat) { - Score stored = scores.get(id); + ScoreReference stored = scores.get(id); if (stored != null) { - stored.setScore(score) - .setDisplayName(displayName) - .setNumberFormat(numberFormat) - .setUpdateType(UpdateType.UPDATE); + stored.updateProperties(scoreboard, score, displayName, numberFormat); return; } registerScore(id, score, displayName, numberFormat); } public void removeScore(String id) { - Score stored = scores.get(id); + ScoreReference stored = scores.remove(id); if (stored != null) { - stored.setUpdateType(UpdateType.REMOVE); + stored.markDeleted(); } } - /** - * Used internally to remove a score from the score map - */ - public void removeScore0(String id) { - scores.remove(id); - } + public void updateProperties(Component displayNameComponent, ScoreType type, NumberFormat format) { + String displayName = MessageTranslator.convertMessageRaw(displayNameComponent, scoreboard.session().locale()); + boolean changed = !Objects.equals(this.displayName, displayName) || this.type != type; - public Objective setDisplayName(String displayName) { this.displayName = displayName; - if (updateType == UpdateType.NOTHING) { - updateType = UpdateType.UPDATE; - } - return this; - } + this.type = type; - public Objective setNumberFormat(NumberFormat numberFormat) { - if (Objects.equals(this.numberFormat, numberFormat)) { - return this; - } - - this.numberFormat = numberFormat; - if (updateType == UpdateType.NOTHING) { - updateType = UpdateType.UPDATE; - } - - // Update the number format for scores that are following this objective's number format - for (Score score : scores.values()) { - if (score.getNumberFormat() == null) { - score.setUpdateType(UpdateType.UPDATE); + if (!Objects.equals(this.numberFormat, format)) { + this.numberFormat = format; + // update the number format for scores that are following this objective's number format, + // but only if the objective itself doesn't need to be updated. + // When the objective itself has to update all scores are updated anyway + if (!changed) { + for (ScoreReference score : scores.values()) { + if (score.numberFormat() == null) { + score.markChanged(); + } + } } } - return this; - } - - public Objective setType(int type) { - this.type = type; - if (updateType == UpdateType.NOTHING) { - updateType = UpdateType.UPDATE; - } - return this; - } - - public void setActive(ScoreboardPosition displaySlot) { - if (!active) { - active = true; - this.displaySlot = displaySlot; - displaySlotName = translateDisplaySlot(displaySlot); + if (changed) { + for (DisplaySlot slot : activeSlots) { + slot.markNeedsUpdate(); + } } } - /** - * The objective will be removed on the next update - */ - public void pendingRemove() { - updateType = UpdateType.REMOVE; + public boolean hasDisplaySlot() { + return !activeSlots.isEmpty(); } - public @Nullable TeamColor getTeamColor() { - return switch (displaySlot) { - case SIDEBAR_TEAM_RED -> TeamColor.RED; - case SIDEBAR_TEAM_AQUA -> TeamColor.AQUA; - case SIDEBAR_TEAM_BLUE -> TeamColor.BLUE; - case SIDEBAR_TEAM_GOLD -> TeamColor.GOLD; - case SIDEBAR_TEAM_GRAY -> TeamColor.GRAY; - case SIDEBAR_TEAM_BLACK -> TeamColor.BLACK; - case SIDEBAR_TEAM_GREEN -> TeamColor.GREEN; - case SIDEBAR_TEAM_WHITE -> TeamColor.WHITE; - case SIDEBAR_TEAM_YELLOW -> TeamColor.YELLOW; - case SIDEBAR_TEAM_DARK_RED -> TeamColor.DARK_RED; - case SIDEBAR_TEAM_DARK_AQUA -> TeamColor.DARK_AQUA; - case SIDEBAR_TEAM_DARK_BLUE -> TeamColor.DARK_BLUE; - case SIDEBAR_TEAM_DARK_GRAY -> TeamColor.DARK_GRAY; - case SIDEBAR_TEAM_DARK_GREEN -> TeamColor.DARK_GREEN; - case SIDEBAR_TEAM_DARK_PURPLE -> TeamColor.DARK_PURPLE; - case SIDEBAR_TEAM_LIGHT_PURPLE -> TeamColor.LIGHT_PURPLE; - default -> null; - }; + public void addDisplaySlot(DisplaySlot slot) { + activeSlots.add(slot); } - public void removed() { - active = false; - updateType = UpdateType.REMOVE; - scores = null; + public void removeDisplaySlot(DisplaySlot slot) { + activeSlots.remove(slot); } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java deleted file mode 100644 index 9a26b7f77..000000000 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java +++ /dev/null @@ -1,199 +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.scoreboard; - -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.FixedFormat; -import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; -import net.kyori.adventure.text.Component; -import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; -import lombok.Getter; -import lombok.experimental.Accessors; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.translator.text.MessageTranslator; - -import java.util.Objects; - -@Getter -@Accessors(chain = true) -public final class Score { - private final long id; - private final String name; - private ScoreInfo cachedInfo; - - /** - * Changes that have been made since the last cached data. - */ - private final Score.ScoreData currentData; - /** - * The data that is currently displayed to the Bedrock client. - */ - private Score.ScoreData cachedData; - - public Score(long id, String name) { - this.id = id; - this.name = name; - this.currentData = new ScoreData(); - } - - public String getDisplayName() { - String displayName = cachedData.displayName; - if (displayName != null) { - return displayName; - } - Team team = cachedData.team; - if (team != null) { - return team.getDisplayName(name); - } - return name; - } - - public int getScore() { - return currentData.getScore(); - } - - public Score setScore(int score) { - currentData.score = score; - return this; - } - - public Team getTeam() { - return currentData.team; - } - - public Score setTeam(Team team) { - if (currentData.team != null && team != null) { - if (!currentData.team.equals(team)) { - currentData.team = team; - setUpdateType(UpdateType.UPDATE); - } - return this; - } - // simplified from (this.team != null && team == null) || (this.team == null && team != null) - if (currentData.team != null || team != null) { - currentData.team = team; - setUpdateType(UpdateType.UPDATE); - } - return this; - } - - public Score setDisplayName(Component displayName) { - if (currentData.displayName != null && displayName != null) { - String convertedDisplayName = MessageTranslator.convertMessage(displayName); - if (!currentData.displayName.equals(convertedDisplayName)) { - currentData.displayName = convertedDisplayName; - setUpdateType(UpdateType.UPDATE); - } - return this; - } - // simplified from (this.displayName != null && displayName == null) || (this.displayName == null && displayName != null) - if (currentData.displayName != null || displayName != null) { - currentData.displayName = MessageTranslator.convertMessage(displayName); - setUpdateType(UpdateType.UPDATE); - } - return this; - } - - public NumberFormat getNumberFormat() { - return currentData.numberFormat; - } - - public Score setNumberFormat(NumberFormat numberFormat) { - if (!Objects.equals(currentData.numberFormat, numberFormat)) { - currentData.numberFormat = numberFormat; - setUpdateType(UpdateType.UPDATE); - } - return this; - } - - public UpdateType getUpdateType() { - return currentData.updateType; - } - - public Score setUpdateType(UpdateType updateType) { - if (updateType != UpdateType.NOTHING) { - currentData.changed = true; - } - currentData.updateType = updateType; - return this; - } - - public boolean shouldUpdate() { - return cachedData == null || currentData.changed || - (currentData.team != null && currentData.team.shouldUpdate()); - } - - public void update(Objective objective) { - if (cachedData == null) { - cachedData = new ScoreData(); - cachedData.updateType = UpdateType.ADD; - if (currentData.updateType == UpdateType.REMOVE) { - cachedData.updateType = UpdateType.REMOVE; - } - } else { - cachedData.updateType = currentData.updateType; - } - - currentData.changed = false; - cachedData.team = currentData.team; - cachedData.score = currentData.score; - cachedData.displayName = currentData.displayName; - cachedData.numberFormat = currentData.numberFormat; - - String name = this.name; - if (cachedData.displayName != null) { - name = cachedData.displayName; - } else if (cachedData.team != null) { - cachedData.team.prepareUpdate(); - name = cachedData.team.getDisplayName(name); - } - - NumberFormat numberFormat = cachedData.numberFormat; - if (numberFormat == null) { - numberFormat = objective.getNumberFormat(); - } - if (numberFormat instanceof FixedFormat fixedFormat) { - name += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue()); - } - - cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), cachedData.score, name); - } - - @Getter - public static final class ScoreData { - private UpdateType updateType; - private boolean changed; - - private Team team; - private int score; - - private String displayName; - private NumberFormat numberFormat; - - private ScoreData() { - updateType = UpdateType.ADD; - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreReference.java b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreReference.java new file mode 100644 index 000000000..c26a59899 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreReference.java @@ -0,0 +1,132 @@ +/* + * 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.scoreboard; + +import java.util.Objects; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; + +public final class ScoreReference { + public static final long LAST_UPDATE_DEFAULT = -1; + private static final long LAST_UPDATE_REMOVE = -2; + + private final String name; + private final boolean hidden; + + private String displayName; + private int score; + private NumberFormat numberFormat; + + private long lastUpdate; + + public ScoreReference( + Scoreboard scoreboard, String name, int score, Component displayName, NumberFormat format) { + this.name = name; + // hidden is a sidebar exclusive feature + this.hidden = name.startsWith("#"); + + updateProperties(scoreboard, score, displayName, format); + this.lastUpdate = LAST_UPDATE_DEFAULT; + } + + public String name() { + return name; + } + + public boolean hidden() { + return hidden; + } + + public String displayName() { + return displayName; + } + + public void displayName(Component displayName, Scoreboard scoreboard) { + if (this.displayName != null && displayName != null) { + String convertedDisplayName = MessageTranslator.convertMessage(displayName, scoreboard.session().locale()); + if (!this.displayName.equals(convertedDisplayName)) { + this.displayName = convertedDisplayName; + markChanged(); + } + return; + } + // simplified from (this.displayName != null && displayName == null) || (this.displayName == null && displayName != null) + if (this.displayName != null || displayName != null) { + this.displayName = MessageTranslator.convertMessage(displayName, scoreboard.session().locale()); + markChanged(); + } + } + + public int score() { + return score; + } + + private void score(int score) { + boolean changed = this.score != score; + this.score = score; + if (changed) { + markChanged(); + } + } + + public NumberFormat numberFormat() { + return numberFormat; + } + + private void numberFormat(NumberFormat numberFormat) { + if (Objects.equals(numberFormat(), numberFormat)) { + return; + } + this.numberFormat = numberFormat; + markChanged(); + } + + public void updateProperties(Scoreboard scoreboard, int score, Component displayName, NumberFormat numberFormat) { + score(score); + displayName(displayName, scoreboard); + numberFormat(numberFormat); + } + + public long lastUpdate() { + return lastUpdate; + } + + public boolean isRemoved() { + return lastUpdate == LAST_UPDATE_REMOVE; + } + + public void markChanged() { + if (lastUpdate == LAST_UPDATE_REMOVE) { + return; + } + lastUpdate = System.currentTimeMillis(); + } + + public void markDeleted() { + lastUpdate = -1; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index acce86f4d..6e0867ddc 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -25,43 +25,72 @@ package org.geysermc.geyser.scoreboard; +import static org.geysermc.geyser.scoreboard.UpdateType.REMOVE; + import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.Getter; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumConstraint; -import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; -import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.display.slot.BelownameDisplaySlot; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; +import org.geysermc.geyser.scoreboard.display.slot.PlayerlistDisplaySlot; +import org.geysermc.geyser.scoreboard.display.slot.SidebarDisplaySlot; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; import org.jetbrains.annotations.Contract; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.geysermc.geyser.scoreboard.UpdateType.*; - +/** + * Here follows some information about how scoreboards work in Java Edition, that is related to the workings of this + * class: + *

+ * Objectives can be divided in two states: inactive and active. + * Inactive objectives is the default state for objectives that have been created using the SetObjective packet. + * Scores can be added, updated and removed, but as long as they're inactive they aren't shown to the player. + * An objective becomes active when a SetDisplayObjective packet is received, which contains the slot that + * the objective should be displayed at. + *

+ * While Bedrock can handle showing one objective on multiple slots at the same time, we have to help Bedrock a bit + * for example by limiting the amount of sidebar scores to the amount of lines that can be shown + * (otherwise Bedrock may lag) and only showing online players in the playerlist (otherwise it's too cluttered.) + * This fact is the biggest contributor for the class being structured like it is. + */ public final class Scoreboard { private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true")); private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(System.getProperty("Geyser.AddTeamSuggestions", "true")); private final GeyserSession session; private final GeyserLogger logger; - @Getter private final AtomicLong nextId = new AtomicLong(0); private final Map objectives = new ConcurrentHashMap<>(); @Getter - private final Map objectiveSlots = new EnumMap<>(ScoreboardPosition.class); + private final Map objectiveSlots = Collections.synchronizedMap(new EnumMap<>(ScoreboardPosition.class)); + private final List removedSlots = Collections.synchronizedList(new ArrayList<>()); + private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads /** * Required to preserve vanilla behavior, which also uses a map. @@ -71,6 +100,7 @@ public final class Scoreboard { @Getter private final Map playerToTeam = new Object2ObjectOpenHashMap<>(); + private final AtomicBoolean updateLockActive = new AtomicBoolean(false); private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -80,24 +110,22 @@ public final class Scoreboard { } public void removeScoreboard() { - Iterator iterator = objectives.values().iterator(); - while (iterator.hasNext()) { - Objective objective = iterator.next(); - iterator.remove(); + var copy = new HashMap<>(objectiveSlots); + objectiveSlots.clear(); - deleteObjective(objective, false); + for (DisplaySlot slot : copy.values()) { + slot.remove(); } } public @Nullable Objective registerNewObjective(String objectiveId) { Objective objective = objectives.get(objectiveId); if (objective != null) { - // we have no other choice, or we have to make a new map? - // if the objective hasn't been deleted, we have to force it - if (objective.getUpdateType() != REMOVE) { - return null; + // matches vanilla behaviour + if (SHOW_SCOREBOARD_LOGS) { + logger.warning("An objective with the same name '" + objectiveId + "' already exists! Ignoring new objective!"); } - deleteObjective(objective, true); + return null; } objective = new Objective(this, objectiveId); @@ -105,273 +133,162 @@ public final class Scoreboard { return objective; } - public void displayObjective(String objectiveId, ScoreboardPosition displaySlot) { + public void displayObjective(String objectiveId, ScoreboardPosition slot) { + if (objectiveId.isEmpty()) { + // matches vanilla behaviour + var display = objectiveSlots.get(slot); + if (display != null) { + removedSlots.add(display); + objectiveSlots.remove(slot, display); + var objective = display.objective(); + objective.removeDisplaySlot(display); + } + return; + } + Objective objective = objectives.get(objectiveId); if (objective == null) { return; } - if (!objective.isActive()) { - objective.setActive(displaySlot); - // for reactivated objectives - objective.setUpdateType(ADD); + var display = objectiveSlots.get(slot); + if (display != null && display.objective() != objective) { + removedSlots.add(display); } - Objective storedObjective = objectiveSlots.get(displaySlot); - if (storedObjective != null && storedObjective != objective) { - storedObjective.pendingRemove(); - } - objectiveSlots.put(displaySlot, objective); - - if (displaySlot == ScoreboardPosition.BELOW_NAME) { - // Display the below name score option to all players - // Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display - for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { - if (!entity.isValid()) { - // Player hasn't spawned yet - don't bother, it'll be done then - continue; - } - - entity.setBelowNameText(objective); - } - } + display = switch (DisplaySlot.slotCategory(slot)) { + case SIDEBAR -> new SidebarDisplaySlot(session, objective, slot); + case BELOW_NAME -> new BelownameDisplaySlot(session, objective); + case PLAYER_LIST -> new PlayerlistDisplaySlot(session, objective); + default -> throw new IllegalStateException("Unexpected value: " + slot); + }; + objectiveSlots.put(slot, display); + objective.addDisplaySlot(display); } - public Team registerNewTeam(String teamName, String[] players) { + public void registerNewTeam( + String teamName, + String[] players, + Component name, + Component prefix, + Component suffix, + NameTagVisibility visibility, + TeamColor color + ) { Team team = teams.get(teamName); if (team != null) { if (SHOW_SCOREBOARD_LOGS) { logger.info(GeyserLocale.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); } - return team; + return; } - team = new Team(this, teamName); - team.addEntities(players); + team = new Team(this, teamName, players, name, prefix, suffix, visibility, color); teams.put(teamName, team); // Update command parameters - is safe to send even if the command enum doesn't exist on the client (as of 1.19.51) if (ADD_TEAM_SUGGESTIONS) { - session.addCommandEnum("Geyser_Teams", team.getId()); + session.addCommandEnum("Geyser_Teams", team.id()); } - return team; } public void onUpdate() { + // if an update is already running, let it finish + if (updateLockActive.getAndSet(true)) { + return; + } + List addScores = new ArrayList<>(lastAddScoreCount); List removeScores = new ArrayList<>(lastRemoveScoreCount); - List removedObjectives = new ArrayList<>(); Team playerTeam = getTeamFor(session.getPlayerEntity().getUsername()); - Objective correctSidebar = null; + DisplaySlot correctSidebarSlot = null; - for (Objective objective : objectives.values()) { - // objective has been deleted - if (objective.getUpdateType() == REMOVE) { - removedObjectives.add(objective); + for (DisplaySlot slot : objectiveSlots.values()) { + // slot has been removed + if (slot.updateType() == REMOVE) { continue; } - // there's nothing we can do with inactive objectives - // after checking if the objective has been deleted, - // except waiting for the objective to become activated (: - if (!objective.isActive()) { - continue; - } - - if (playerTeam != null && playerTeam.getColor() == objective.getTeamColor()) { - correctSidebar = objective; + if (playerTeam != null && playerTeam.color() == slot.teamColor()) { + correctSidebarSlot = slot; } } - if (correctSidebar == null) { - correctSidebar = objectiveSlots.get(ScoreboardPosition.SIDEBAR); + if (correctSidebarSlot == null) { + correctSidebarSlot = objectiveSlots.get(ScoreboardPosition.SIDEBAR); } - for (Objective objective : removedObjectives) { + var actualRemovedSlots = new ArrayList<>(removedSlots); + for (var slot : actualRemovedSlots) { // Deletion must be handled before the active objectives are handled - otherwise if a scoreboard display is changed before the current // scoreboard is removed, the client can crash - deleteObjective(objective, true); + slot.remove(); } + removedSlots.removeAll(actualRemovedSlots); - handleObjective(objectiveSlots.get(ScoreboardPosition.PLAYER_LIST), addScores, removeScores); - handleObjective(correctSidebar, addScores, removeScores); - handleObjective(objectiveSlots.get(ScoreboardPosition.BELOW_NAME), addScores, removeScores); - - Iterator teamIterator = teams.values().iterator(); - while (teamIterator.hasNext()) { - Team current = teamIterator.next(); - - switch (current.getCachedUpdateType()) { - case ADD, UPDATE -> current.markUpdated(); - case REMOVE -> teamIterator.remove(); - } - } + handleDisplaySlot(objectiveSlots.get(ScoreboardPosition.PLAYER_LIST), addScores, removeScores); + handleDisplaySlot(correctSidebarSlot, addScores, removeScores); + handleDisplaySlot(objectiveSlots.get(ScoreboardPosition.BELOW_NAME), addScores, removeScores); if (!removeScores.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.REMOVE); - setScorePacket.setInfos(removeScores); - session.sendUpstreamPacket(setScorePacket); + SetScorePacket packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(removeScores); + session.sendUpstreamPacket(packet); } if (!addScores.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.SET); - setScorePacket.setInfos(addScores); - session.sendUpstreamPacket(setScorePacket); + SetScorePacket packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(addScores); + session.sendUpstreamPacket(packet); } lastAddScoreCount = addScores.size(); lastRemoveScoreCount = removeScores.size(); + updateLockActive.set(false); } - private void handleObjective(Objective objective, List addScores, List removeScores) { - if (objective == null || objective.getUpdateType() == REMOVE) { - return; + private void handleDisplaySlot(DisplaySlot slot, List addScores, List removeScores) { + if (slot != null) { + slot.render(addScores, removeScores); } - - // hearts can't hold teams, so we treat them differently - if (objective.getType() == 1) { - for (Score score : objective.getScores().values()) { - boolean update = score.shouldUpdate(); - - if (update) { - score.update(objective); - } - - if (score.getUpdateType() != REMOVE && update) { - addScores.add(score.getCachedInfo()); - } - if (score.getUpdateType() != ADD && update) { - removeScores.add(score.getCachedInfo()); - } - } - return; - } - - boolean objectiveAdd = objective.getUpdateType() == ADD; - boolean objectiveUpdate = objective.getUpdateType() == UPDATE; - - for (Score score : objective.getScores().values()) { - if (score.getUpdateType() == REMOVE) { - ScoreInfo cachedInfo = score.getCachedInfo(); - // cachedInfo can be null here when ScoreboardUpdater is being used and a score is added and - // removed before a single update cycle is performed - if (cachedInfo != null) { - removeScores.add(cachedInfo); - } - // score is pending to be removed, so we can remove it from the objective - objective.removeScore0(score.getName()); - break; - } - - Team team = score.getTeam(); - - boolean add = objectiveAdd || objectiveUpdate; - - if (team != null) { - if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { - score.setTeam(null); - add = true; - } - } - - if (score.shouldUpdate()) { - score.update(objective); - add = true; - } - - if (add) { - addScores.add(score.getCachedInfo()); - } - - // we need this as long as MCPE-143063 hasn't been fixed. - // the checks after 'add' are there to prevent removing scores that - // are going to be removed anyway / don't need to be removed - if (add && score.getUpdateType() != ADD && !(objectiveUpdate || objectiveAdd)) { - removeScores.add(score.getCachedInfo()); - } - - score.setUpdateType(NOTHING); - } - - if (objectiveUpdate) { - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.sendUpstreamPacket(removeObjectivePacket); - } - - if (objectiveAdd || objectiveUpdate) { - SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); - displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); - displayObjectivePacket.setDisplayName(objective.getDisplayName()); - displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); - displayObjectivePacket.setSortOrder(1); // 0 = ascending, 1 = descending - session.sendUpstreamPacket(displayObjectivePacket); - } - - objective.setUpdateType(NOTHING); - } - - /** - * @param remove if we should remove the objective from the objectives map. - */ - public void deleteObjective(Objective objective, boolean remove) { - if (remove) { - objectives.remove(objective.getObjectiveName()); - } - objectiveSlots.remove(objective.getDisplaySlot(), objective); - - objective.removed(); - - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.sendUpstreamPacket(removeObjectivePacket); } public Objective getObjective(String objectiveName) { return objectives.get(objectiveName); } - public Collection getObjectives() { - return objectives.values(); - } - - public void unregisterObjective(String objectiveName) { - Objective objective = getObjective(objectiveName); - if (objective != null) { - objective.pendingRemove(); + public void removeObjective(Objective objective) { + objectives.remove(objective.getObjectiveName()); + for (DisplaySlot slot : objective.getActiveSlots()) { + objectiveSlots.remove(slot.position(), slot); + removedSlots.add(slot); } } - public Objective getSlot(ScoreboardPosition slot) { - return objectiveSlots.get(slot); + public void resetPlayerScores(String playerNameOrEntityUuid) { + for (Objective objective : objectives.values()) { + objective.removeScore(playerNameOrEntityUuid); + } } public Team getTeam(String teamName) { return teams.get(teamName); } - public Team getTeamFor(String entity) { - return playerToTeam.get(entity); + public Team getTeamFor(String playerNameOrEntityUuid) { + return playerToTeam.get(playerNameOrEntityUuid); } public void removeTeam(String teamName) { Team remove = teams.remove(teamName); - if (remove != null) { - remove.setUpdateType(REMOVE); - // We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly - // With the player's lack of a team in visibility checks - updateEntityNames(remove, remove.getEntities(), true); - for (String name : remove.getEntities()) { - // 1.19.3 Mojmap Scoreboard#removePlayerTeam(PlayerTeam) - playerToTeam.remove(name); - } - - session.removeCommandEnum("Geyser_Teams", remove.getId()); + if (remove == null) { + return; } + remove.remove(); + session.removeCommandEnum("Geyser_Teams", remove.id()); } @Contract("-> new") @@ -381,48 +298,46 @@ public final class Scoreboard { (o1, o2) -> o1, LinkedHashMap::new)); } - /** - * Updates the display names of all entities in a given team. - * @param teamChange the players have either joined or left the team. Used for optimizations when just the display name updated. - */ - public void updateEntityNames(Team team, boolean teamChange) { - Set names = new HashSet<>(team.getEntities()); - updateEntityNames(team, names, teamChange); + public void playerRegistered(PlayerEntity player) { + for (DisplaySlot slot : objectiveSlots.values()) { + slot.playerRegistered(player); + } } - /** - * Updates the display name of a set of entities within a given team. The team may also be null if the set is being removed - * from a team. - */ - public void updateEntityNames(@Nullable Team team, Set names, boolean teamChange) { - if (names.remove(session.getPlayerEntity().getUsername()) && teamChange) { - // If the player's team changed, then other entities' teams may modify their visibility based on team status - refreshSessionPlayerDisplays(); + public void playerRemoved(PlayerEntity player) { + for (DisplaySlot slot : objectiveSlots.values()) { + slot.playerRemoved(player); } - if (!names.isEmpty()) { - for (Entity entity : session.getEntityCache().getEntities().values()) { - // This more complex logic is for the future to iterate over all entities, not just players - if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) { - player.updateDisplayName(team); - player.updateBedrockMetadata(); - if (names.isEmpty()) { - break; - } - } + } + + public void entityRegistered(Entity entity) { + var team = getTeamFor(entity.teamIdentifier()); + if (team != null) { + team.onEntitySpawn(entity); + } + } + + public void entityRemoved(Entity entity) { + var team = getTeamFor(entity.teamIdentifier()); + if (team != null) { + team.onEntityRemove(entity); + } + } + + public void setTeamFor(Team team, Set entities) { + for (DisplaySlot slot : objectiveSlots.values()) { + // only sidebar slots use teams + if (slot instanceof SidebarDisplaySlot sidebar) { + sidebar.setTeamFor(team, entities); } } } - /** - * If the team's player was refreshed, then we need to go through every entity and check... - */ - private void refreshSessionPlayerDisplays() { - for (Entity entity : session.getEntityCache().getEntities().values()) { - if (entity instanceof PlayerEntity player) { - Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername()); - player.updateDisplayName(playerTeam); - player.updateBedrockMetadata(); - } - } + public long nextId() { + return nextId.getAndIncrement(); + } + + public GeyserSession session() { + return session; } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java index 395eb9576..18a4bce39 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java @@ -173,7 +173,6 @@ public final class ScoreboardUpdater extends Thread { @Getter public static final class ScoreboardSession { private final GeyserSession session; - @SuppressWarnings("WriteOnlyObject") private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); private int packetsPerSecond; private long lastUpdate; diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index cdf2e247e..d7c06ac4f 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -25,48 +25,66 @@ package org.geysermc.geyser.scoreboard; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashSet; import java.util.Set; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; -@Getter -@Accessors(chain = true) public final class Team { + public static final long LAST_UPDATE_DEFAULT = -1; + private static final long LAST_UPDATE_REMOVE = -2; + private final Scoreboard scoreboard; private final String id; - @Getter(AccessLevel.PACKAGE) private final Set entities; + private final Set managedEntities; @NonNull private NameTagVisibility nameTagVisibility = NameTagVisibility.ALWAYS; - @Setter private TeamColor color; + private TeamColor color; - private final TeamData currentData; - private TeamData cachedData; + private String name; + private String prefix; + private String suffix; + private long lastUpdate; - private boolean updating; - - public Team(Scoreboard scoreboard, String id) { + public Team( + Scoreboard scoreboard, + String id, + String[] players, + Component name, + Component prefix, + Component suffix, + NameTagVisibility visibility, + TeamColor color + ) { this.scoreboard = scoreboard; this.id = id; - currentData = new TeamData(); - entities = new ObjectOpenHashSet<>(); + this.entities = new ObjectOpenHashSet<>(); + this.managedEntities = new ObjectOpenHashSet<>(); + this.lastUpdate = LAST_UPDATE_DEFAULT; + + // doesn't call entity update + updateProperties(name, prefix, suffix, visibility, color); + // calls entity update + addEntities(players); + lastUpdate = LAST_UPDATE_DEFAULT; } - public Set addEntities(String... names) { + public void addEntities(String... names) { Set added = new HashSet<>(); for (String name : names) { - if (entities.add(name)) { - added.add(name); + // go to next score if score is already present + if (!entities.add(name)) { + continue; } + added.add(name); scoreboard.getPlayerToTeam().compute(name, (player, oldTeam) -> { if (oldTeam != null) { // Remove old team from this map, and from the set of players of the old team. @@ -78,26 +96,15 @@ public final class Team { } if (added.isEmpty()) { - return added; + return; } - // we don't have to change the updateType, - // because the scores itself need updating, not the team - for (Objective objective : scoreboard.getObjectives()) { - for (String addedEntity : added) { - Score score = objective.getScores().get(addedEntity); - if (score != null) { - score.setTeam(this); - } - } - } - - return added; + // we don't have to change our updateType, + // because the scores themselves need updating, not the team + scoreboard.setTeamFor(this, added); + addAddedEntities(added); } - /** - * @return all removed entities from this team - */ - public Set removeEntities(String... names) { + public void removeEntities(String... names) { Set removed = new HashSet<>(); for (String name : names) { if (entities.remove(name)) { @@ -105,87 +112,22 @@ public final class Team { } scoreboard.getPlayerToTeam().remove(name, this); } - return removed; + removeRemovedEntities(removed); } public boolean hasEntity(String name) { return entities.contains(name); } - public Team setName(String name) { - currentData.name = name; - return this; - } - - public Team setPrefix(String prefix) { - // replace "null" to an empty string, - // we do this here to improve the performance of Score#getDisplayName - if (prefix.length() == 4 && "null".equals(prefix)) { - currentData.prefix = ""; - return this; + public String displayName(String score) { + String chatColor = ChatColor.chatColorFor(color); + // most sidebar plugins will use the reset color, because they don't want color + // skip the unneeded double reset color in that case + if (ChatColor.RESET.equals(chatColor)) { + chatColor = ""; } - currentData.prefix = prefix; - return this; - } - - public Team setSuffix(String suffix) { - // replace "null" to an empty string, - // we do this here to improve the performance of Score#getDisplayName - if (suffix.length() == 4 && "null".equals(suffix)) { - currentData.suffix = ""; - return this; - } - currentData.suffix = suffix; - return this; - } - - public String getDisplayName(String score) { - return cachedData != null ? - cachedData.getDisplayName(score) : - currentData.getDisplayName(score); - } - - public void markUpdated() { - updating = false; - } - - public boolean shouldUpdate() { - return updating || cachedData == null || currentData.changed; - } - - public void prepareUpdate() { - if (updating) { - return; - } - updating = true; - - if (cachedData == null) { - cachedData = new TeamData(); - cachedData.updateType = currentData.updateType != UpdateType.REMOVE ? UpdateType.ADD : UpdateType.REMOVE; - } else { - cachedData.updateType = currentData.updateType; - } - - currentData.changed = false; - cachedData.name = currentData.name; - cachedData.prefix = currentData.prefix; - cachedData.suffix = currentData.suffix; - } - - public UpdateType getUpdateType() { - return currentData.updateType; - } - - public UpdateType getCachedUpdateType() { - return cachedData != null ? cachedData.updateType : currentData.updateType; - } - - public Team setUpdateType(UpdateType updateType) { - if (updateType != UpdateType.NOTHING) { - currentData.changed = true; - } - currentData.updateType = updateType; - return this; + // also add reset because setting the color does not reset the formatting, unlike Java + return chatColor + prefix + ChatColor.RESET + chatColor + score + ChatColor.RESET + chatColor + suffix; } public boolean isVisibleFor(String entity) { @@ -201,34 +143,178 @@ public final class Team { }; } - public Team setNameTagVisibility(@Nullable NameTagVisibility nameTagVisibility) { - if (nameTagVisibility != null) { - // Null check like this (and this.nameTagVisibility defaults to ALWAYS) as of Java 1.19.4 - this.nameTagVisibility = nameTagVisibility; + public void updateProperties(Component name, Component prefix, Component suffix, NameTagVisibility visibility, TeamColor color) { + // this shouldn't happen but hey! + if (lastUpdate == LAST_UPDATE_REMOVE) { + return; } - return this; + + String oldName = this.name; + String oldPrefix = this.prefix; + String oldSuffix = this.suffix; + boolean oldVisible = isVisibleFor(playerName()); + var oldColor = this.color; + + this.name = MessageTranslator.convertMessageRaw(name, session().locale()); + this.prefix = MessageTranslator.convertMessageRaw(prefix, session().locale()); + this.suffix = MessageTranslator.convertMessageRaw(suffix, session().locale()); + // matches vanilla behaviour, the visibility is not reset (to ALWAYS) if it is null. + // instead the visibility is not altered + if (visibility != null) { + this.nameTagVisibility = visibility; + } + this.color = color; + + if (lastUpdate == LAST_UPDATE_DEFAULT) { + // addEntities is called after the initial updateProperties, so no need to do any entity updates here + if (this.color != TeamColor.RESET || !this.prefix.isEmpty() || !this.suffix.isEmpty()) { + markChanged(); + } + return; + } + + if (!this.name.equals(oldName) + || !this.prefix.equals(oldPrefix) + || !this.suffix.equals(oldSuffix) + || color != oldColor) { + markChanged(); + updateEntities(); + return; + } + + if (isVisibleFor(playerName()) != oldVisible) { + // if just the visibility changed, we only have to update the entities. + // We don't have to mark it as changed + updateEntities(); + } + } + + public boolean shouldRemove() { + return lastUpdate == LAST_UPDATE_REMOVE; + } + + public void markChanged() { + if (lastUpdate == LAST_UPDATE_REMOVE) { + return; + } + lastUpdate = System.currentTimeMillis(); + } + + public void remove() { + lastUpdate = LAST_UPDATE_REMOVE; + + for (String name : entities()) { + // 1.19.3 Mojmap Scoreboard#removePlayerTeam(PlayerTeam) + scoreboard.getPlayerToTeam().remove(name); + } + + if (entities().contains(playerName())) { + refreshAllEntities(); + return; + } + for (Entity entity : managedEntities) { + entity.updateNametag(null); + entity.updateBedrockMetadata(); + } + } + + private void updateEntities() { + for (Entity entity : managedEntities) { + entity.updateNametag(this); + entity.updateBedrockMetadata(); + } + } + + public void onEntitySpawn(Entity entity) { + // I've basically ported addAddedEntities + if (entities.contains(entity.teamIdentifier())) { + managedEntities.add(entity); + // onEntitySpawn includes all entities but players, so it cannot contain self + entity.updateNametag(this); + entity.updateBedrockMetadata(); + } + } + + public void onEntityRemove(Entity entity) { + // we don't have to update anything, since the player is removed. + managedEntities.remove(entity); + } + + private void addAddedEntities(Set names) { + // can't contain self if none are added + if (names.isEmpty()) { + return; + } + boolean containsSelf = names.contains(playerName()); + + for (Entity entity : session().getEntityCache().getEntities().values()) { + if (names.contains(entity.teamIdentifier())) { + managedEntities.add(entity); + if (!containsSelf) { + entity.updateNametag(this); + entity.updateBedrockMetadata(); + } + } + } + + if (containsSelf) { + refreshAllEntities(); + } + } + + private void removeRemovedEntities(Set names) { + boolean containsSelf = names.contains(playerName()); + + var iterator = managedEntities.iterator(); + while (iterator.hasNext()) { + var entity = iterator.next(); + if (names.contains(entity.teamIdentifier())) { + iterator.remove(); + if (!containsSelf) { + entity.updateNametag(null); + entity.updateBedrockMetadata(); + } + } + } + + if (containsSelf) { + refreshAllEntities(); + } + } + + private void refreshAllEntities() { + for (Entity entity : session().getEntityCache().getEntities().values()) { + entity.updateNametag(scoreboard.getTeamFor(entity.teamIdentifier())); + entity.updateBedrockMetadata(); + } + } + + private GeyserSession session() { + return scoreboard.session(); + } + + private String playerName() { + return session().getPlayerEntity().getUsername(); + } + + public String id() { + return id; + } + + public TeamColor color() { + return color; + } + + public long lastUpdate() { + return lastUpdate; + } + + public Set entities() { + return entities; } @Override public int hashCode() { return id.hashCode(); } - - @Getter - public static final class TeamData { - private UpdateType updateType; - private boolean changed; - - private String name; - private String prefix; - private String suffix; - - private TeamData() { - updateType = UpdateType.ADD; - } - - public String getDisplayName(String score) { - return prefix + score + suffix; - } - } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/BelownameDisplayScore.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/BelownameDisplayScore.java new file mode 100644 index 000000000..8e101d66a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/BelownameDisplayScore.java @@ -0,0 +1,56 @@ +/* + * 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.scoreboard.display.score; + +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; + +public class BelownameDisplayScore extends DisplayScore { + private final PlayerEntity player; + + public BelownameDisplayScore(DisplaySlot slot, long scoreId, ScoreReference reference, PlayerEntity player) { + super(slot, scoreId, reference); + this.player = player; + } + + @Override + public void update(Objective objective) {} + + public PlayerEntity player() { + return player; + } + + @Override + public void markUpdated() { + super.markUpdated(); + } + + public ScoreReference reference() { + return reference; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/DisplayScore.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/DisplayScore.java new file mode 100644 index 000000000..c6d70bb96 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/DisplayScore.java @@ -0,0 +1,70 @@ +/* + * 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.scoreboard.display.score; + +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; + +public abstract class DisplayScore { + protected final DisplaySlot slot; + protected final long id; + protected final ScoreReference reference; + + protected long lastTeamUpdate; + protected long lastUpdate; + + public DisplayScore(DisplaySlot slot, long scoreId, ScoreReference reference) { + this.slot = slot; + this.id = scoreId; + this.reference = reference; + } + + public boolean shouldUpdate() { + return reference.lastUpdate() != lastUpdate; + } + + public abstract void update(Objective objective); + + public String name() { + return reference.name(); + } + + public int score() { + return reference.score(); + } + + public boolean referenceRemoved() { + return reference.isRemoved(); + } + + protected void markUpdated() { + // with the last update (also for team) we rather have an old lastUpdate + // (and have to update again the next cycle) than potentially losing information + // by fetching the lastUpdate after update was performed + this.lastUpdate = reference.lastUpdate(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/PlayerlistDisplayScore.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/PlayerlistDisplayScore.java new file mode 100644 index 000000000..c4d8d91be --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/PlayerlistDisplayScore.java @@ -0,0 +1,61 @@ +/* + * 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.scoreboard.display.score; + +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; + +public final class PlayerlistDisplayScore extends DisplayScore { + private final long playerId; + private ScoreInfo cachedInfo; + + public PlayerlistDisplayScore(DisplaySlot slot, long scoreId, ScoreReference reference, long playerId) { + super(slot, scoreId, reference); + this.playerId = playerId; + } + + @Override + public boolean shouldUpdate() { + // for player references the player's name is shown, + // so we only have to update when the score has changed + return cachedInfo == null || cachedInfo.getScore() != reference.score(); + } + + @Override + public void update(Objective objective) { + cachedInfo = new ScoreInfo(id, slot.objectiveId(), reference.score(), ScoreInfo.ScorerType.PLAYER, playerId); + } + + public ScoreInfo cachedInfo() { + return cachedInfo; + } + + public boolean exists() { + return cachedInfo != null; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java new file mode 100644 index 000000000..42c0dbbf7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java @@ -0,0 +1,139 @@ +/* + * 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.scoreboard.display.score; + +import java.util.Objects; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.Team; +import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.FixedFormat; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; + +public final class SidebarDisplayScore extends DisplayScore { + private ScoreInfo cachedInfo; + private Team team; + private String order; + private boolean onlyScoreValueChanged; + + public SidebarDisplayScore(DisplaySlot slot, long scoreId, ScoreReference reference) { + super(slot, scoreId, reference); + team(slot.objective().getScoreboard().getTeamFor(reference.name())); + } + + @Override + public boolean shouldUpdate() { + return super.shouldUpdate() || shouldTeamUpdate(); + } + + private boolean shouldTeamUpdate() { + return team != null && team.lastUpdate() != lastTeamUpdate; + } + + @Override + public void update(Objective objective) { + markUpdated(); + + String finalName = reference.name(); + String displayName = reference.displayName(); + + if (displayName != null) { + finalName = displayName; + } else if (team != null) { + this.lastTeamUpdate = team.lastUpdate(); + finalName = team.displayName(reference.name()); + } + + NumberFormat numberFormat = reference.numberFormat(); + if (numberFormat == null) { + numberFormat = objective.getNumberFormat(); + } + if (numberFormat instanceof FixedFormat fixedFormat) { + finalName += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue(), objective.getScoreboard().session().locale()); + } + + if (order != null) { + finalName = order + ChatColor.RESET + finalName; + } + + if (cachedInfo != null) { + onlyScoreValueChanged = finalName.equals(cachedInfo.getName()); + } + cachedInfo = new ScoreInfo(id, slot.objectiveId(), reference.score(), finalName); + } + + public String order() { + return order; + } + + public DisplayScore order(String order) { + if (Objects.equals(this.order, order)) { + return this; + } + this.order = order; + // this guarantees an update + requestUpdate(); + return this; + } + + public Team team() { + return team; + } + + public void team(Team team) { + if (this.team != null && team != null) { + if (!this.team.equals(team)) { + this.team = team; + requestUpdate(); + } + return; + } + // simplified from (this.team != null && team == null) || (this.team == null && team != null) + if (this.team != null || team != null) { + this.team = team; + requestUpdate(); + } + } + + private void requestUpdate() { + this.lastUpdate = 0; + } + + public ScoreInfo cachedInfo() { + return cachedInfo; + } + + public boolean exists() { + return cachedInfo != null; + } + + public boolean onlyScoreValueChanged() { + return onlyScoreValueChanged; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/BelownameDisplaySlot.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/BelownameDisplaySlot.java new file mode 100644 index 000000000..42a1e8c3f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/BelownameDisplaySlot.java @@ -0,0 +1,182 @@ +/* + * 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.scoreboard.display.slot; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.List; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.scoreboard.display.score.BelownameDisplayScore; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.codec.NbtComponentSerializer; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.BlankFormat; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.FixedFormat; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat; +import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.StyledFormat; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; + +public class BelownameDisplaySlot extends DisplaySlot { + private final Long2ObjectMap displayScores = new Long2ObjectOpenHashMap<>(); + + public BelownameDisplaySlot(GeyserSession session, Objective objective) { + super(session, objective, ScoreboardPosition.BELOW_NAME); + } + + @Override + protected void render0(List addScores, List removeScores) { + // how belowname works is that if the player itself has belowname as a display slot, + // every player entity will show a score below their name. + // when the objective is added, updated or removed we thus have to update the belowname for every player + // when an individual score is updated (score or number format) we have to update the individual player + + // remove is handled in #remove() + if (updateType == UpdateType.ADD) { + for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) { + playerRegistered(player); + } + return; + } + if (updateType == UpdateType.UPDATE) { + for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) { + setBelowNameText(player, scoreFor(player.getUsername())); + } + updateType = UpdateType.NOTHING; + return; + } + + for (var score : displayScores.values()) { + // we don't have to worry about a score not existing, because that's handled by both + // this method when an objective is added and addScore/playerRegistered. + // we only have to update them, if they have changed + // (or delete them, if the score no longer exists) + if (!score.shouldUpdate()) { + continue; + } + + if (score.referenceRemoved()) { + clearBelowNameText(score.player()); + continue; + } + + score.markUpdated(); + setBelowNameText(score.player(), score.reference()); + } + } + + @Override + public void remove() { + updateType = UpdateType.REMOVE; + for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) { + clearBelowNameText(player); + } + } + + @Override + public void addScore(ScoreReference reference) { + addDisplayScore(reference); + } + + @Override + public void playerRegistered(PlayerEntity player) { + var reference = scoreFor(player.getUsername()); + setBelowNameText(player, reference); + // keep track of score when the player is active + if (reference != null) { + // we already set the text, so we only have to update once the score does + addDisplayScore(player, reference).markUpdated(); + } + } + + @Override + public void playerRemoved(PlayerEntity player) { + displayScores.remove(player.getGeyserId()); + } + + private void addDisplayScore(ScoreReference reference) { + var players = session.getEntityCache().getPlayersByName(reference.name()); + for (PlayerEntity player : players) { + addDisplayScore(player, reference); + } + } + + private BelownameDisplayScore addDisplayScore(PlayerEntity player, ScoreReference reference) { + var score = new BelownameDisplayScore(this, objective.getScoreboard().nextId(), reference, player); + displayScores.put(player.getGeyserId(), score); + return score; + } + + private void setBelowNameText(PlayerEntity player, ScoreReference reference) { + player.setBelowNameText(calculateBelowNameText(reference)); + player.updateBedrockMetadata(); + } + + private void clearBelowNameText(PlayerEntity player) { + player.setBelowNameText(null); + player.updateBedrockMetadata(); + } + + private String calculateBelowNameText(ScoreReference reference) { + String numberString; + NumberFormat numberFormat = null; + // even if the player doesn't have a score, as long as belowname is on the client Java behaviour is + // to show them with a score of 0 + int score = 0; + if (reference != null) { + score = reference.score(); + numberFormat = reference.numberFormat(); + } + if (numberFormat == null) { + numberFormat = objective.getNumberFormat(); + } + + if (numberFormat instanceof BlankFormat) { + numberString = ""; + } else if (numberFormat instanceof FixedFormat fixedFormat) { + numberString = MessageTranslator.convertMessage(fixedFormat.getValue(), session.locale()); + } else if (numberFormat instanceof StyledFormat styledFormat) { + NbtMapBuilder styledAmount = styledFormat.getStyle().toBuilder(); + styledAmount.putString("text", String.valueOf(score)); + + numberString = MessageTranslator.convertJsonMessage( + NbtComponentSerializer.tagComponentToJson(styledAmount.build()).toString(), session.locale()); + } else { + numberString = String.valueOf(score); + } + + return numberString + " " + ChatColor.RESET + objective.getDisplayName(); + } + + private ScoreReference scoreFor(String username) { + return objective.getScores().get(username); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/DisplaySlot.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/DisplaySlot.java new file mode 100644 index 000000000..bac79e23e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/DisplaySlot.java @@ -0,0 +1,162 @@ +/* + * 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.scoreboard.display.slot; + +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; + +public abstract class DisplaySlot { + protected final GeyserSession session; + protected final Objective objective; + /** + * Use this instead of objective name because one objective can be shared in multiple slots, + * but each slot has its own logic and might not contain all scores + */ + protected final String objectiveId; + protected final ScoreboardPosition slot; + protected final TeamColor teamColor; + protected final String positionName; + + protected UpdateType updateType = UpdateType.ADD; + + public DisplaySlot(GeyserSession session, Objective objective, ScoreboardPosition slot) { + this.session = session; + this.objective = objective; + this.objectiveId = String.valueOf(objective.getScoreboard().nextId()); + this.slot = slot; + this.teamColor = teamColor(slot); + this.positionName = positionName(slot); + } + + public final void render(List addScores, List removeScores) { + if (updateType == UpdateType.REMOVE) { + return; + } + render0(addScores, removeScores); + } + + protected abstract void render0(List addScores, List removeScores); + + public abstract void addScore(ScoreReference reference); + + public abstract void playerRegistered(PlayerEntity player); + public abstract void playerRemoved(PlayerEntity player); + + public void remove() { + updateType = UpdateType.REMOVE; + sendRemoveObjective(); + } + + public void markNeedsUpdate() { + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } + } + + protected void sendDisplayObjective() { + SetDisplayObjectivePacket packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId(objectiveId()); + packet.setDisplayName(objective.getDisplayName()); + packet.setCriteria("dummy"); + packet.setDisplaySlot(positionName); + packet.setSortOrder(1); // 0 = ascending, 1 = descending + session.sendUpstreamPacket(packet); + } + + protected void sendRemoveObjective() { + RemoveObjectivePacket packet = new RemoveObjectivePacket(); + packet.setObjectiveId(objectiveId()); + session.sendUpstreamPacket(packet); + } + + public Objective objective() { + return objective; + } + + public String objectiveId() { + return objectiveId; + } + + public ScoreboardPosition position() { + return slot; + } + + public @Nullable TeamColor teamColor() { + return teamColor; + } + + public UpdateType updateType() { + return updateType; + } + + public static ScoreboardPosition slotCategory(ScoreboardPosition slot) { + return switch (slot) { + case BELOW_NAME -> ScoreboardPosition.BELOW_NAME; + case PLAYER_LIST -> ScoreboardPosition.PLAYER_LIST; + default -> ScoreboardPosition.SIDEBAR; + }; + } + + private static String positionName(ScoreboardPosition slot) { + return switch (slot) { + case BELOW_NAME -> "belowname"; + case PLAYER_LIST -> "list"; + default -> "sidebar"; + }; + } + + private static @Nullable TeamColor teamColor(ScoreboardPosition slot) { + return switch (slot) { + case SIDEBAR_TEAM_RED -> TeamColor.RED; + case SIDEBAR_TEAM_AQUA -> TeamColor.AQUA; + case SIDEBAR_TEAM_BLUE -> TeamColor.BLUE; + case SIDEBAR_TEAM_GOLD -> TeamColor.GOLD; + case SIDEBAR_TEAM_GRAY -> TeamColor.GRAY; + case SIDEBAR_TEAM_BLACK -> TeamColor.BLACK; + case SIDEBAR_TEAM_GREEN -> TeamColor.GREEN; + case SIDEBAR_TEAM_WHITE -> TeamColor.WHITE; + case SIDEBAR_TEAM_YELLOW -> TeamColor.YELLOW; + case SIDEBAR_TEAM_DARK_RED -> TeamColor.DARK_RED; + case SIDEBAR_TEAM_DARK_AQUA -> TeamColor.DARK_AQUA; + case SIDEBAR_TEAM_DARK_BLUE -> TeamColor.DARK_BLUE; + case SIDEBAR_TEAM_DARK_GRAY -> TeamColor.DARK_GRAY; + case SIDEBAR_TEAM_DARK_GREEN -> TeamColor.DARK_GREEN; + case SIDEBAR_TEAM_DARK_PURPLE -> TeamColor.DARK_PURPLE; + case SIDEBAR_TEAM_LIGHT_PURPLE -> TeamColor.LIGHT_PURPLE; + default -> null; + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/PlayerlistDisplaySlot.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/PlayerlistDisplaySlot.java new file mode 100644 index 000000000..6fd83ab8d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/PlayerlistDisplaySlot.java @@ -0,0 +1,158 @@ +/* + * 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.scoreboard.display.slot; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.scoreboard.display.score.PlayerlistDisplayScore; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; + +public class PlayerlistDisplaySlot extends DisplaySlot { + private final Long2ObjectMap displayScores = + Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private final List removedScores = Collections.synchronizedList(new ArrayList<>()); + + public PlayerlistDisplaySlot(GeyserSession session, Objective objective) { + super(session, objective, ScoreboardPosition.PLAYER_LIST); + registerExisting(); + } + + @Override + protected void render0(List addScores, List removeScores) { + boolean objectiveAdd = updateType == UpdateType.ADD; + boolean objectiveUpdate = updateType == UpdateType.UPDATE; + boolean objectiveNothing = updateType == UpdateType.NOTHING; + + // if 'add' the scores aren't present, if 'update' the objective is re-added so the scores don't have to be + // manually removed, if 'remove' the scores are removed anyway + if (objectiveNothing) { + var removedScoresCopy = new ArrayList<>(removedScores); + for (var removedScore : removedScoresCopy) { + //todo idk if this if-statement is needed + if (removedScore.cachedInfo() != null) { + removeScores.add(removedScore.cachedInfo()); + } + } + removedScores.removeAll(removedScoresCopy); + } else { + removedScores.clear(); + } + + for (var score : displayScores.values()) { + if (score.referenceRemoved()) { + ScoreInfo cachedInfo = score.cachedInfo(); + // cachedInfo can be null here when ScoreboardUpdater is being used and a score is added and + // removed before a single update cycle is performed + if (cachedInfo != null) { + removeScores.add(cachedInfo); + } + continue; + } + + //todo does an animated title exist on tab? + boolean add = objectiveAdd || objectiveUpdate; + boolean exists = score.exists(); + + if (score.shouldUpdate()) { + score.update(objective); + add = true; + } + + if (add) { + addScores.add(score.cachedInfo()); + } + + // we need this as long as MCPE-143063 hasn't been fixed. + // the checks after 'add' are there to prevent removing scores that + // are going to be removed anyway / don't need to be removed + if (add && exists && objectiveNothing) { + removeScores.add(score.cachedInfo()); + } + } + + if (objectiveUpdate) { + sendRemoveObjective(); + } + + if (objectiveAdd || objectiveUpdate) { + sendDisplayObjective(); + } + + updateType = UpdateType.NOTHING; + } + + @Override + public void addScore(ScoreReference reference) { + // while it breaks a lot of stuff in Java, scoreboard do work fine with multiple players having + // the same username + var players = session.getEntityCache().getPlayersByName(reference.name()); + var selfPlayer = session.getPlayerEntity(); + if (reference.name().equals(selfPlayer.getUsername())) { + players.add(selfPlayer); + } + + for (PlayerEntity player : players) { + var score = + new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId()); + displayScores.put(player.getGeyserId(), score); + } + } + + private void registerExisting() { + playerRegistered(session.getPlayerEntity()); + session.getEntityCache().getAllPlayerEntities().forEach(this::playerRegistered); + } + + @Override + public void playerRegistered(PlayerEntity player) { + var reference = objective.getScores().get(player.getUsername()); + if (reference == null) { + return; + } + var score = + new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId()); + displayScores.put(player.getGeyserId(), score); + } + + @Override + public void playerRemoved(PlayerEntity player) { + var score = displayScores.remove(player.getGeyserId()); + if (score == null) { + return; + } + removedScores.add(score); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java new file mode 100644 index 000000000..24cc81f78 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java @@ -0,0 +1,189 @@ +/* + * 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.scoreboard.display.slot; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.ScoreReference; +import org.geysermc.geyser.scoreboard.Team; +import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.scoreboard.display.score.SidebarDisplayScore; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; + +public final class SidebarDisplaySlot extends DisplaySlot { + private static final int SCORE_DISPLAY_LIMIT = 15; + private static final Comparator SCORE_DISPLAY_ORDER = + Comparator.comparing(ScoreReference::score) + .reversed() + .thenComparing(ScoreReference::name, String.CASE_INSENSITIVE_ORDER); + + private List displayScores = new ArrayList<>(SCORE_DISPLAY_LIMIT); + + public SidebarDisplaySlot(GeyserSession session, Objective objective, ScoreboardPosition position) { + super(session, objective, position); + } + + @Override + protected void render0(List addScores, List removeScores) { + // while one could argue that we may not have to do this fancy Java filter when there are fewer scores than the + // line limit, we would lose the correct order of the scores if we don't + var newDisplayScores = + objective.getScores().values().stream() + .filter(score -> !score.hidden()) + .sorted(SCORE_DISPLAY_ORDER) + .limit(SCORE_DISPLAY_LIMIT) + .map(reference -> { + // pretty much an ArrayList#remove + var iterator = this.displayScores.iterator(); + while (iterator.hasNext()) { + var score = iterator.next(); + if (score.name().equals(reference.name())) { + iterator.remove(); + return score; + } + } + + // new score, so it should be added + return new SidebarDisplayScore(this, objective.getScoreboard().nextId(), reference); + }).collect(Collectors.toList()); + + // in newDisplayScores we removed the items that were already present from displayScores, + // meaning that the items that remain are items that are no longer displayed + for (var score : this.displayScores) { + removeScores.add(score.cachedInfo()); + } + + // preserves the new order + this.displayScores = newDisplayScores; + + // fixes ordering issues with multiple entries with same score + if (!this.displayScores.isEmpty()) { + SidebarDisplayScore lastScore = null; + int count = 0; + for (var score : this.displayScores) { + if (lastScore == null) { + lastScore = score; + continue; + } + + if (score.score() == lastScore.score()) { + // something to keep in mind is that Bedrock doesn't support some legacy color codes and adds some + // codes as well, so if the line limit is every increased keep that in mind + if (count == 0) { + lastScore.order(ChatColor.styleOrder(count++)); + } + score.order(ChatColor.styleOrder(count++)); + } else { + if (count == 0) { + lastScore.order(null); + } + count = 0; + } + lastScore = score; + } + + if (count == 0 && lastScore != null) { + lastScore.order(null); + } + } + + boolean objectiveAdd = updateType == UpdateType.ADD; + boolean objectiveUpdate = updateType == UpdateType.UPDATE; + + for (var score : this.displayScores) { + Team team = score.team(); + boolean add = objectiveAdd || objectiveUpdate; + boolean exists = score.exists(); + + if (team != null) { + // entities are mostly removed from teams without notifying the scores. + if (team.shouldRemove() || !team.hasEntity(score.name())) { + score.team(null); + add = true; + } + } + + if (score.shouldUpdate()) { + score.update(objective); + add = true; + } + + if (add) { + addScores.add(score.cachedInfo()); + } + + // we need this as long as MCPE-143063 hasn't been fixed. + // the checks after 'add' are there to prevent removing scores that + // are going to be removed anyway / don't need to be removed + if (add && exists && !(objectiveUpdate || objectiveAdd) && !score.onlyScoreValueChanged()) { + removeScores.add(score.cachedInfo()); + } + } + + if (objectiveUpdate) { + sendRemoveObjective(); + } + + if (objectiveAdd || objectiveUpdate) { + sendDisplayObjective(); + } + + updateType = UpdateType.NOTHING; + } + + @Override + public void addScore(ScoreReference reference) { + // we handle them a bit different: we sort the scores, and we add them ourselves + } + + @Override + public void playerRegistered(PlayerEntity player) { + + } + + @Override + public void playerRemoved(PlayerEntity player) { + + } + + public void setTeamFor(Team team, Set entities) { + // we only have to worry about scores that are currently displayed, + // because the constructor of the display score fetches the team + for (var score : displayScores) { + if (entities.contains(score.name())) { + score.team(team); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 7c4eb7d2c..7ecba7077 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,9 +25,8 @@ package org.geysermc.geyser.session; -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.auth.exception.request.RequestException; -import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -41,22 +40,68 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import net.kyori.adventure.key.Key; +import net.raphimc.minecraftauth.responsehandler.exception.MinecraftRequestException; +import net.raphimc.minecraftauth.step.java.StepMCProfile; +import net.raphimc.minecraftauth.step.java.StepMCToken; +import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.IntRange; -import org.cloudburstmc.math.vector.*; +import org.cloudburstmc.math.vector.Vector2f; +import org.cloudburstmc.math.vector.Vector2i; +import org.cloudburstmc.math.vector.Vector3d; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.netty.channel.raknet.RakChildChannel; +import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons; import org.cloudburstmc.protocol.bedrock.BedrockServerSession; -import org.cloudburstmc.protocol.bedrock.data.*; +import org.cloudburstmc.protocol.bedrock.data.Ability; +import org.cloudburstmc.protocol.bedrock.data.AbilityLayer; +import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode; +import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel; +import org.cloudburstmc.protocol.bedrock.data.ExperimentData; +import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting; +import org.cloudburstmc.protocol.bedrock.data.GameRuleData; +import org.cloudburstmc.protocol.bedrock.data.GameType; +import org.cloudburstmc.protocol.bedrock.data.LevelEvent; +import org.cloudburstmc.protocol.bedrock.data.PlayerPermission; +import org.cloudburstmc.protocol.bedrock.data.SoundEvent; +import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType; import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData; import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission; import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType; +import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket; +import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket; +import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; +import org.cloudburstmc.protocol.bedrock.packet.ClientboundCloseFormPacket; +import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket; +import org.cloudburstmc.protocol.bedrock.packet.DimensionDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket; +import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket; +import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet; +import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket; +import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket; +import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket; +import org.cloudburstmc.protocol.bedrock.packet.TextPacket; +import org.cloudburstmc.protocol.bedrock.packet.TransferPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAbilitiesPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAdventureSettingsPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateSoftEnumPacket; import org.cloudburstmc.protocol.common.util.OptionalBoolean; import org.geysermc.api.util.BedrockPlatform; import org.geysermc.api.util.InputMode; @@ -88,7 +133,9 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler; +import org.geysermc.geyser.erosion.ErosionCancellationException; import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler; import org.geysermc.geyser.impl.camera.CameraDefinitions; import org.geysermc.geyser.impl.camera.GeyserCameraData; @@ -98,27 +145,49 @@ import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.BlockItem; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; -import org.geysermc.geyser.session.cache.*; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.session.cache.BookEditCache; +import org.geysermc.geyser.session.cache.ChunkCache; +import org.geysermc.geyser.session.cache.EntityCache; +import org.geysermc.geyser.session.cache.EntityEffectCache; +import org.geysermc.geyser.session.cache.FormCache; +import org.geysermc.geyser.session.cache.LodestoneCache; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.session.cache.PreferencesCache; +import org.geysermc.geyser.session.cache.RegistryCache; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.session.cache.StructureBlockCache; +import org.geysermc.geyser.session.cache.TagCache; +import org.geysermc.geyser.session.cache.TeleportCache; +import org.geysermc.geyser.session.cache.WorldBorder; +import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MinecraftAuthLogger; +import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.network.BuiltinFlags; import org.geysermc.mcprotocollib.network.Session; -import org.geysermc.mcprotocollib.network.event.session.*; +import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent; +import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent; +import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent; +import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent; +import org.geysermc.mcprotocollib.network.event.session.SessionAdapter; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.network.tcp.TcpClientSession; import org.geysermc.mcprotocollib.network.tcp.TcpSession; @@ -153,8 +222,18 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -163,6 +242,8 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter public class GeyserSession implements GeyserConnection, GeyserCommandSource { + private static final Gson GSON = new Gson(); + private final GeyserImpl geyser; private final UpstreamSession upstream; private DownstreamSession downstream; @@ -183,7 +264,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @NonNull @Setter - private AbstractGeyserboundPacketHandler erosionHandler; + private volatile AbstractGeyserboundPacketHandler erosionHandler; @Accessors(fluent = true) @Setter @@ -222,7 +303,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private boolean closingInventory; @Setter - private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; + private @NonNull InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; /** * Use {@link #getNextItemNetId()} instead for consistency @@ -307,15 +388,24 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private boolean sprinting; + /** + * The overworld dimension which Bedrock Edition uses. + */ + private BedrockDimension bedrockOverworldDimension = BedrockDimension.OVERWORLD; /** * The dimension of the player. * As all entities are in the same world, this can be safely applied to all other entities. */ - @Setter - private int dimension = DimensionUtils.OVERWORLD; @MonotonicNonNull @Setter private JavaDimension dimensionType = null; + /** + * Which dimension Bedrock understands themselves to be in. + * This should only be set after the ChangeDimensionPacket is sent, or + * right before the StartGamePacket is sent. + */ + @Setter + private BedrockDimension bedrockDimension = this.bedrockOverworldDimension; @Setter private int breakingBlock; @@ -486,16 +576,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private float walkSpeed; /** - * Caches current rain status. + * Caches current rain strength. + * Value between 0 and 1. */ - @Setter - private boolean raining = false; + private float rainStrength = 0.0f; /** - * Caches current thunder status. + * Caches current thunder strength. + * Value between 0 and 1. */ - @Setter - private boolean thunder = false; + private float thunderStrength = 0.0f; /** * Stores a map of all statistics sent from the server. @@ -522,6 +612,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ private ScheduledFuture tickThread = null; + /** + * The number of ticks that have elapsed since the start of this session + */ + private int ticks; + + /** + * The world time in ticks according to the server + *

+ * Note: The TickingStatePacket is currently ignored. + */ + @Setter + private long worldTicks; + /** * Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator */ @@ -612,6 +715,31 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Send all necessary packets to load Bedrock into the server */ public void connect() { + // Note: this.dimensionType may be null here if the player is connecting from online mode + int minY = BedrockDimension.OVERWORLD.minY(); + int maxY = BedrockDimension.OVERWORLD.maxY(); + for (JavaDimension javaDimension : this.registryCache.dimensions().values()) { + if (javaDimension.bedrockId() == BedrockDimension.OVERWORLD_ID) { + minY = Math.min(minY, javaDimension.minY()); + maxY = Math.max(maxY, javaDimension.maxY()); + } + } + minY = Math.max(minY, -512); + maxY = Math.min(maxY, 512); + + if (minY < BedrockDimension.OVERWORLD.minY() || maxY > BedrockDimension.OVERWORLD.maxY()) { + final boolean isInOverworld = this.bedrockDimension == this.bedrockOverworldDimension; + this.bedrockOverworldDimension = new BedrockDimension(minY, maxY - minY, true, BedrockDimension.OVERWORLD_ID); + if (isInOverworld) { + this.bedrockDimension = this.bedrockOverworldDimension; + } + geyser.getLogger().debug("Extending overworld dimension to " + minY + " - " + maxY); + + DimensionDataPacket dimensionDataPacket = new DimensionDataPacket(); + dimensionDataPacket.getDefinitions().add(new DimensionDefinition("minecraft:overworld", maxY, minY, 5 /* Void */)); + upstream.sendPacket(dimensionDataPacket); + } + startGame(); sentSpawnPacket = true; syncEntityProperties(); @@ -689,7 +817,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } } - public void authenticateWithRefreshToken(String refreshToken) { + public void authenticateWithAuthChain(String authChain) { if (loggedIn) { geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); return; @@ -698,24 +826,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { loggingIn = true; CompletableFuture.supplyAsync(() -> { - MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); - service.setRefreshToken(refreshToken); + StepFullJavaSession step = PendingMicrosoftAuthentication.AUTH_FLOW.apply(true, 30); + StepFullJavaSession.FullJavaSession response; try { - service.login(); - } catch (RequestException e) { - geyser.getLogger().error("Error while attempting to use refresh token for " + bedrockUsername() + "!", e); + response = step.refresh(MinecraftAuthLogger.INSTANCE, PendingMicrosoftAuthentication.AUTH_CLIENT, step.fromJson(GSON.fromJson(authChain, JsonObject.class))); + } catch (Exception e) { + geyser.getLogger().error("Error while attempting to use auth chain for " + bedrockUsername() + "!", e); return Boolean.FALSE; } - GameProfile profile = service.getSelectedProfile(); - if (profile == null) { - // Java account is offline - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); - return null; - } + StepMCProfile.MCProfile mcProfile = response.getMcProfile(); + StepMCToken.MCToken mcToken = mcProfile.getMcToken(); - protocol = new MinecraftProtocol(profile, service.getAccessToken()); - geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken()); + protocol = new MinecraftProtocol( + new GameProfile(mcProfile.getId(), mcProfile.getName()), + mcToken.getAccessToken() + ); + geyser.saveAuthChain(bedrockUsername(), GSON.toJson(step.toJson(response))); return Boolean.TRUE; }).whenComplete((successful, ex) -> { if (this.closed) { @@ -760,25 +887,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask( getAuthData().xuid() ); - task.setOnline(true); - task.resetTimer(); - - if (task.getAuthentication().isDone()) { + if (task.getAuthentication() != null && task.getAuthentication().isDone()) { onMicrosoftLoginComplete(task); } else { - task.getCode(offlineAccess).whenComplete((response, ex) -> { - boolean connected = !closed; - if (ex != null) { - if (connected) { - geyser.getLogger().error("Failed to get Microsoft auth code", ex); - disconnect(ex.toString()); - } - task.cleanup(); // error getting auth code -> clean up immediately - } else if (connected) { - LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); - task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task)); + task.resetRunningFlow(); + task.performLoginAttempt(offlineAccess, code -> { + if (!closed) { + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, code); } - }); + }).handle((r, e) -> onMicrosoftLoginComplete(task)); } } @@ -790,36 +907,39 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return false; } task.cleanup(); // player is online -> remove pending authentication immediately - Throwable ex = task.getLoginException(); - if (ex != null) { - geyser.getLogger().error("Failed to log in with Microsoft code!", ex); - disconnect(ex.toString()); - } else { - MsaAuthenticationService service = task.getMsaAuthenticationService(); - GameProfile selectedProfile = service.getSelectedProfile(); - if (selectedProfile == null) { - disconnect(GeyserLocale.getPlayerLocaleString( - "geyser.network.remote.invalid_account", - clientData.getLanguageCode() - )); - } else { - this.protocol = new MinecraftProtocol( - selectedProfile, - service.getAccessToken() - ); - try { - connectDownstream(); - } catch (Throwable t) { - t.printStackTrace(); - return false; - } + return task.getAuthentication().handle((result, ex) -> { + if (ex != null) { + geyser.getLogger().error("Failed to log in with Microsoft code!", ex); + if (ex instanceof CompletionException ce + && ce.getCause() instanceof MinecraftRequestException mre + && mre.getResponse().getStatusCode() == 404) { + // Player is trying to join with a Microsoft account that doesn't have Java Edition purchased + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", locale())); + } else { + disconnect(ex.toString()); + } + return false; + } - // Save our refresh token for later use - geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken()); - return true; - } - } - return false; + StepMCProfile.MCProfile mcProfile = result.session().getMcProfile(); + StepMCToken.MCToken mcToken = mcProfile.getMcToken(); + + this.protocol = new MinecraftProtocol( + new GameProfile(mcProfile.getId(), mcProfile.getName()), + mcToken.getAccessToken() + ); + + try { + connectDownstream(); + } catch (Throwable t) { + t.printStackTrace(); + return false; + } + + // Save our auth chain for later use + geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session()))); + return true; + }).getNow(false); } /** @@ -842,8 +962,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Start ticking tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); - this.protocol.setUseDefaultListeners(false); - TcpSession downstream; if (geyser.getBootstrap().getSocketAddress() != null) { // We're going to connect through the JVM and not through TCP @@ -869,7 +987,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.downstream.getSession().setFlag(MinecraftConstants.FOLLOW_TRANSFERS, false); if (geyser.getConfig().getRemote().isUseProxyProtocol()) { - downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } if (geyser.getConfig().isForwardPlayerPing()) { @@ -879,22 +996,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // We'll handle this since we have the registry data on hand downstream.setFlag(MinecraftConstants.SEND_BLANK_KNOWN_PACKS_RESPONSE, false); - // This isn't a great solution, but... we want to make sure the finish configuration packet cannot be sent - // before the KnownPacks packet. - this.downstream.getSession().addListener(new ClientListener(ProtocolState.LOGIN, loginEvent.transferring()) { - @Override - public void packetReceived(Session session, Packet packet) { - if (protocol.getState() == ProtocolState.CONFIGURATION) { - if (packet instanceof ClientboundFinishConfigurationPacket) { - // Prevent - GeyserSession.this.ensureInEventLoop(() -> GeyserSession.this.sendDownstreamPacket(new ServerboundFinishConfigurationPacket())); - return; - } - } - super.packetReceived(session, packet); - } - }); - downstream.addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { @@ -1102,7 +1203,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (authData != null) { PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid()); if (task != null) { - task.setOnline(false); + task.resetRunningFlow(); } } } @@ -1111,9 +1212,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { tickThread.cancel(false); } - erosionHandler.close(); - + // Mark session as closed before cancelling erosion futures closed = true; + erosionHandler.close(); } /** @@ -1134,6 +1235,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { eventLoop.execute(() -> { try { runnable.run(); + } catch (ErosionCancellationException e) { + geyser.getLogger().debug("Caught ErosionCancellationException"); } catch (Throwable e) { geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e); } @@ -1151,6 +1254,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (!closed) { runnable.run(); } + } catch (ErosionCancellationException e) { + geyser.getLogger().debug("Caught ErosionCancellationException"); } catch (Throwable e) { geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e); } @@ -1196,6 +1301,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { isInWorldBorderWarningArea = false; } + Entity vehicle = playerEntity.getVehicle(); + if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) { + clientVehicle.getVehicleComponent().tickVehicle(); + } for (Tickable entity : entityCache.getTickableEntities()) { entity.tick(); @@ -1231,6 +1340,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } catch (Throwable throwable) { throwable.printStackTrace(); } + + ticks++; + worldTicks++; } public void setAuthenticationData(AuthData authData) { @@ -1401,11 +1513,28 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return false; } + @Override + public UUID playerUuid() { + return javaUuid(); // CommandSource allows nullable + } + + @Override + public GeyserSession connection() { + return this; + } + @Override public String locale() { return clientData.getLanguageCode(); } + @Override + public boolean hasPermission(String permission) { + // for Geyser-Standalone, standalone's permission system will handle it. + // for server platforms, the session will be mapped to a server command sender, and the server's api will be used. + return geyser.commandRegistry().hasPermission(this, permission); + } + /** * Sends a chat message to the Java server. */ @@ -1495,7 +1624,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(-1L); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(chunkCache.getBedrockDimension())); + startGamePacket.setDimensionId(bedrockDimension.bedrockId()); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); @@ -1659,8 +1788,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return; } - if (protocol.getState() != intendedState) { - geyser.getLogger().debug("Tried to send " + packet.getClass().getSimpleName() + " packet while not in " + intendedState.name() + " state"); + if (protocol.getOutboundState() != intendedState) { + geyser.getLogger().debug("Tried to send " + packet.getClass().getSimpleName() + " packet while not in " + intendedState.name() + " outbound state"); return; } @@ -1694,7 +1823,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } private void sendDownstreamPacket0(Packet packet) { - ProtocolState state = protocol.getState(); + ProtocolState state = protocol.getOutboundState(); if (state == ProtocolState.GAME || state == ProtocolState.CONFIGURATION || packet.getClass() == ServerboundCustomQueryAnswerPacket.class) { downstream.sendPacket(packet); } else { @@ -1738,17 +1867,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { upstream.sendPacket(gameRulesChangedPacket); } - /** - * Checks if the given session's player has a permission - * - * @param permission The permission node to check - * @return true if the player has the requested permission, false if not - */ - @Override - public boolean hasPermission(String permission) { - return geyser.getWorldManager().hasPermission(this, permission); - } - private static final Ability[] USED_ABILITIES = Ability.values(); /** @@ -1930,6 +2048,71 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { }; } + /** + * Sends a packet to update rain strength. + * Stops rain if strength is 0. + * + * @param strength value between 0 and 1 + */ + public void updateRain(float strength) { + boolean wasRaining = isRaining(); + this.rainStrength = strength; + + LevelEventPacket rainPacket = new LevelEventPacket(); + rainPacket.setType(isRaining() ? LevelEvent.START_RAINING : LevelEvent.STOP_RAINING); + rainPacket.setData((int) (strength * 65535)); + rainPacket.setPosition(Vector3f.ZERO); + sendUpstreamPacket(rainPacket); + + // Keep thunder in sync with rain when starting/stopping a storm + if ((wasRaining != isRaining()) && isThunder()) { + if (isRaining()) { + LevelEventPacket thunderPacket = new LevelEventPacket(); + thunderPacket.setType(LevelEvent.START_THUNDERSTORM); + thunderPacket.setData((int) (this.thunderStrength * 65535)); + thunderPacket.setPosition(Vector3f.ZERO); + sendUpstreamPacket(thunderPacket); + } else { + LevelEventPacket thunderPacket = new LevelEventPacket(); + thunderPacket.setType(LevelEvent.STOP_THUNDERSTORM); + thunderPacket.setData(0); + thunderPacket.setPosition(Vector3f.ZERO); + sendUpstreamPacket(thunderPacket); + } + } + } + + /** + * Sends a packet to update thunderstorm strength. + * Stops thunderstorm if strength is 0. + * + * @param strength value between 0 and 1 + */ + public void updateThunder(float strength) { + this.thunderStrength = strength; + + // Do not send thunder packet if not raining + // The bedrock client will start raining automatically when updating thunder strength + // https://github.com/GeyserMC/Geyser/issues/3679 + if (!isRaining()) { + return; + } + + LevelEventPacket thunderPacket = new LevelEventPacket(); + thunderPacket.setType(isThunder() ? LevelEvent.START_THUNDERSTORM : LevelEvent.STOP_THUNDERSTORM); + thunderPacket.setData((int) (strength * 65535)); + thunderPacket.setPosition(Vector3f.ZERO); + sendUpstreamPacket(thunderPacket); + } + + public boolean isRaining() { + return this.rainStrength > 0; + } + + public boolean isThunder() { + return this.thunderStrength > 0; + } + @Override public @NonNull String bedrockUsername() { return authData.name(); @@ -2059,6 +2242,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return this.cameraData.fogEffects(); } + @Override + public int ping() { + RakSessionCodec rakSessionCodec = ((RakChildChannel) getUpstream().getSession().getPeer().getChannel()).rakPipeline().get(RakSessionCodec.class); + return (int) Math.floor(rakSessionCodec.getPing()); + } + + @Override + public void closeForm() { + if (!GameProtocol.isPre1_21_2(this)) { + sendUpstreamPacket(new ClientboundCloseFormPacket()); + } + } + public void addCommandEnum(String name, String enums) { softEnumPacket(name, SoftEnumUpdateType.ADD, enums); } diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java index 0651039a0..7d0e2fbf9 100644 --- a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -25,27 +25,44 @@ package org.geysermc.geyser.session; -import com.github.steveice10.mc.auth.exception.request.AuthPendingException; -import com.github.steveice10.mc.auth.exception.request.RequestException; -import com.github.steveice10.mc.auth.service.MsaAuthenticationService; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; +import net.lenni0451.commons.httpclient.HttpClient; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession; +import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode; +import net.raphimc.minecraftauth.util.MicrosoftConstants; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.util.MinecraftAuthLogger; -import java.io.Serial; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Consumer; /** * Pending Microsoft authentication task cache. * It permits user to exit the server while they authorize Geyser to access their Microsoft account. */ public class PendingMicrosoftAuthentication { + public static final HttpClient AUTH_CLIENT = MinecraftAuth.createHttpClient(); + public static final BiFunction AUTH_FLOW = (offlineAccess, timeoutSec) -> MinecraftAuth.builder() + .withClientId(GeyserImpl.OAUTH_CLIENT_ID) + .withScope(offlineAccess ? "XboxLive.signin XboxLive.offline_access" : "XboxLive.signin") + .withTimeout(timeoutSec) + .deviceCode() + .withoutDeviceToken() + .regularAuthentication(MicrosoftConstants.JAVA_XSTS_RELYING_PARTY) + .buildMinecraftJavaProfileStep(false); /** * For GeyserConnect usage. */ @@ -57,8 +74,8 @@ public class PendingMicrosoftAuthentication { .build(new CacheLoader<>() { @Override public AuthenticationTask load(@NonNull String userKey) { - return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L) - : new AuthenticationTask(userKey, timeoutSeconds * 1000L); + return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds) + : new AuthenticationTask(userKey, timeoutSeconds); } }); } @@ -80,37 +97,23 @@ public class PendingMicrosoftAuthentication { public class AuthenticationTask { private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS); - @Getter - private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); private final String userKey; - private final long timeoutMs; - - private long remainingTimeMs; - - @Setter - private boolean online = true; - + private final int timeoutSec; @Getter - private final CompletableFuture authentication; + private CompletableFuture authentication; - @Getter - private volatile Throwable loginException; - - private AuthenticationTask(String userKey, long timeoutMs) { + private AuthenticationTask(String userKey, int timeoutSec) { this.userKey = userKey; - this.timeoutMs = timeoutMs; - this.remainingTimeMs = timeoutMs; - - this.authentication = new CompletableFuture<>(); - this.authentication.whenComplete((r, ex) -> { - this.loginException = ex; - // avoid memory leak, in case player doesn't connect again - CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup); - }); + this.timeoutSec = timeoutSec; } - public void resetTimer() { - this.remainingTimeMs = this.timeoutMs; + public void resetRunningFlow() { + if (authentication == null) { + return; + } + + // Interrupt the current flow + this.authentication.cancel(true); } public void cleanup() { @@ -121,52 +124,18 @@ public class PendingMicrosoftAuthentication { authentications.invalidate(userKey); } - public CompletableFuture getCode(boolean offlineAccess) { - // Request the code - CompletableFuture code = CompletableFuture.supplyAsync( - () -> tryGetCode(offlineAccess)); - // Once the code is received, continuously try to request the access token, profile, etc - code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); - return code; - } - - /** - * @param offlineAccess whether we want a refresh token for later use. - */ - private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException { - try { - return msaAuthenticationService.getAuthCode(offlineAccess); - } catch (RequestException e) { - throw new CompletionException(e); - } - } - - private void performLoginAttempt(long lastAttempt) { - CompletableFuture.runAsync(() -> { + public CompletableFuture performLoginAttempt(boolean offlineAccess, Consumer deviceCodeConsumer) { + return authentication = CompletableFuture.supplyAsync(() -> { try { - msaAuthenticationService.login(); - } catch (AuthPendingException e) { - long currentAttempt = System.currentTimeMillis(); - if (!online) { - // decrement timer only when player's offline - remainingTimeMs -= currentAttempt - lastAttempt; - if (remainingTimeMs <= 0L) { - // time's up - authentication.completeExceptionally(new TaskTimeoutException()); - cleanup(); - return; - } - } - // try again in 1 second - performLoginAttempt(currentAttempt); - return; + StepFullJavaSession step = AUTH_FLOW.apply(offlineAccess, timeoutSec); + return new StepChainResult(step, step.getFromInput(MinecraftAuthLogger.INSTANCE, AUTH_CLIENT, new StepMsaDeviceCode.MsaDeviceCodeCallback(deviceCodeConsumer))); } catch (Exception e) { - authentication.completeExceptionally(e); - return; + throw new CompletionException(e); } - // login successful - authentication.complete(msaAuthenticationService); - }, DELAYED_BY_ONE_SECOND); + }, DELAYED_BY_ONE_SECOND).whenComplete((r, ex) -> { + // avoid memory leak, in case player doesn't connect again + CompletableFuture.delayedExecutor(timeoutSec, TimeUnit.SECONDS).execute(this::cleanup); + }); } @Override @@ -181,22 +150,11 @@ public class PendingMicrosoftAuthentication { private String server; private int port; - private ProxyAuthenticationTask(String userKey, long timeoutMs) { - super(userKey, timeoutMs); + private ProxyAuthenticationTask(String userKey, int timeoutSec) { + super(userKey, timeoutSec); } } - /** - * @see PendingMicrosoftAuthentication - */ - public static class TaskTimeoutException extends Exception { - - @Serial - private static final long serialVersionUID = 1L; - - TaskTimeoutException() { - super("It took too long to authorize Geyser to access your Microsoft account. " + - "Please request new code and try again."); - } + public record StepChainResult(StepFullJavaSession step, StepFullJavaSession.FullJavaSession session) { } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java index be1eb3a5b..ac04bdf04 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java @@ -158,7 +158,15 @@ public class AdvancementsCache { // Cache language for easier access String language = session.locale(); - String earned = isEarned(advancement) ? "yes" : "no"; + boolean advancementHasProgress = advancement.getRequirements().size() > 1; + + int advancementProgress = getProgress(advancement); + int advancementRequirements = advancement.getRequirements().size(); + + boolean advancementEarned = advancementRequirements > 0 + && advancementProgress >= advancementRequirements; + + String earned = advancementEarned ? "yes" : "no"; String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language); String earnedString = GeyserLocale.getPlayerLocaleString("geyser.advancements.earned", language, MinecraftLocale.getLocaleString("gui." + earned, language)); @@ -171,10 +179,20 @@ public class AdvancementsCache { (Description) Mine stone with your new pickaxe Earned: Yes + Progress: 1/4 // When advancement has multiple requirements Parent Advancement: Minecraft // If relevant */ String content = description + "\n\n§f" + earnedString + "\n"; + + if (advancementHasProgress) { + // Only display progress with multiple requirements + String progress = MinecraftLocale.getLocaleString("advancements.progress", language) + .replaceFirst("%s", String.valueOf(advancementProgress)) + .replaceFirst("%s", String.valueOf(advancementRequirements)); + content += GeyserLocale.getPlayerLocaleString("geyser.advancements.progress", language, progress) + "\n"; + } + if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { // Only display the parent if it is not the category content += GeyserLocale.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); @@ -200,34 +218,44 @@ public class AdvancementsCache { * @return true if the advancement has been earned. */ public boolean isEarned(GeyserAdvancement advancement) { - boolean earned = false; - if (advancement.getRequirements().size() == 0) { + if (advancement.getRequirements().isEmpty()) { // Minecraft handles this case, so we better as well return false; } - Map progress = storedAdvancementProgress.get(advancement.getId()); - if (progress != null) { + // Progress should never be above requirements count, but you never know + return getProgress(advancement) >= advancement.getRequirements().size(); + } + + /** + * Determine the progress on an advancement. + * + * @param advancement the advancement to determine + * @return the progress on the advancement. + */ + public int getProgress(GeyserAdvancement advancement) { + if (advancement.getRequirements().isEmpty()) { + // Minecraft handles this case + return 0; + } + int progress = 0; + Map progressMap = storedAdvancementProgress.get(advancement.getId()); + if (progressMap != null) { // Each advancement's requirement must be fulfilled // For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved // But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed for (List requirements : advancement.getRequirements()) { - boolean requirementsDone = false; for (String requirement : requirements) { - Long obtained = progress.get(requirement); + Long obtained = progressMap.get(requirement); // -1 means that this particular component required for completing the advancement // has yet to be fulfilled if (obtained != null && !obtained.equals(-1L)) { - requirementsDone = true; - break; + progress++; } } - if (!requirementsDone) { - return false; - } } - earned = true; } - return earned; + + return progress; } public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index 7b279857a..ad5237c23 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -25,17 +25,14 @@ package org.geysermc.geyser.session.cache; -import org.geysermc.geyser.level.block.type.Block; -import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.level.BedrockDimension; -import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.chunk.GeyserChunk; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.MathUtils; +import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; public class ChunkCache { private final boolean cache; @@ -46,13 +43,6 @@ public class ChunkCache { @Setter private int heightY; - /** - * Which dimension Bedrock understands themselves to be in. - */ - @Getter - @Setter - private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD; - public ChunkCache(GeyserSession session) { this.cache = !session.getGeyser().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing chunks = cache ? new Long2ObjectOpenHashMap<>() : null; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 6524e1ddc..a80ed3e3a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -31,15 +31,18 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; import lombok.Getter; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; -import java.util.*; -import java.util.concurrent.atomic.AtomicLong; - /** * Each session has its own EntityCache in the occasion that an entity packet is sent specifically * for that player (e.g. seeing vanished players from /vanish) @@ -68,6 +71,10 @@ public class EntityCache { if (cacheEntity(entity)) { entity.spawnEntity(); + // start tracking newly spawned entities. + // This is however not called for players, that's done in addPlayerEntity + session.getWorldCache().getScoreboard().entityRegistered(entity); + if (entity instanceof Tickable) { // Start ticking it tickableEntities.add((Tickable) entity); @@ -86,21 +93,24 @@ public class EntityCache { } public void removeEntity(Entity entity) { + if (entity == null) { + return; + } + if (entity instanceof PlayerEntity player) { session.getPlayerWithCustomHeads().remove(player.getUuid()); } - if (entity != null) { - if (entity.isValid()) { - entity.despawnEntity(); - } + if (entity.isValid()) { + entity.despawnEntity(); + } + entities.remove(entityIdTranslations.remove(entity.getEntityId())); - long geyserId = entityIdTranslations.remove(entity.getEntityId()); - entities.remove(geyserId); + // don't track the entity anymore, now that it's removed + session.getWorldCache().getScoreboard().entityRemoved(entity); - if (entity instanceof Tickable) { - tickableEntities.remove(entity); - } + if (entity instanceof Tickable) { + tickableEntities.remove(entity); } } @@ -126,21 +136,49 @@ public class EntityCache { public void addPlayerEntity(PlayerEntity entity) { // putIfAbsent matches the behavior of playerInfoMap in Java as of 1.19.3 - playerEntities.putIfAbsent(entity.getUuid(), entity); + boolean exists = playerEntities.putIfAbsent(entity.getUuid(), entity) != null; + if (exists) { + return; + } + + // notify scoreboard for new entity + var scoreboard = session.getWorldCache().getScoreboard(); + scoreboard.playerRegistered(entity); + // spawnPlayer's entityRegistered is not called for players + scoreboard.entityRegistered(entity); } public PlayerEntity getPlayerEntity(UUID uuid) { return playerEntities.get(uuid); } + public List getPlayersByName(String name) { + var list = new ArrayList(); + for (PlayerEntity player : playerEntities.values()) { + if (name.equals(player.getUsername())) { + list.add(player); + } + } + return list; + } + public PlayerEntity removePlayerEntity(UUID uuid) { - return playerEntities.remove(uuid); + var player = playerEntities.remove(uuid); + if (player != null) { + // notify scoreboard + session.getWorldCache().getScoreboard().playerRemoved(player); + } + return player; } public Collection getAllPlayerEntities() { return playerEntities.values(); } + public void removeAllPlayerEntities() { + playerEntities.clear(); + } + public void addBossBar(UUID uuid, BossBar bossBar) { bossBars.put(uuid, bossBar); bossBar.addBossBar(); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java index d0a5bc094..dee4aa7cf 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java @@ -33,7 +33,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.session.GeyserSession; @@ -119,6 +121,12 @@ public class PistonCache { private void sendPlayerMovement() { if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) { SessionPlayerEntity playerEntity = session.getPlayerEntity(); + + Entity vehicle = playerEntity.getVehicle(); + if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + return; + } + boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround(); Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter(); playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true); @@ -128,6 +136,13 @@ public class PistonCache { private void sendPlayerMotion() { if (!playerMotion.equals(Vector3f.ZERO)) { SessionPlayerEntity playerEntity = session.getPlayerEntity(); + + Entity vehicle = playerEntity.getVehicle(); + if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + vehicle.setMotion(playerMotion); + return; + } + playerEntity.setMotion(playerMotion); SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket(); @@ -149,10 +164,15 @@ public class PistonCache { totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d); Vector3d delta = totalDisplacement.sub(playerDisplacement); - // Check if the piston is pushing a player into collision - delta = session.getCollisionManager().correctPlayerMovement(delta, true, false); - session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ()); + // Check if the piston is pushing a player into collision + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + delta = clientVehicle.getVehicleComponent().correctMovement(delta); + clientVehicle.getVehicleComponent().moveRelative(delta); + } else { + delta = session.getCollisionManager().correctPlayerMovement(delta, true, false); + session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ()); + } playerDisplacement = totalDisplacement; } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java index a393d461d..4a4167f15 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java @@ -49,7 +49,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.JavaRegistry; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; -import org.geysermc.geyser.text.TextDecoration; +import org.geysermc.geyser.text.ChatDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; @@ -78,7 +78,7 @@ public final class RegistryCache { private static final Map>> REGISTRIES = new HashMap<>(); static { - register("chat_type", cache -> cache.chatTypes, TextDecoration::readChatType); + register("chat_type", cache -> cache.chatTypes, ChatDecoration::readChatType); register("dimension_type", cache -> cache.dimensions, JavaDimension::read); register("enchantment", cache -> cache.enchantments, Enchantment::read); register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java index a40a1156d..0eec39b0b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -243,8 +243,16 @@ public class SkullCache { } public void clear() { + for (Skull skull : skulls.values()) { + if (skull.entity != null) { + skull.entity.despawnEntity(); + } + } skulls.clear(); inRangeSkulls.clear(); + for (SkullPlayerEntity skull : unusedSkullEntities) { + skull.despawnEntity(); + } unusedSkullEntities.clear(); totalSkullEntities = 0; lastPlayerPosition = null; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java index 8cb590f57..35579db13 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.session.cache; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.math.GenericMath; import org.cloudburstmc.math.vector.Vector2d; +import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.LevelEventType; @@ -36,8 +37,12 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.level.physics.Axis; +import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.session.GeyserSession; +import static org.geysermc.geyser.level.physics.CollisionManager.COLLISION_TOLERANCE; + public class WorldBorder { private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D; @@ -190,6 +195,53 @@ public class WorldBorder { return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ; } + /** + * Adjusts the movement of an entity so that it does not cross the world border. + * + * @param boundingBox bounding box of the entity + * @param movement movement of the entity + * @return the corrected movement + */ + public Vector3d correctMovement(BoundingBox boundingBox, Vector3d movement) { + double correctedX; + if (movement.getX() < 0) { + correctedX = -limitMovement(-movement.getX(), boundingBox.getMin(Axis.X) - GenericMath.floor(minX)); + } else { + correctedX = limitMovement(movement.getX(), GenericMath.ceil(maxX) - boundingBox.getMax(Axis.X)); + } + + // Outside of border, don't adjust movement + if (Double.isNaN(correctedX)) { + return movement; + } + + double correctedZ; + if (movement.getZ() < 0) { + correctedZ = -limitMovement(-movement.getZ(), boundingBox.getMin(Axis.Z) - GenericMath.floor(minZ)); + } else { + correctedZ = limitMovement(movement.getZ(), GenericMath.ceil(maxZ) - boundingBox.getMax(Axis.Z)); + } + + if (Double.isNaN(correctedZ)) { + return movement; + } + + return Vector3d.from(correctedX, movement.getY(), correctedZ); + } + + private double limitMovement(double movement, double limit) { + if (limit < 0) { + // Return NaN to indicate outside of border + return Double.NaN; + } + + if (limit < COLLISION_TOLERANCE) { + return 0; + } + + return Math.min(movement, limit); + } + /** * Updates the world border's minimum and maximum properties */ diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 8eb715560..5927963c0 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -31,9 +31,11 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; @@ -48,7 +50,7 @@ public final class WorldCache { @Getter private final ScoreboardSession scoreboardSession; @Getter - private Scoreboard scoreboard; + private @NonNull Scoreboard scoreboard; @Getter @Setter private Difficulty difficulty = Difficulty.EASY; @@ -70,6 +72,8 @@ public final class WorldCache { @Setter private boolean editingSignOnFront; + private final Object2IntMap activeCooldowns = new Object2IntOpenHashMap<>(2); + public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); @@ -78,10 +82,8 @@ public final class WorldCache { } public void removeScoreboard() { - if (scoreboard != null) { - scoreboard.removeScoreboard(); - scoreboard = new Scoreboard(session); - } + scoreboard.removeScoreboard(); + scoreboard = new Scoreboard(session); } public int increaseAndGetScoreboardPacketsPerSecond() { @@ -201,4 +203,32 @@ public final class WorldCache { public String removeActiveRecord(Vector3i pos) { return this.activeRecords.remove(pos); } -} \ No newline at end of file + + public void setCooldown(Item item, int ticks) { + if (ticks == 0) { + // As of Java 1.21 + this.activeCooldowns.removeInt(item); + return; + } + this.activeCooldowns.put(item, session.getTicks() + ticks); + } + + public boolean hasCooldown(Item item) { + return this.activeCooldowns.containsKey(item); + } + + public void tick() { + // Implementation note: technically we could empty the field during hasCooldown checks, + // but we don't want the cooldown field to balloon in size from overuse. + if (!this.activeCooldowns.isEmpty()) { + int ticks = session.getTicks(); + Iterator> it = Object2IntMaps.fastIterator(this.activeCooldowns); + while (it.hasNext()) { + Object2IntMap.Entry entry = it.next(); + if (entry.getIntValue() <= ticks) { + it.remove(); + } + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index ef3ff3293..22786a4ee 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -25,11 +25,10 @@ package org.geysermc.geyser.skin; -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.auth.data.GameProfile.Texture; -import com.github.steveice10.mc.auth.data.GameProfile.TextureModel; -import com.github.steveice10.mc.auth.data.GameProfile.TextureType; -import com.github.steveice10.mc.auth.exception.property.PropertyException; +import org.geysermc.mcprotocollib.auth.GameProfile; +import org.geysermc.mcprotocollib.auth.GameProfile.Texture; +import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel; +import org.geysermc.mcprotocollib.auth.GameProfile.TextureType; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -113,11 +112,12 @@ public class FakeHeadProvider { return; } - Map textures = null; + Map textures; try { textures = profile.getTextures(false); - } catch (PropertyException e) { - session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e); + } catch (IllegalStateException e) { + GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage())); + textures = null; } if (textures == null || textures.isEmpty()) { @@ -220,4 +220,4 @@ public class FakeHeadProvider { } } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/text/ChatColor.java b/core/src/main/java/org/geysermc/geyser/text/ChatColor.java index 49178f033..22e553678 100644 --- a/core/src/main/java/org/geysermc/geyser/text/ChatColor.java +++ b/core/src/main/java/org/geysermc/geyser/text/ChatColor.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.text; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; + public class ChatColor { public static final String ANSI_RESET = (char) 0x1b + "[0m"; @@ -84,4 +86,58 @@ public class ChatColor { string = string.replace(WHITE, (char) 0x1b + "[37;1m"); return string; } -} \ No newline at end of file + + public static String styleOrder(int index) { + // https://bugs.mojang.com/browse/MCPE-41729 + // strikethrough and underlined do not exist on Bedrock + return switch (index) { + case 0 -> BLACK; + case 1 -> DARK_BLUE; + case 2 -> DARK_GREEN; + case 3 -> DARK_AQUA; + case 4 -> DARK_RED; + case 5 -> DARK_PURPLE; + case 6 -> GOLD; + case 7 -> GRAY; + case 8 -> DARK_GRAY; + case 9 -> BLUE; + case 10 -> GREEN; + case 11 -> AQUA; + case 12 -> RED; + case 13 -> LIGHT_PURPLE; + case 14 -> YELLOW; + case 15 -> WHITE; + case 16 -> OBFUSCATED; + case 17 -> BOLD; + default -> ITALIC; + }; + } + + public static String chatColorFor(TeamColor teamColor) { + // https://bugs.mojang.com/browse/MCPE-41729 + // strikethrough and underlined do not exist on Bedrock + return switch (teamColor) { + case BLACK -> BLACK; + case DARK_BLUE -> DARK_BLUE; + case DARK_GREEN -> DARK_GREEN; + case DARK_AQUA -> DARK_AQUA; + case DARK_RED -> DARK_RED; + case DARK_PURPLE -> DARK_PURPLE; + case GOLD -> GOLD; + case GRAY -> GRAY; + case DARK_GRAY -> DARK_GRAY; + case BLUE -> BLUE; + case GREEN -> GREEN; + case AQUA -> AQUA; + case RED -> RED; + case LIGHT_PURPLE -> LIGHT_PURPLE; + case YELLOW -> YELLOW; + case WHITE -> WHITE; + case OBFUSCATED -> OBFUSCATED; + case BOLD -> BOLD; + case STRIKETHROUGH, UNDERLINED -> ""; + case ITALIC -> ITALIC; + default -> RESET; + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java b/core/src/main/java/org/geysermc/geyser/text/ChatDecoration.java similarity index 74% rename from core/src/main/java/org/geysermc/geyser/text/TextDecoration.java rename to core/src/main/java/org/geysermc/geyser/text/ChatDecoration.java index 94aec22ef..fc7597cfd 100644 --- a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java +++ b/core/src/main/java/org/geysermc/geyser/text/ChatDecoration.java @@ -25,17 +25,19 @@ package org.geysermc.geyser.text; -import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; -public record TextDecoration(String translationKey, List parameters, Style deserializedStyle) implements ChatTypeDecoration { +public record ChatDecoration(String translationKey, List parameters, Style deserializedStyle) implements ChatTypeDecoration { @Override public NbtMap style() { @@ -53,38 +55,22 @@ public record TextDecoration(String translationKey, List parameters, String translationKey = chat.getString("translation_key"); NbtMap styleTag = chat.getCompound("style"); - Style style = deserializeStyle(styleTag); + Style style = MessageTranslator.getStyleFromNbtMap(styleTag); List parameters = new ArrayList<>(); List parametersNbt = chat.getList("parameters", NbtType.STRING); for (String parameter : parametersNbt) { parameters.add(ChatTypeDecoration.Parameter.valueOf(parameter.toUpperCase(Locale.ROOT))); } - return new ChatType(new TextDecoration(translationKey, parameters, style), null); + return new ChatType(new ChatDecoration(translationKey, parameters, style), null); } return new ChatType(null, null); } public static Style getStyle(ChatTypeDecoration decoration) { - if (decoration instanceof TextDecoration textDecoration) { - return textDecoration.deserializedStyle(); + if (decoration instanceof ChatDecoration chatDecoration) { + return chatDecoration.deserializedStyle(); } - return deserializeStyle(decoration.style()); - } - - private static Style deserializeStyle(NbtMap styleTag) { - Style.Builder builder = Style.style(); - if (!styleTag.isEmpty()) { - String color = styleTag.getString("color", null); - if (color != null) { - builder.color(NamedTextColor.NAMES.value(color)); - } - //TODO implement the rest - boolean italic = styleTag.getBoolean("italic"); - if (italic) { - builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); - } - } - return builder.build(); + return MessageTranslator.getStyleFromNbtMap(decoration.style()); } } diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java index 28fd6f9a4..b8867c356 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -150,7 +150,7 @@ public class GeyserLocale { } else { if (!validLocalLanguage) { // Don't warn on missing locales if a local file has been found - bootstrap.getGeyserLogger().warning("Missing locale: " + locale); + bootstrap.getGeyserLogger().debug("Missing locale: " + locale); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java index 2481028a4..bfe3f4417 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java @@ -166,4 +166,22 @@ public class BlockCollision { } return offset; } + + /** + * Checks if this block collision is below the given bounding box. + * + * @param blockY the y position of the block in the world + * @param boundingBox the bounding box to compare + * @return true if this block collision is below the bounding box + */ + public boolean isBelow(int blockY, BoundingBox boundingBox) { + double minY = boundingBox.getMiddleY() - boundingBox.getSizeY() / 2; + for (BoundingBox b : boundingBoxes) { + double offset = blockY + b.getMiddleY() + b.getSizeY() / 2 - minY; + if (offset > CollisionManager.COLLISION_TOLERANCE) { + return false; + } + } + return true; + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 4c426b410..3338d2e52 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.*; @@ -200,6 +201,9 @@ public abstract class InventoryTranslator { public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { ClickPlan plan = new ClickPlan(session, this, inventory); IntSet affectedSlots = new IntOpenHashSet(); + int pendingOutput = 0; + int savedTempSlot = -1; + for (ItemStackRequestAction action : request.getActions()) { GeyserItemStack cursor = session.getPlayerInventory().getCursor(); switch (action.getType()) { @@ -240,6 +244,65 @@ public abstract class InventoryTranslator { return rejectRequest(request, false); } + // Handle partial transfer of output slot + if (pendingOutput == 0 && !isSourceCursor && getSlotType(sourceSlot) == SlotType.OUTPUT + && transferAction.getCount() < plan.getItem(sourceSlot).getAmount()) { + // Cursor as dest should always be full transfer. + if (isDestCursor) { + return rejectRequest(request); + } + + if (!plan.getCursor().isEmpty()) { + savedTempSlot = findTempSlot(plan, plan.getCursor(), true); + if (savedTempSlot == -1) { + return rejectRequest(request); + } + plan.add(Click.LEFT, savedTempSlot); + } + + // Pickup entire stack from output + pendingOutput = plan.getItem(sourceSlot).getAmount(); + plan.add(Click.LEFT, sourceSlot); + } + + // Continue transferring items from output that is currently stored in the cursor + if (pendingOutput > 0) { + if (isSourceCursor || getSlotType(sourceSlot) != SlotType.OUTPUT + || transferAction.getCount() > pendingOutput + || destSlot == savedTempSlot + || isDestCursor) { + return rejectRequest(request); + } + + // Make sure item can be placed here + GeyserItemStack destItem = plan.getItem(destSlot); + if (!destItem.isEmpty() && !InventoryUtils.canStack(destItem, plan.getCursor())) { + return rejectRequest(request); + } + + // TODO: Optimize using max stack size + if (pendingOutput == transferAction.getCount()) { + plan.add(Click.LEFT, destSlot); + } else { + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); + } + } + + pendingOutput -= transferAction.getCount(); + if (pendingOutput != plan.getCursor().getAmount()) { + return rejectRequest(request); + } + + if (pendingOutput == 0 && savedTempSlot != -1) { + plan.add(Click.LEFT, savedTempSlot); + savedTempSlot = -1; + } + + // Skip to next action + continue; + } + if (isSourceCursor && isDestCursor) { //??? return rejectRequest(request); } else if (isSourceCursor) { //releasing cursor @@ -270,7 +333,7 @@ public abstract class InventoryTranslator { return rejectRequest(request); } if (transferAction.getCount() != sourceAmount) { - int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot); + int tempSlot = findTempSlot(plan, cursor, false, sourceSlot); if (tempSlot == -1) { return rejectRequest(request); } @@ -291,7 +354,7 @@ public abstract class InventoryTranslator { } else { //transfer from one slot to another int tempSlot = -1; if (!plan.getCursor().isEmpty()) { - tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot); + tempSlot = findTempSlot(plan, cursor, getSlotType(sourceSlot) != SlotType.NORMAL, sourceSlot, destSlot); if (tempSlot == -1) { return rejectRequest(request); } @@ -439,6 +502,11 @@ public abstract class InventoryTranslator { return rejectRequest(request); } } + + if (pendingOutput != 0) { + return rejectRequest(request); + } + plan.execute(false); affectedSlots.addAll(plan.getAffectedSlots()); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); @@ -535,7 +603,7 @@ public abstract class InventoryTranslator { } } else { GeyserItemStack cursor = session.getPlayerInventory().getCursor(); - int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot); + int tempSlot = findTempSlot(plan, cursor, true, sourceSlot, destSlot); if (tempSlot == -1) { return rejectRequest(request); } @@ -698,7 +766,7 @@ public abstract class InventoryTranslator { int javaSlot = bedrockSlotToJava(transferAction.getDestination()); if (isCursor(transferAction.getDestination())) { //TODO if (timesCrafted > 1) { - tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true); + tempSlot = findTempSlot(plan, GeyserItemStack.from(output), true); if (tempSlot == -1) { return rejectRequest(request); } @@ -835,49 +903,68 @@ public abstract class InventoryTranslator { } /** - * Try to find a slot that can temporarily store the given item. + * Try to find a slot that is preferably empty, or does not stack with a given item. * Only looks in the main inventory and hotbar (excluding offhand). - * Only slots that are empty or contain a different type of item are valid. + *

+ * Slots are searched in the reverse order that the bedrock client uses for quick moving. * - * @return java id for the temporary slot, or -1 if no viable slot was found + * @param plan used to check the simulated inventory + * @param item the item to temporarily store + * @param emptyOnly if only empty slots should be considered + * @param slotBlacklist list of slots to exclude; the items contained in these slots will also be checked for stacking + * @return the temp slot, or -1 if no suitable slot was found */ - //TODO: compatibility for simulated inventory (ClickPlan) - private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) { - int offset = inventory.getJavaId() == 0 ? 1 : 0; //offhand is not a viable temp slot - HashSet itemBlacklist = new HashSet<>(slotBlacklist.length + 1); - itemBlacklist.add(item); + private static int findTempSlot(ClickPlan plan, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) { + IntSortedSet potentialSlots = new IntLinkedOpenHashSet(PLAYER_INVENTORY_SIZE); + int hotbarOffset = plan.getInventory().getOffsetForHotbar(0); - IntSet potentialSlots = new IntOpenHashSet(36); - for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { + // Add main inventory slots in reverse + for (int i = hotbarOffset - 1; i >= hotbarOffset - 27; i--) { potentialSlots.add(i); } + + // Add hotbar slots in reverse + for (int i = hotbarOffset + 8; i >= hotbarOffset; i--) { + potentialSlots.add(i); + } + for (int i : slotBlacklist) { potentialSlots.remove(i); - GeyserItemStack blacklistedItem = inventory.getItem(i); - if (!blacklistedItem.isEmpty()) { - itemBlacklist.add(blacklistedItem); + } + + // Prefer empty slots + IntIterator it = potentialSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); + if (plan.isEmpty(slot)) { + return slot; } } - for (int i : potentialSlots) { - GeyserItemStack testItem = inventory.getItem(i); - if ((emptyOnly && !testItem.isEmpty())) { + if (emptyOnly) { + return -1; + } + + // No empty slots. Look for a slot that does not stack + it = potentialSlots.iterator(); + + outer: + while (it.hasNext()) { + int slot = it.nextInt(); + if (plan.canStack(slot, item)) { continue; } - boolean viable = true; - for (GeyserItemStack blacklistedItem : itemBlacklist) { - if (InventoryUtils.canStack(testItem, blacklistedItem)) { - viable = false; - break; + for (int blacklistedSlot : slotBlacklist) { + GeyserItemStack blacklistedItem = plan.getItem(blacklistedSlot); + if (plan.canStack(slot, blacklistedItem)) { + continue outer; } } - if (!viable) { - continue; - } - return i; + + return slot; } - //could not find a viable temp slot + return -1; } @@ -894,11 +981,11 @@ public abstract class InventoryTranslator { List containerEntries = new ArrayList<>(); for (Map.Entry> entry : containerMap.entrySet()) { - containerEntries.add(new ItemStackResponseContainer(entry.getKey(), entry.getValue())); + containerEntries.add(new ItemStackResponseContainer(entry.getKey(), entry.getValue(), new FullContainerName(entry.getKey(), null))); } ItemStackResponseSlot cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor()); - containerEntries.add(new ItemStackResponseContainer(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry))); + containerEntries.add(new ItemStackResponseContainer(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry), new FullContainerName(ContainerSlotType.CURSOR, null))); return containerEntries; } @@ -952,4 +1039,4 @@ public abstract class InventoryTranslator { TRANSFER, DONE } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/OldSmithingTableTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/OldSmithingTableTranslator.java index 21fe9ca21..685d51fc0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/OldSmithingTableTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/OldSmithingTableTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.inventory; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData; @@ -139,6 +140,7 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(53); slotPacket.setItem(UPGRADE_TEMPLATE.apply(session.getUpstream().getProtocolVersion())); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java index bc6ff2adf..a276e4750 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData; @@ -83,6 +84,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { contents[i - 36] = inventory.getItem(i).getItemData(session); } inventoryContentPacket.setContents(Arrays.asList(contents)); + inventoryContentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(inventoryContentPacket); // Armor @@ -99,12 +101,14 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } } armorContentPacket.setContents(Arrays.asList(contents)); + armorContentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(armorContentPacket); // Offhand InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); offhandPacket.setContents(Collections.singletonList(inventory.getItem(45).getItemData(session))); + offhandPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(offhandPacket); } @@ -123,9 +127,10 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (session.getGameMode() == GameMode.CREATIVE) { slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion())); } else { - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack())); + slotPacket.setItem(inventory.getItem(i).getItemData(session)); } + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } } @@ -162,11 +167,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator { slotPacket.setSlot(slot + 27); } slotPacket.setItem(bedrockItem); + slotPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(slotPacket); } else if (slot == 45) { InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); offhandPacket.setContents(Collections.singletonList(bedrockItem)); + offhandPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(offhandPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java index f1a5723c8..ba3b7285e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.inventory.horse; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData; import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket; @@ -94,6 +95,7 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); contentPacket.setContents(Arrays.asList(bedrockItems)); + contentPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(contentPacket); ItemData[] horseItems = new ItemData[chestSize + 1]; @@ -107,6 +109,7 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven InventoryContentPacket horseContentsPacket = new InventoryContentPacket(); horseContentsPacket.setContainerId(inventory.getBedrockId()); horseContentsPacket.setContents(Arrays.asList(horseItems)); + horseContentsPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(horseContentsPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 6a781dcb8..163eef20b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -25,10 +25,6 @@ package org.geysermc.geyser.translator.item; -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.auth.data.GameProfile.Texture; -import com.github.steveice10.mc.auth.data.GameProfile.TextureType; -import com.github.steveice10.mc.auth.exception.property.PropertyException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.checkerframework.checker.nullness.qual.NonNull; @@ -44,6 +40,7 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.Rarity; +import org.geysermc.geyser.item.type.BedrockRequiresTagItem; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; @@ -55,13 +52,24 @@ import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.mcprotocollib.auth.GameProfile; +import org.geysermc.mcprotocollib.auth.GameProfile.Texture; +import org.geysermc.mcprotocollib.auth.GameProfile.TextureType; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.ModifierOperation; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.*; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate; +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.HolderSet; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public final class ItemTranslator { @@ -148,6 +156,12 @@ public final class ItemTranslator { if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true; } + // Fixes fireworks crafting recipe: they always contain a tag + // TODO remove once all items have their default components + if (javaItem instanceof BedrockRequiresTagItem requiresTagItem) { + requiresTagItem.addRequiredNbt(session, components, nbtBuilder); + } + Rarity rarity = javaItem.rarity(); boolean enchantmentGlint = javaItem.glint(); if (components != null) { @@ -480,11 +494,12 @@ public final class ItemTranslator { GameProfile profile = components.get(DataComponentType.PROFILE); if (profile != null) { - Map textures = null; + Map textures; try { textures = profile.getTextures(false); - } catch (PropertyException e) { - GeyserImpl.getInstance().getLogger().debug("Failed to get textures from GameProfile: " + e); + } catch (IllegalStateException e) { + GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage())); + return null; } if (textures == null || textures.isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java index fb71a84cc..703c0954c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java @@ -42,10 +42,9 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { List items = javaNbt.getList("Items", NbtType.COMPOUND); if (items != null) { - int i = 1; for (NbtMap itemTag : items) { - bedrockNbt.put("Item" + i, getItem(session, itemTag)); - i++; + int slot = itemTag.getByte("Slot") + 1; + bedrockNbt.put("Item" + slot, getItem(session, itemTag)); } } } @@ -55,8 +54,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { if (mapping == null) { mapping = ItemMapping.AIR; } - NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getByte("Count"), mapping.getBedrockData()); - tagBuilder.put("tag", NbtMap.builder().build()); // I don't think this is necessary... - Camo, 1.20.5/1.20.80 + NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getInt("count"), mapping.getBedrockData()); return tagBuilder.build(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java index 350ce8c3e..b668a88cf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java @@ -37,7 +37,7 @@ import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.property.Properties; @@ -230,8 +230,8 @@ public class PistonBlockEntity { BlockState state = session.getGeyser().getWorldManager().blockAt(session, blockInFront); if (state.is(Blocks.PISTON_HEAD)) { ChunkUtils.updateBlock(session, Block.JAVA_AIR_ID, blockInFront); - } else if ((session.getGeyser().getPlatformType() == PlatformType.SPIGOT || session.getErosionHandler().isActive()) && state.is(Blocks.AIR)) { - // Spigot removes the piston head from the cache, but we need to send the block update ourselves + } else if ((session.getGeyser().getWorldManager().hasOwnChunkCache() || session.getErosionHandler().isActive()) && state.is(Blocks.AIR)) { + // The platform removes the piston head from the cache, but we need to send the block update ourselves ChunkUtils.updateBlock(session, Block.JAVA_AIR_ID, blockInFront); } } @@ -348,18 +348,31 @@ public class PistonBlockEntity { blockMovement = 1f - lastProgress; } - BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox(); + boolean onGround; + BoundingBox playerBoundingBox; + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + onGround = session.getPlayerEntity().getVehicle().isOnGround(); + playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox(); + } else { + onGround = session.getPlayerEntity().isOnGround(); + playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox(); + } + // Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2); - playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() - shrink.getX()); - playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() - shrink.getY()); - playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ()); + double sizeX = playerBoundingBox.getSizeX(); + double sizeY = playerBoundingBox.getSizeY(); + double sizeZ = playerBoundingBox.getSizeZ(); + + playerBoundingBox.setSizeX(sizeX - shrink.getX()); + playerBoundingBox.setSizeY(sizeY - shrink.getY()); + playerBoundingBox.setSizeZ(sizeZ - shrink.getZ()); // Resolve collision with the piston head BlockState pistonHeadId = Blocks.PISTON_HEAD.defaultBlockState() .withValue(Properties.SHORT, false) .withValue(Properties.FACING, orientation); - pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox); + pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox, onGround); // Resolve collision with any attached moving blocks, but skip slime blocks // This prevents players from being launched by slime blocks covered by other blocks @@ -367,7 +380,7 @@ public class PistonBlockEntity { BlockState state = entry.getValue(); if (!state.is(Blocks.SLIME_BLOCK)) { Vector3d blockPos = entry.getKey().toDouble(); - pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox); + pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround); } } // Resolve collision with slime blocks @@ -375,14 +388,14 @@ public class PistonBlockEntity { BlockState state = entry.getValue(); if (state.is(Blocks.SLIME_BLOCK)) { Vector3d blockPos = entry.getKey().toDouble(); - pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox); + pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround); } } // Undo shrink - playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + shrink.getX()); - playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() + shrink.getY()); - playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + shrink.getZ()); + playerBoundingBox.setSizeX(sizeX); + playerBoundingBox.setSizeY(sizeY); + playerBoundingBox.setSizeZ(sizeZ); } /** @@ -392,20 +405,22 @@ public class PistonBlockEntity { * @param playerBoundingBox The player's bounding box * @return True if the player attached, otherwise false */ - private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox) { + private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox, boolean onGround) { if (orientation.isVertical()) { return false; } - return session.getPlayerEntity().isOnGround() && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox); + return onGround && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox); } /** * Launches a player if the player is on the pushing side of the slime block * * @param blockPos The position of the slime block - * @param playerPos The player's position + * @param playerBoundingBox The player's bounding box */ - private void applySlimeBlockMotion(Vector3d blockPos, Vector3d playerPos) { + private void applySlimeBlockMotion(Vector3d blockPos, BoundingBox playerBoundingBox) { + Vector3d playerPos = Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()); + Direction movementDirection = orientation; // Invert direction when pulling if (action == PistonValueType.PULLING) { @@ -471,7 +486,7 @@ public class PistonBlockEntity { return maxIntersection; } - private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) { + private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox, boolean onGround) { PistonCache pistonCache = session.getPistonCache(); Vector3d movement = getMovement().toDouble(); // Check if the player collides with the movingBlock block entity @@ -481,12 +496,12 @@ public class PistonBlockEntity { if (state.is(Blocks.SLIME_BLOCK)) { pistonCache.setPlayerSlimeCollision(true); - applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + applySlimeBlockMotion(finalBlockPos, playerBoundingBox); } } Vector3d blockPos = startingPos.add(movement.mul(blockMovement)); - if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox)) { + if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox, onGround)) { pistonCache.setPlayerCollided(true); pistonCache.setPlayerAttachedToHoney(true); @@ -509,7 +524,7 @@ public class PistonBlockEntity { if (state.is(Blocks.SLIME_BLOCK)) { pistonCache.setPlayerSlimeCollision(true); - applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + applySlimeBlockMotion(blockPos, playerBoundingBox); } } } @@ -585,7 +600,7 @@ public class PistonBlockEntity { movingBlockMap.put(getPistonHeadPos(), this); Vector3i movement = getMovement(); - BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone(); + BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone(); if (orientation == Direction.UP) { // Extend the bounding box down, to catch collisions when the player is falling down playerBoundingBox.extend(0, -256, 0); @@ -629,17 +644,19 @@ public class PistonBlockEntity { return; } placedFinalBlocks = true; + Vector3i movement = getMovement(); + BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone(); attachedBlocks.forEach((blockPos, state) -> { blockPos = blockPos.add(movement); // Don't place blocks that collide with the player - if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox)) { ChunkUtils.updateBlock(session, state, blockPos); } }); if (action == PistonValueType.PUSHING) { Vector3i pistonHeadPos = getPistonHeadPos().add(movement); - if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), playerBoundingBox)) { ChunkUtils.updateBlock(session, Blocks.PISTON_HEAD.defaultBlockState() .withValue(Properties.SHORT, false) .withValue(Properties.FACING, orientation), pistonHeadPos); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 8d4df6f3f..1e84f032e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -43,13 +44,26 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { + String root = args[0]; + + CommandRegistry registry = GeyserImpl.getInstance().commandRegistry(); + if (registry.rootCommands().contains(root)) { + registry.runCommand(session, command); + return; // don't pass the command to the java server + } + } } + + if (MessageTranslator.isTooLong(command, session)) { + return; + } + + session.sendCommand(command); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 534a89e23..6ae21067f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -31,6 +31,7 @@ import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; +import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; @@ -42,6 +43,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetIte import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; @@ -70,15 +72,26 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.mcprotocollib.protocol.data.game.Holder; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; 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.entity.player.InteractAction; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Instrument; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; -import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; import java.util.List; import java.util.concurrent.TimeUnit; @@ -90,11 +103,6 @@ import java.util.concurrent.TimeUnit; @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { - private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f; - private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49; - private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36; - private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f; - @Override public void translate(GeyserSession session, InventoryTransactionPacket packet) { if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) { @@ -243,17 +251,11 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) { restoreCorrectBlock(session, blockPos, packet); return; } @@ -262,26 +264,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { - restoreCorrectBlock(session, blockPos, packet); - return; - } - Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f); - // Vanilla check - if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0) - .distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { - // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. - restoreCorrectBlock(session, blockPos, packet); - return; - } - // More recent vanilla check (as of 1.18.2) double clickDistanceX = clickPositionFullX - blockCenter.getX(); double clickDistanceY = clickPositionFullY - blockCenter.getY(); double clickDistanceZ = clickPositionFullZ - blockCenter.getZ(); @@ -303,13 +287,16 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator instrument = session.getPlayerInventory() + .getItemInHand() + .getComponent(DataComponentType.INSTRUMENT); + if (instrument != null && instrument.isId()) { + // BDS uses a LevelSoundEvent2Packet, but that doesn't work here... (as of 1.21.20) + LevelSoundEventPacket soundPacket = new LevelSoundEventPacket(); + soundPacket.setSound(SoundEvent.valueOf("GOAT_CALL_" + instrument.id())); + soundPacket.setPosition(session.getPlayerEntity().getPosition()); + soundPacket.setIdentifier("minecraft:player"); + soundPacket.setExtraData(-1); + session.sendUpstreamPacket(soundPacket); + } + } } } @@ -433,14 +436,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator MAXIMUM_BLOCK_DESTROYING_DISTANCE) { + playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight()); + + if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) { restoreCorrectBlock(session, packet.getBlockPosition(), packet); return; } @@ -550,6 +549,28 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator(BlockPos) + float minX = packetBlockPosition.getX(); + float minY = packetBlockPosition.getY(); + float minZ = packetBlockPosition.getZ(); + float maxX = packetBlockPosition.getX() + 1; + float maxY = packetBlockPosition.getY() + 1; + float maxZ = packetBlockPosition.getZ() + 1; + + // AABB#distanceToSqr + float diffX = Math.max(Math.max(minX - playerPosition.getX(), playerPosition.getX() - maxX), 0); + float diffY = Math.max(Math.max(minY - playerPosition.getY(), playerPosition.getY() - maxY), 0); + float diffZ = Math.max(Math.max(minZ - playerPosition.getZ(), playerPosition.getZ() - maxZ), 0); + return ((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) < (additionalRangeCheck * additionalRangeCheck); + } + /** * Restore the correct block state from the server without updating the chunk cache. * @@ -696,4 +717,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -70,7 +84,7 @@ public class BedrockActionTranslator extends PacketTranslator { // Respawn process is finished and the server and client are both OK with respawning. EntityEventPacket eventPacket = new EntityEventPacket(); eventPacket.setRuntimeEntityId(entity.getGeyserId()); @@ -88,16 +102,16 @@ public class BedrockActionTranslator extends PacketTranslator { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); session.sendDownstreamGamePacket(startSwimPacket); session.setSwimming(true); } - break; - case STOP_SWIMMING: + } + case STOP_SWIMMING -> { // Prevent packet spam when Bedrock players are crawling near the edge of a block if (!session.getCollisionManager().mustPlayerCrawlHere()) { ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); @@ -105,56 +119,55 @@ public class BedrockActionTranslator extends PacketTranslator { // Otherwise gliding will not work in creative ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false); session.sendDownstreamGamePacket(playerAbilitiesPacket); - case STOP_GLIDE: - ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); - session.sendDownstreamGamePacket(glidePacket); - break; - case START_SNEAK: + sendPlayerGlideToggle(session, entity); + } + case STOP_GLIDE -> sendPlayerGlideToggle(session, entity); + case START_SNEAK -> { ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING); session.sendDownstreamGamePacket(startSneakPacket); session.startSneaking(); - break; - case STOP_SNEAK: + } + case STOP_SNEAK -> { ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING); session.sendDownstreamGamePacket(stopSneakPacket); session.stopSneaking(); - break; - case START_SPRINT: + } + case START_SPRINT -> { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); session.sendDownstreamGamePacket(startSprintPacket); session.setSprinting(true); } - break; - case STOP_SPRINT: + } + case STOP_SPRINT -> { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); session.sendDownstreamGamePacket(stopSprintPacket); } session.setSprinting(false); - break; - case DROP_ITEM: + } + case DROP_ITEM -> { ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, - vector, Direction.VALUES[packet.getFace()], 0); + vector, Direction.VALUES[packet.getFace()], 0); session.sendDownstreamGamePacket(dropItemPacket); - break; - case STOP_SLEEP: + } + case STOP_SLEEP -> { ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED); session.sendDownstreamGamePacket(stopSleepingPacket); - break; - case START_BREAK: { - // Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021 - if (session.getGameMode() == GameMode.CREATIVE) { + } + case START_BREAK -> { + // Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021 + if (session.getGameMode() == GameMode.CREATIVE) { break; } - + // Start the block breaking animation int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector); LevelEventPacket startBreak = new LevelEventPacket(); @@ -180,18 +193,20 @@ public class BedrockActionTranslator extends PacketTranslator { if (session.getGameMode() == GameMode.CREATIVE) { break; } @@ -201,52 +216,48 @@ public class BedrockActionTranslator extends PacketTranslator= (breakTime+=2) * 50) { + if (timeSinceStart >= (breakTime += 2) * 50) { // Play break sound and particle LevelEventPacket effectPacket = new LevelEventPacket(); effectPacket.setPosition(vectorFloat); effectPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK); effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock)); session.sendUpstreamPacket(effectPacket); - + // Break the block ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, - vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); + vector, direction, session.getWorldCache().nextPredictionSequence()); session.sendDownstreamGamePacket(finishBreakingPacket); session.setBlockBreakStartTime(0); break; } } - + // Update the break time in the event that player conditions changed (jumping, effects applied) + LevelEventPacket updateBreak = new LevelEventPacket(); + updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK); + updateBreak.setPosition(vectorFloat); updateBreak.setData((int) (65535 / breakTime)); session.sendUpstreamPacket(updateBreak); - break; - case ABORT_BREAK: + } + case ABORT_BREAK -> { if (session.getGameMode() != GameMode.CREATIVE) { // As of 1.16.210: item frame items are taken out here. // Survival also sends START_BREAK, but by attaching our process here adventure mode also works Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector); if (itemFrameEntity != null) { ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(), - InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); + InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); session.sendDownstreamGamePacket(interactPacket); break; } @@ -260,25 +271,23 @@ public class BedrockActionTranslator extends PacketTranslator { + } + case DIMENSION_CHANGE_SUCCESS -> { //sometimes the client doesn't feel like loading PlayStatusPacket spawnPacket = new PlayStatusPacket(); spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); session.sendUpstreamPacket(spawnPacket); - attributesPacket = new UpdateAttributesPacket(); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(entity.getGeyserId()); attributesPacket.getAttributes().addAll(entity.getAttributes().values()); session.sendUpstreamPacket(attributesPacket); - break; - case JUMP: - entity.setOnGround(false); // Increase block break time while jumping - break; - case MISSED_SWING: + } + case JUMP -> entity.setOnGround(false); // Increase block break time while jumping + case MISSED_SWING -> { // Java edition sends a cooldown when hitting air. // Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this. CooldownUtils.sendCooldown(session); @@ -294,18 +303,18 @@ public class BedrockActionTranslator extends PacketTranslator { // Since 1.20.30 if (session.isCanFly()) { if (session.getGameMode() == GameMode.SPECTATOR) { - // should already be flying + // should already be flying session.sendAdventureSettings(); break; } if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) { - // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling - // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE + // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling + // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE session.sendAdventureSettings(); break; } @@ -313,9 +322,9 @@ public class BedrockActionTranslator extends PacketTranslator { session.setFlying(false); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); - break; - case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK: // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70 + } + case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70 BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector); - + if (state.getValue(Properties.HAS_BOOK, false)) { session.setDroppingLecternBook(true); ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket( - vector, - Direction.DOWN, - Hand.MAIN_HAND, - 0, 0, 0, - false, - session.getWorldCache().nextPredictionSequence()); + vector, + Direction.DOWN, + Hand.MAIN_HAND, + 0, 0, 0, + false, + session.getWorldCache().nextPredictionSequence()); session.sendDownstreamGamePacket(blockPacket); break; } @@ -349,10 +358,30 @@ public class BedrockActionTranslator extends PacketTranslator levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP); + case DOWN -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_DOWN); + case NORTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_NORTH); + case EAST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_EAST); + case SOUTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_SOUTH); + case WEST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_WEST); + } + levelEventPacket.setPosition(position.toFloat()); + levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId()); + session.sendUpstreamPacket(levelEventPacket); + } + + private void sendPlayerGlideToggle(GeyserSession session, Entity entity) { + ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); + session.sendDownstreamGamePacket(glidePacket); + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index 3d612c481..ee80cac16 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -30,6 +30,7 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -57,6 +58,14 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, RiderJumpPacket packet) { + session.getPlayerEntity().setVehicleJumpStrength(packet.getJumpStrength()); + Entity vehicle = session.getPlayerEntity().getVehicle(); if (vehicle instanceof AbstractHorseEntity) { ServerboundPlayerCommandPacket playerCommandPacket = new ServerboundPlayerCommandPacket(vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java index aa815fab7..a7199be97 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import org.cloudburstmc.protocol.bedrock.packet.SetDefaultGameTypePacket; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -41,7 +42,7 @@ public class BedrockSetDefaultGameTypeTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) { session.getGeyser().getWorldManager().setDefaultGameMode(session, GameMode.byId(packet.getGamemode())); } // Stop the client from updating their own Gamemode without telling the server diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java index 176f00b8f..c3fa2a1b3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; +import org.geysermc.geyser.Permissions; import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket; import org.geysermc.geyser.session.GeyserSession; @@ -39,7 +40,7 @@ public class BedrockSetDifficultyTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) { if (packet.getDifficulty() != session.getWorldCache().getDifficulty().ordinal()) { session.getGeyser().getWorldManager().setDifficulty(session, Difficulty.from(packet.getDifficulty())); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java index f00156268..0590ca0ad 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -45,7 +46,7 @@ public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) { if (packet.getGamemode() != session.getGameMode().ordinal()) { // Bedrock has more Gamemodes than Java, leading to cases 5 (for "default") and 6 (for "spectator") being sent // https://github.com/CloudburstMC/Protocol/blob/3.0/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/data/GameType.java diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java index 996dc9d84..24740d680 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java @@ -25,21 +25,29 @@ package org.geysermc.geyser.translator.protocol.java; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket; import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket; @Translator(packet = ClientboundChangeDifficultyPacket.class) public class JavaChangeDifficultyTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundChangeDifficultyPacket packet) { + Difficulty difficulty = packet.getDifficulty(); + session.getWorldCache().setDifficulty(difficulty); + + // Peaceful difficulty allows always eating food - hence, we just do not send it to Bedrock. + if (difficulty == Difficulty.PEACEFUL) { + difficulty = Difficulty.EASY; + } + SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket(); - setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal()); + setDifficultyPacket.setDifficulty(difficulty.ordinal()); session.sendUpstreamPacket(setDifficultyPacket); - session.getWorldCache().setDifficulty(packet.getDifficulty()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index ecfb2d220..f189658cd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -41,7 +41,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.*; import org.cloudburstmc.protocol.bedrock.packet.AvailableCommandsPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -76,6 +76,9 @@ public class JavaCommandsTranslator extends PacketTranslator commandData = new ArrayList<>(); IntSet commandNodes = new IntOpenHashSet(); @@ -151,8 +161,10 @@ public class JavaCommandsTranslator extends PacketTranslator new HashSet<>()).add(node.getName().toLowerCase()); + String name = node.getName().toLowerCase(Locale.ROOT); + String description = registry.description(name, session.locale()); + BedrockCommandInfo info = new BedrockCommandInfo(name, description, params); + commands.computeIfAbsent(info, $ -> new HashSet<>()).add(name); } var eventBus = session.getGeyser().eventBus(); @@ -169,8 +181,10 @@ public class JavaCommandsTranslator extends PacketTranslator flags = Set.of(); + // The command flags, set to NOT_CHEAT so known commands can be used while achievements are enabled. + Set flags = Set.of(CommandData.Flag.NOT_CHEAT); + + boolean helpAdded = false; // Loop through all the found commands for (Map.Entry> entry : commands.entrySet()) { @@ -188,6 +202,15 @@ public class JavaCommandsTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundFinishConfigurationPacket packet) { + // Clear the player list, as on Java the player list is cleared after transitioning from config to play phase + PlayerListPacket playerListPacket = new PlayerListPacket(); + playerListPacket.setAction(PlayerListPacket.Action.REMOVE); + for (PlayerEntity otherEntity : session.getEntityCache().getAllPlayerEntities()) { + playerListPacket.getEntries().add(new PlayerListPacket.Entry(otherEntity.getTabListUuid())); + } + session.sendUpstreamPacket(playerListPacket); + session.getEntityCache().removeAllPlayerEntities(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java index e7bde6a9d..47d1cff08 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.auth.data.GameProfile; +import org.geysermc.mcprotocollib.auth.GameProfile; import net.kyori.adventure.key.Key; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 6988d6cc8..fb9159c47 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -26,27 +26,25 @@ package org.geysermc.geyser.translator.protocol.java; import net.kyori.adventure.key.Key; -import org.geysermc.erosion.Constants; -import org.geysermc.geyser.util.MinecraftKey; -import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo; -import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket; -import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.GameRuleData; -import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket; -import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.erosion.Constants; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler; +import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo; +import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -59,18 +57,19 @@ public class JavaLoginTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundStartConfigurationPacket packet) { + var erosionHandler = session.getErosionHandler(); + if (erosionHandler.isActive()) { + // Set new handler before closing + session.setErosionHandler(new GeyserboundHandshakePacketHandler(session)); + erosionHandler.close(); + } + } + + @Override + public boolean shouldExecuteInEventLoop() { + // Execute outside of event loop to cancel any pending erosion futures + return false; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java index 0a0f2832c..27481aa26 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java @@ -25,8 +25,13 @@ package org.geysermc.geyser.translator.protocol.java; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.TranslationArgument; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.protocol.bedrock.data.LevelEvent; +import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket; import org.cloudburstmc.protocol.bedrock.packet.TextPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; @@ -34,18 +39,47 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket; @Translator(packet = ClientboundSystemChatPacket.class) public class JavaSystemChatTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundSystemChatPacket packet) { - if (packet.getContent() instanceof TranslatableComponent component && component.key().equals("chat.disabled.missingProfileKey")) { - // We likely got this message as a response to a player trying to chat - // As there SHOULD be no false flags for this, print every time it shows up in chat. - if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) { - session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale())); - session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat")); + if (packet.getContent() instanceof TranslatableComponent component) { + if (component.key().equals("chat.disabled.missingProfileKey")) { + // We likely got this message as a response to a player trying to chat + // As there SHOULD be no false flags for this, print every time it shows up in chat. + if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) { + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale())); + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat")); + } + } else if (component.key().equals("sleep.players_sleeping")) { + if (component.arguments().size() == 2) { + // Hack FYI, but it allows Bedrock players to easily understand this information + // without it being covered up or saying the night is being slept through. + Integer numPlayersSleeping = convertToInt(component.arguments().get(0)); + Integer totalPlayersNeeded = convertToInt(component.arguments().get(1)); + if (numPlayersSleeping != null && totalPlayersNeeded != null) { + LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket(); + sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS); + sleepInfoPacket.setTag(NbtMap.builder() + .putInt("ableToSleep", totalPlayersNeeded) + .putInt("overworldPlayerCount", totalPlayersNeeded) + .putInt("sleepingPlayerCount", numPlayersSleeping) + .build()); + session.sendUpstreamPacket(sleepInfoPacket); + } + } + } else if (component.key().equals("sleep.skipping_night")) { + LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket(); + sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS); + sleepInfoPacket.setTag(NbtMap.builder() + .putInt("ableToSleep", 1) + .putInt("overworldPlayerCount", 1) + .putInt("sleepingPlayerCount", 1) + .build()); + session.sendUpstreamPacket(sleepInfoPacket); } } @@ -68,4 +102,19 @@ public class JavaSystemChatTranslator extends PacketTranslator { craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId())); } + case CRAFTING_SPECIAL_FIREWORK_ROCKET -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000002"), context.getAndIncrementNetId())); + } default -> { List recipes = Registries.RECIPES.get(recipe.getType()); if (recipes != null) { @@ -232,7 +255,8 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator Registries.JAVA_ITEMS.get().get(stoneCuttingRecipeData.getResult().getId()) - .javaIdentifier()))); + // See RecipeManager#getRecipesFor as of 1.21 + .translationKey()))); // Now that it's sorted, let's translate these recipes int buttonId = 0; @@ -421,13 +445,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator translateShulkerBoxRecipe(GeyserShapelessRecipe recipe) { - ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + ItemStack result = recipe.result(); + ItemData output = ItemTranslator.translateToBedrock(session, result); if (!output.isValid()) { // Likely modded item that Bedrock will complain about if it persists return null; } - // Strip NBT - tools won't appear in the recipe book otherwise - output = output.toBuilder().tag(null).build(); + + Item javaItem = Registries.JAVA_ITEMS.get(result.getId()); + if (!(javaItem instanceof BedrockRequiresTagItem)) { + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + } ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); if (inputCombinations == null) { return null; @@ -445,13 +474,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator translateShapelessRecipe(GeyserShapelessRecipe recipe) { - ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + ItemStack result = recipe.result(); + ItemData output = ItemTranslator.translateToBedrock(session, result); if (!output.isValid()) { // Likely modded item that Bedrock will complain about if it persists return null; } - // Strip NBT - tools won't appear in the recipe book otherwise - output = output.toBuilder().tag(null).build(); + + Item javaItem = Registries.JAVA_ITEMS.get(result.getId()); + if (!(javaItem instanceof BedrockRequiresTagItem)) { + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + } ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); if (inputCombinations == null) { return null; @@ -469,13 +503,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator translateShapedRecipe(GeyserShapedRecipe recipe) { - ItemData output = ItemTranslator.translateToBedrock(session, recipe.result()); + ItemStack result = recipe.result(); + ItemData output = ItemTranslator.translateToBedrock(session, result); if (!output.isValid()) { // Likely modded item that Bedrock will complain about if it persists return null; } - // See above - output = output.toBuilder().tag(null).build(); + + Item javaItem = Registries.JAVA_ITEMS.get(result.getId()); + if (!(javaItem instanceof BedrockRequiresTagItem)) { + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + } ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); if (inputCombinations == null) { return null; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java index bf6dfe684..844b82afe 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java @@ -92,7 +92,7 @@ public class JavaAnimateTranslator extends PacketTranslator { - // BODY is sent for llamas with a carpet equipped, as of 1.20.5 + case CHESTPLATE -> { livingEntity.setChestplate(stack); armorUpdated = true; } + case BODY -> { + // BODY is sent for llamas with a carpet equipped, as of 1.20.5 + if (GameProtocol.isPre1_21_2(session)) { + livingEntity.setChestplate(stack); + } else { + livingEntity.setBody(stack); + } + armorUpdated = true; + } case LEGGINGS -> { livingEntity.setLeggings(stack); armorUpdated = true; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java index 9895a248c..865ca0464 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java @@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -55,6 +56,10 @@ public class JavaSetPassengersTranslator extends PacketTranslator new PistonBlockEntity(session, pos, direction, true, true)); + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> new PistonBlockEntity(session, pos, direction, isSticky, true)); if (blockEntity.getAction() != action) { blockEntity.setAction(action, Object2ObjectMaps.emptyMap()); } @@ -99,7 +113,7 @@ public class JavaBlockEventTranslator extends PacketTranslator { BlockState state = session.getGeyser().getWorldManager().blockAt(session, position); - boolean sticky = state.is(Blocks.STICKY_PISTON); + boolean sticky = isSticky(state); boolean extended = action != PistonValueType.PUSHING; return new PistonBlockEntity(session, pos, direction, sticky, extended); }); @@ -149,4 +163,8 @@ public class JavaBlockEventTranslator extends PacketTranslator { - // Strength of rainstorms and thunderstorms is a 0-1 float on Java, while on Bedrock it is a 0-65535 int - private static final int MAX_STORM_STRENGTH = 65535; - @Override public void translate(GeyserSession session, ClientboundGameEventPacket packet) { PlayerEntity entity = session.getPlayerEntity(); @@ -65,42 +60,20 @@ public class JavaGameEventTranslator extends PacketTranslator 0f; - LevelEventPacket changeRainPacket = new LevelEventPacket(); - changeRainPacket.setType(isCurrentlyRaining ? LevelEvent.START_RAINING : LevelEvent.STOP_RAINING); // This is the rain strength on LevelEventType.START_RAINING, but can be any value on LevelEventType.STOP_RAINING - changeRainPacket.setData((int) (rainStrength * MAX_STORM_STRENGTH)); - changeRainPacket.setPosition(Vector3f.ZERO); - session.sendUpstreamPacket(changeRainPacket); - session.setRaining(isCurrentlyRaining); + float rainStrength = ((RainStrengthValue) packet.getValue()).getStrength(); + session.updateRain(rainStrength); break; case THUNDER_STRENGTH: // See above, same process float thunderStrength = ((ThunderStrengthValue) packet.getValue()).getStrength(); - boolean isCurrentlyThundering = thunderStrength > 0f; - LevelEventPacket changeThunderPacket = new LevelEventPacket(); - changeThunderPacket.setType(isCurrentlyThundering ? LevelEvent.START_THUNDERSTORM : LevelEvent.STOP_THUNDERSTORM); - changeThunderPacket.setData((int) (thunderStrength * MAX_STORM_STRENGTH)); - changeThunderPacket.setPosition(Vector3f.ZERO); - session.sendUpstreamPacket(changeThunderPacket); - session.setThunder(isCurrentlyThundering); + session.updateThunder(thunderStrength); break; case CHANGE_GAMEMODE: GameMode gameMode = (GameMode) packet.getValue(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index b6bb1c0c6..af3c8595e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -29,7 +29,12 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; -import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntImmutableList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NBTOutputStream; @@ -56,7 +61,6 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.mcprotocollib.protocol.data.game.chunk.BitStorage; import org.geysermc.mcprotocollib.protocol.data.game.chunk.ChunkSection; import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; @@ -99,7 +103,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4) - 1; int sectionCount; @@ -509,7 +513,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator entry : session.getItemFrameCache().entrySet()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index 542af1598..5b4ff1de7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.level; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.ParticleType; import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket; @@ -292,10 +291,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { - effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_GENERIC_SPAWN); - effectPacket.setData(61); - } + case PARTICLES_DRAGON_BLOCK_BREAK -> effectPacket.setType(ParticleType.DRAGON_DESTROY_BLOCK); case PARTICLES_WATER_EVAPORATING -> { effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EVAPORATE_WATER); effectPacket.setPosition(pos.add(-0.5f, 0.5f, -0.5f)); @@ -481,7 +477,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); stringPacket.setIdentifier(particleMapping.identifier()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java index 1591b4952..52a08ab29 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java @@ -46,7 +46,7 @@ public class JavaMapItemDataTranslator extends PacketTranslator { - @Override public void translate(GeyserSession session, ClientboundResetScorePacket packet) { WorldCache worldCache = session.getWorldCache(); Scoreboard scoreboard = worldCache.getScoreboard(); int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - Objective belowName = scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); - if (packet.getObjective() == null) { // No objective name means all scores are reset for that player (/scoreboard players reset PLAYERNAME) - for (Objective otherObjective : scoreboard.getObjectives()) { - otherObjective.removeScore(packet.getOwner()); - } - - // as described below - if (belowName != null) { - JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner()); - } + scoreboard.resetPlayerScores(packet.getOwner()); } else { Objective objective = scoreboard.getObjective(packet.getObjective()); objective.removeScore(packet.getOwner()); - - // If this is the objective that is in use to show the below name text, we need to update the player - // attached to this score. - if (objective == belowName) { - // Update the score on this player to now reflect 0 - JavaSetScoreTranslator.setBelowName(session, objective, packet.getOwner()); - } } // ScoreboardUpdater will handle it for us if the packets per second diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java index 85d93c0b5..0a7c6131f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java @@ -25,72 +25,45 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.scoreboard.Objective; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; -import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; @Translator(packet = ClientboundSetObjectivePacket.class) public class JavaSetObjectiveTranslator extends PacketTranslator { - private final GeyserLogger logger = GeyserImpl.getInstance().getLogger(); - @Override public void translate(GeyserSession session, ClientboundSetObjectivePacket packet) { WorldCache worldCache = session.getWorldCache(); Scoreboard scoreboard = worldCache.getScoreboard(); int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - Objective objective = scoreboard.getObjective(packet.getName()); - if (objective != null && objective.getUpdateType() != UpdateType.REMOVE && packet.getAction() == ObjectiveAction.ADD) { - // matches vanilla behaviour - logger.warning("An objective with the same name '" + packet.getName() + "' already exists! Ignoring packet"); + Objective objective; + if (packet.getAction() == ObjectiveAction.ADD) { + objective = scoreboard.registerNewObjective(packet.getName()); + } else { + objective = scoreboard.getObjective(packet.getName()); + } + + // matches vanilla + if (objective == null) { return; } - if ((objective == null || objective.getUpdateType() == UpdateType.REMOVE) && packet.getAction() != ObjectiveAction.REMOVE) { - objective = scoreboard.registerNewObjective(packet.getName()); - } - switch (packet.getAction()) { - case ADD, UPDATE -> { - objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName())) - .setNumberFormat(packet.getNumberFormat()) - .setType(packet.getType().ordinal()); - if (objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { - // Update the score tag of all players - for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { - if (entity.isValid()) { - entity.setBelowNameText(objective); - } - } - } - } - case REMOVE -> { - scoreboard.unregisterObjective(packet.getName()); - if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { - // Clear the score tag from all players - for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { - // Other places we check for the entity being valid, - // but we must set the below name text as null for all players - // or else PlayerEntity#spawnEntity will find a null objective and not touch EntityData#SCORE_TAG - entity.setBelowNameText(null); - } - } - } + case ADD, UPDATE -> + objective.updateProperties(packet.getDisplayName(), packet.getType(), packet.getNumberFormat()); + case REMOVE -> scoreboard.removeObjective(objective); } - if (objective == null || !objective.isActive()) { + // Scoreboard#removeObjective doesn't touch the display slot(s) that were attached to it. + // So Objective#hasDisplaySlot will be true as long as it's currently present on the Bedrock client + if (!objective.hasDisplaySlot()) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java index 999edcc8c..3a1ee6373 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java @@ -25,23 +25,17 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; +import java.util.Arrays; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.scoreboard.Team; -import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.text.MessageTranslator; - -import java.util.Arrays; -import java.util.Set; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; @Translator(packet = ClientboundSetPlayerTeamPacket.class) public class JavaSetPlayerTeamTranslator extends PacketTranslator { @@ -60,83 +54,45 @@ public class JavaSetPlayerTeamTranslator extends PacketTranslator { - team = scoreboard.registerNewTeam(packet.getTeamName(), packet.getPlayers()) - .setName(MessageTranslator.convertMessage(packet.getDisplayName())) - .setColor(packet.getColor()) - .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.locale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.locale())); - if (packet.getPlayers().length != 0) { - if ((team.getNameTagVisibility() != NameTagVisibility.ALWAYS && !team.isVisibleFor(session.getPlayerEntity().getUsername())) - || team.getColor() != TeamColor.RESET - || !team.getCurrentData().getPrefix().isEmpty() - || !team.getCurrentData().getSuffix().isEmpty()) { - // Something is here that would modify entity names - scoreboard.updateEntityNames(team, true); - } + if (packet.getAction() == TeamAction.CREATE) { + scoreboard.registerNewTeam( + packet.getTeamName(), + packet.getPlayers(), + packet.getDisplayName(), + packet.getPrefix(), + packet.getSuffix(), + packet.getNameTagVisibility(), + packet.getColor() + ); + } else { + Team team = scoreboard.getTeam(packet.getTeamName()); + if (team == null) { + if (logger.isDebug()) { + logger.debug("Error while translating Team Packet " + packet.getAction() + + "! Scoreboard Team " + packet.getTeamName() + " is not registered." + ); } + return; } - case UPDATE -> { - if (team == null) { - if (logger.isDebug()) { - logger.debug("Error while translating Team Packet " + packet.getAction() - + "! Scoreboard Team " + packet.getTeamName() + " is not registered." - ); - } - return; - } - TeamColor oldColor = team.getColor(); - NameTagVisibility oldVisibility = team.getNameTagVisibility(); - String oldPrefix = team.getCurrentData().getPrefix(); - String oldSuffix = team.getCurrentData().getSuffix(); - - team.setName(MessageTranslator.convertMessage(packet.getDisplayName())) - .setColor(packet.getColor()) - .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.locale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.locale())) - .setUpdateType(UpdateType.UPDATE); - - if (oldVisibility != team.getNameTagVisibility() - || oldColor != team.getColor() - || !oldPrefix.equals(team.getCurrentData().getPrefix()) - || !oldSuffix.equals(team.getCurrentData().getSuffix())) { - // Update entities attached to this team as something about their nameplates have changed - scoreboard.updateEntityNames(team, false); + switch (packet.getAction()) { + case UPDATE -> { + team.updateProperties( + packet.getDisplayName(), + packet.getPrefix(), + packet.getSuffix(), + packet.getNameTagVisibility(), + packet.getColor() + ); } + case ADD_PLAYER -> team.addEntities(packet.getPlayers()); + case REMOVE_PLAYER -> team.removeEntities(packet.getPlayers()); + case REMOVE -> scoreboard.removeTeam(packet.getTeamName()); } - case ADD_PLAYER -> { - if (team == null) { - if (logger.isDebug()) { - logger.debug("Error while translating Team Packet " + packet.getAction() - + "! Scoreboard Team " + packet.getTeamName() + " is not registered." - ); - } - return; - } - Set added = team.addEntities(packet.getPlayers()); - scoreboard.updateEntityNames(team, added, true); - } - case REMOVE_PLAYER -> { - if (team == null) { - if (logger.isDebug()) { - logger.debug("Error while translating Team Packet " + packet.getAction() - + "! Scoreboard Team " + packet.getTeamName() + " is not registered." - ); - } - return; - } - Set removed = team.removeEntities(packet.getPlayers()); - scoreboard.updateEntityNames(null, removed, true); - } - case REMOVE -> scoreboard.removeTeam(packet.getTeamName()); } + // ScoreboardUpdater will handle it for us if the packets per second // (for score and team packets) is higher than the first threshold if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java index d1645b496..989f0f2cb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java @@ -25,12 +25,8 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard; -import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.scoreboard.Objective; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; @@ -39,6 +35,7 @@ import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; @Translator(packet = ClientboundSetScorePacket.class) public class JavaSetScoreTranslator extends PacketTranslator { @@ -63,16 +60,7 @@ public class JavaSetScoreTranslator extends PacketTranslator TEAM_COLORS = new EnumMap<>(TeamColor.class); - // Legacy formatting character private static final String BASE = "\u00a7"; @@ -69,31 +74,6 @@ public class MessageTranslator { private static final String RESET = BASE + "r"; static { - TEAM_COLORS.put(TeamColor.RESET, RESET); - - TEAM_COLORS.put(TeamColor.BLACK, BASE + "0"); - TEAM_COLORS.put(TeamColor.DARK_BLUE, BASE + "1"); - TEAM_COLORS.put(TeamColor.DARK_GREEN, BASE + "2"); - TEAM_COLORS.put(TeamColor.DARK_AQUA, BASE + "3"); - TEAM_COLORS.put(TeamColor.DARK_RED, BASE + "4"); - TEAM_COLORS.put(TeamColor.DARK_PURPLE, BASE + "5"); - TEAM_COLORS.put(TeamColor.GOLD, BASE + "6"); - TEAM_COLORS.put(TeamColor.GRAY, BASE + "7"); - TEAM_COLORS.put(TeamColor.DARK_GRAY, BASE + "8"); - TEAM_COLORS.put(TeamColor.BLUE, BASE + "9"); - TEAM_COLORS.put(TeamColor.GREEN, BASE + "a"); - TEAM_COLORS.put(TeamColor.AQUA, BASE + "b"); - TEAM_COLORS.put(TeamColor.RED, BASE + "c"); - TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, BASE + "d"); - TEAM_COLORS.put(TeamColor.YELLOW, BASE + "e"); - TEAM_COLORS.put(TeamColor.WHITE, BASE + "f"); - - // Formats, not colors - TEAM_COLORS.put(TeamColor.OBFUSCATED, BASE + "k"); - TEAM_COLORS.put(TeamColor.BOLD, BASE + "l"); - TEAM_COLORS.put(TeamColor.STRIKETHROUGH, BASE + "m"); - TEAM_COLORS.put(TeamColor.ITALIC, BASE + "o"); - // Temporary fix for https://github.com/KyoriPowered/adventure/issues/447 - TODO resolve properly GsonComponentSerializer source = DefaultComponentSerializer.get() .toBuilder() @@ -145,13 +125,31 @@ public class MessageTranslator { } /** - * Convert a Java message to the legacy format ready for bedrock + * Convert a Java message to the legacy format ready for bedrock. Unlike + * {@link #convertMessageRaw(Component, String)} this adds a leading color reset. In Bedrock + * some places have build-in colors. * * @param message Java message * @param locale Locale to use for translation strings * @return Parsed and formatted message for bedrock */ public static String convertMessage(Component message, String locale) { + return convertMessage(message, locale, true); + } + + /** + * Convert a Java message to the legacy format ready for bedrock. Unlike {@link #convertMessage(Component, String)} + * this version does not add a leading color reset. In Bedrock some places have build-in colors. + * + * @param message Java message + * @param locale Locale to use for translation strings + * @return Parsed and formatted message for bedrock + */ + public static String convertMessageRaw(Component message, String locale) { + return convertMessage(message, locale, false); + } + + private static String convertMessage(Component message, String locale, boolean addLeadingResetFormat) { try { // Translate any components that require it message = RENDERER.render(message, locale); @@ -160,7 +158,7 @@ public class MessageTranslator { StringBuilder finalLegacy = new StringBuilder(); char[] legacyChars = legacy.toCharArray(); - boolean lastFormatReset = false; + boolean lastFormatReset = !addLeadingResetFormat; for (int i = 0; i < legacyChars.length; i++) { char legacyChar = legacyChars[i]; if (legacyChar != ChatColor.ESCAPE || i >= legacyChars.length - 1) { @@ -173,7 +171,7 @@ public class MessageTranslator { char next = legacyChars[++i]; if (BEDROCK_COLORS.indexOf(next) != -1) { - // Append this color code, as well as a necessary reset code + // Unlike Java Edition, the ChatFormatting is not reset when a ChatColor is added if (!lastFormatReset) { finalLegacy.append(RESET); } @@ -341,16 +339,16 @@ public class MessageTranslator { // Though, Bedrock cannot care about the signed stuff. TranslatableComponent.Builder withDecoration = Component.translatable() .key(chat.translationKey()) - .style(TextDecoration.getStyle(chat)); + .style(ChatDecoration.getStyle(chat)); List parameters = chat.parameters(); List args = new ArrayList<>(3); - if (parameters.contains(TextDecoration.Parameter.TARGET)) { + if (parameters.contains(ChatDecoration.Parameter.TARGET)) { args.add(targetName); } - if (parameters.contains(TextDecoration.Parameter.SENDER)) { + if (parameters.contains(ChatDecoration.Parameter.SENDER)) { args.add(sender); } - if (parameters.contains(TextDecoration.Parameter.CONTENT)) { + if (parameters.contains(ChatDecoration.Parameter.CONTENT)) { args.add(message); } withDecoration.arguments(args); @@ -366,16 +364,6 @@ public class MessageTranslator { session.sendUpstreamPacket(textPacket); } - /** - * Convert a team color to a chat color - * - * @param teamColor Color or format to convert - * @return The chat color character - */ - public static String toChatColor(TeamColor teamColor) { - return TEAM_COLORS.getOrDefault(teamColor, ""); - } - /** * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is * @@ -426,17 +414,92 @@ public class MessageTranslator { } /** - * Deserialize an NbtMap provided from a registry into a string. + * Deserialize an NbtMap with a description text component (usually provided from a registry) into a Bedrock-formatted string. */ - // This may be a Component in the future. - public static String deserializeDescription(NbtMap tag) { - NbtMap description = tag.getCompound("description"); - String translate = description.getString("translate", null); - if (translate == null) { - GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag); - return ""; + public static String deserializeDescription(GeyserSession session, NbtMap tag) { + Object description = tag.get("description"); + Component parsed = componentFromNbtTag(description); + return convertMessage(session, parsed); + } + + public static Component componentFromNbtTag(Object nbtTag) { + return componentFromNbtTag(nbtTag, Style.empty()); + } + + private static Component componentFromNbtTag(Object nbtTag, Style style) { + if (nbtTag instanceof String literal) { + return Component.text(literal).style(style); + } else if (nbtTag instanceof List list) { + return Component.join(JoinConfiguration.noSeparators(), componentsFromNbtList(list, style)); + } else if (nbtTag instanceof NbtMap map) { + Component component = null; + String text = map.getString("text", null); + if (text != null) { + component = Component.text(text); + } else { + String translateKey = map.getString("translate", null); + if (translateKey != null) { + String fallback = map.getString("fallback", ""); + List args = new ArrayList<>(); + + Object with = map.get("with"); + if (with instanceof List list) { + args = componentsFromNbtList(list, style); + } else if (with != null) { + args.add(componentFromNbtTag(with, style)); + } + component = Component.translatable(translateKey, fallback, args); + } + } + + if (component != null) { + Style newStyle = getStyleFromNbtMap(map, style); + component = component.style(newStyle); + + Object extra = map.get("extra"); + if (extra != null) { + component = component.append(componentFromNbtTag(extra, newStyle)); + } + + return component; + } } - return translate; + + GeyserImpl.getInstance().getLogger().error("Expected tag to be a literal string, a list of components, or a component object with a text/translate key: " + nbtTag); + return Component.empty(); + } + + private static List componentsFromNbtList(List list, Style style) { + List components = new ArrayList<>(); + for (Object entry : list) { + components.add(componentFromNbtTag(entry, style)); + } + return components; + } + + public static Style getStyleFromNbtMap(NbtMap map) { + Style.Builder style = Style.style(); + + String colorString = map.getString("color", null); + if (colorString != null) { + if (colorString.startsWith(TextColor.HEX_PREFIX)) { + style.color(TextColor.fromHexString(colorString)); + } else { + style.color(NamedTextColor.NAMES.value(colorString)); + } + } + + map.listenForBoolean("bold", value -> style.decoration(TextDecoration.BOLD, value)); + map.listenForBoolean("italic", value -> style.decoration(TextDecoration.ITALIC, value)); + map.listenForBoolean("underlined", value -> style.decoration(TextDecoration.UNDERLINED, value)); + map.listenForBoolean("strikethrough", value -> style.decoration(TextDecoration.STRIKETHROUGH, value)); + map.listenForBoolean("obfuscated", value -> style.decoration(TextDecoration.OBFUSCATED, value)); + + return style.build(); + } + + public static Style getStyleFromNbtMap(NbtMap map, Style base) { + return base.merge(getStyleFromNbtMap(map)); } public static void init() { diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index 7ad97c852..cb149426d 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -156,7 +156,7 @@ public class ChunkUtils { } public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) { - BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + BedrockDimension bedrockDimension = session.getBedrockDimension(); int bedrockSubChunkCount = bedrockDimension.height() >> 4; byte[] payload; @@ -174,7 +174,7 @@ public class ChunkUtils { byteBuf.readBytes(payload); LevelChunkPacket data = new LevelChunkPacket(); - data.setDimension(DimensionUtils.javaToBedrock(session.getChunkCache().getBedrockDimension())); + data.setDimension(session.getBedrockDimension().bedrockId()); data.setChunkX(chunkX); data.setChunkZ(chunkZ); data.setSubChunksLength(0); @@ -210,19 +210,11 @@ public class ChunkUtils { * This must be done after the player has switched dimensions so we know what their dimension is */ public static void loadDimension(GeyserSession session) { - JavaDimension dimension = session.getRegistryCache().dimensions().byId(session.getDimension()); - session.setDimensionType(dimension); + JavaDimension dimension = session.getDimensionType(); int minY = dimension.minY(); int maxY = dimension.maxY(); - if (minY % 16 != 0) { - throw new RuntimeException("Minimum Y must be a multiple of 16!"); - } - if (maxY % 16 != 0) { - throw new RuntimeException("Maximum Y must be a multiple of 16!"); - } - - BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + BedrockDimension bedrockDimension = session.getBedrockDimension(); // Yell in the console if the world height is too height in the current scenario // The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled // (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ ) @@ -230,7 +222,7 @@ public class ChunkUtils { session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds", String.valueOf(bedrockDimension.minY()), String.valueOf(bedrockDimension.height()), - session.getDimension())); + session.getRegistryCache().dimensions().byValue(session.getDimensionType()))); } session.getChunkCache().setMinY(minY); diff --git a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java index 8446a340a..b4fd6b924 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -25,12 +25,18 @@ package org.geysermc.geyser.util; +import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.PlayerActionType; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.ChangeDimensionPacket; +import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; +import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket; +import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; @@ -38,27 +44,14 @@ import java.util.Set; public class DimensionUtils { - // Changes if the above-bedrock Nether building workaround is applied - private static int BEDROCK_NETHER_ID = 1; - public static final String BEDROCK_FOG_HELL = "minecraft:fog_hell"; - /** - * String reference to vanilla Java overworld dimension identifier - */ - public static final int OVERWORLD = 0; - /** - * String reference to vanilla Java nether dimension identifier - */ - public static final int NETHER = 3; - /** - * String reference to vanilla Java end dimension identifier - */ - public static final int THE_END = 2; + public static void switchDimension(GeyserSession session, JavaDimension javaDimension) { + switchDimension(session, javaDimension, javaDimension.bedrockId()); + } - public static void switchDimension(GeyserSession session, int javaDimension) { - int bedrockDimension = javaToBedrock(javaDimension); // new bedrock dimension - int previousDimension = session.getDimension(); // previous java dimension + public static void switchDimension(GeyserSession session, JavaDimension javaDimension, int bedrockDimension) { + @Nullable JavaDimension previousDimension = session.getDimensionType(); // previous java dimension; can be null if an online player with no saved auth token logs in. Entity player = session.getPlayerEntity(); @@ -69,6 +62,48 @@ public class DimensionUtils { session.getPistonCache().clear(); session.getSkullCache().clear(); + changeDimension(session, bedrockDimension); + + session.setDimensionType(javaDimension); + + Set entityEffects = session.getEffectCache().getEntityEffects(); + for (Effect effect : entityEffects) { + MobEffectPacket mobEffectPacket = new MobEffectPacket(); + mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE); + mobEffectPacket.setRuntimeEntityId(player.getGeyserId()); + mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect)); + session.sendUpstreamPacket(mobEffectPacket); + } + // Effects are re-sent from server + entityEffects.clear(); + + // Always reset weather, as it sometimes suddenly starts raining. See https://github.com/GeyserMC/Geyser/issues/3679 + session.updateRain(0); + session.updateThunder(0); + + finalizeDimensionSwitch(session, player); + + // If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension, + // we check if the player is entering the nether and apply the nether fog to fake the fact that the client + // thinks they are in the end dimension. + if (BedrockDimension.isCustomBedrockNetherId()) { + if (javaDimension.isNetherLike()) { + session.camera().sendFog(BEDROCK_FOG_HELL); + } else if (previousDimension != null && previousDimension.isNetherLike()) { + session.camera().removeFog(BEDROCK_FOG_HELL); + } + } + } + + /** + * Switch dimensions without clearing internal logic. + */ + public static void fastSwitchDimension(GeyserSession session, int bedrockDimension) { + changeDimension(session, bedrockDimension); + finalizeDimensionSwitch(session, session.getPlayerEntity()); + } + + private static void changeDimension(GeyserSession session, int bedrockDimension) { if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) { // The server-sided view distance wasn't a thing until Minecraft Java 1.14 // So ViaVersion compensates by sending a "view distance" of 64 @@ -77,7 +112,7 @@ public class DimensionUtils { // To solve this, we cap at 32 unless we know that the render distance actually exceeds 32 // Also, as of 1.19: PS4 crashes with a ChunkRadiusUpdatedPacket too large session.getGeyser().getLogger().debug("Applying dimension switching workaround for Bedrock render distance of " - + session.getServerRenderDistance()); + + session.getServerRenderDistance()); ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); chunkRadiusUpdatedPacket.setRadius(32); session.sendUpstreamPacket(chunkRadiusUpdatedPacket); @@ -92,24 +127,14 @@ public class DimensionUtils { changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); - session.setDimension(javaDimension); - setBedrockDimension(session, javaDimension); + setBedrockDimension(session, bedrockDimension); - player.setPosition(pos); + session.getPlayerEntity().setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); + } - Set entityEffects = session.getEffectCache().getEntityEffects(); - for (Effect effect : entityEffects) { - MobEffectPacket mobEffectPacket = new MobEffectPacket(); - mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE); - mobEffectPacket.setRuntimeEntityId(player.getGeyserId()); - mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect)); - session.sendUpstreamPacket(mobEffectPacket); - } - // Effects are re-sent from server - entityEffects.clear(); - + private static void finalizeDimensionSwitch(GeyserSession session, Entity player) { //let java server handle portal travel sound StopSoundPacket stopSoundPacket = new StopSoundPacket(); stopSoundPacket.setStoppingAllSound(true); @@ -130,51 +155,16 @@ public class DimensionUtils { // TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent. // The client wants sections sent to it before it can successfully respawn. ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true); - - // If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension, - // we check if the player is entering the nether and apply the nether fog to fake the fact that the client - // thinks they are in the end dimension. - if (isCustomBedrockNetherId()) { - if (NETHER == javaDimension) { - session.camera().sendFog(BEDROCK_FOG_HELL); - } else if (NETHER == previousDimension) { - session.camera().removeFog(BEDROCK_FOG_HELL); - } - } } - public static void setBedrockDimension(GeyserSession session, int javaDimension) { - session.getChunkCache().setBedrockDimension(switch (javaDimension) { - case DimensionUtils.THE_END -> BedrockDimension.THE_END; - case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER; - default -> BedrockDimension.OVERWORLD; + public static void setBedrockDimension(GeyserSession session, int bedrockDimension) { + session.setBedrockDimension(switch (bedrockDimension) { + case BedrockDimension.END_ID -> BedrockDimension.THE_END; + case BedrockDimension.DEFAULT_NETHER_ID -> BedrockDimension.THE_NETHER; // JavaDimension *should* be set to BEDROCK_END_ID if the Nether workaround is enabled. + default -> session.getBedrockOverworldDimension(); }); } - public static int javaToBedrock(BedrockDimension dimension) { - if (dimension == BedrockDimension.THE_NETHER) { - return BEDROCK_NETHER_ID; - } else if (dimension == BedrockDimension.THE_END) { - return 2; - } else { - return 0; - } - } - - /** - * Map the Java edition dimension IDs to Bedrock edition - * - * @param javaDimension Dimension ID to convert - * @return Converted Bedrock edition dimension ID - */ - public static int javaToBedrock(int javaDimension) { - return switch (javaDimension) { - case NETHER -> BEDROCK_NETHER_ID; - case THE_END -> 2; - default -> 0; - }; - } - /** * Map the Java edition dimension IDs to Bedrock edition * @@ -183,42 +173,39 @@ public class DimensionUtils { */ public static int javaToBedrock(String javaDimension) { return switch (javaDimension) { - case "minecraft:the_nether" -> BEDROCK_NETHER_ID; - case "minecraft:the_end" -> 2; - default -> 0; + case BedrockDimension.NETHER_IDENTIFIER -> BedrockDimension.BEDROCK_NETHER_ID; + case "minecraft:the_end" -> BedrockDimension.END_ID; + default -> BedrockDimension.OVERWORLD_ID; }; } /** - * The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension. - * This workaround sets the Nether as the End dimension to ignore this limit. - * - * @param isAboveNetherBedrockBuilding true if we should apply The End workaround + * Gets the Bedrock dimension ID, with a safety check if a packet is created before the player is logged/spawned in. */ - public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) { - // Change dimension ID to the End to allow for building above Bedrock - BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1; + public static int javaToBedrock(GeyserSession session) { + JavaDimension dimension = session.getDimensionType(); + if (dimension == null) { + return BedrockDimension.OVERWORLD_ID; + } + return dimension.bedrockId(); } /** * Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional * dimension switch. * - * @param currentDimension the current dimension of the player - * @param newDimension the new dimension that the player will be transferred to - * @return the fake dimension to transfer to + * @param currentBedrockDimension the current dimension of the player + * @param newBedrockDimension the new dimension that the player will be transferred to + * @return the Bedrock fake dimension to transfer to */ - public static int getTemporaryDimension(int currentDimension, int newDimension) { - if (isCustomBedrockNetherId()) { + public static int getTemporaryDimension(int currentBedrockDimension, int newBedrockDimension) { + if (BedrockDimension.isCustomBedrockNetherId()) { // Prevents rare instances of Bedrock locking up - return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER; + return newBedrockDimension == BedrockDimension.END_ID ? BedrockDimension.OVERWORLD_ID : BedrockDimension.END_ID; } // Check current Bedrock dimension and not just the Java dimension. // Fixes rare instances like https://github.com/GeyserMC/Geyser/issues/3161 - return javaToBedrock(currentDimension) == 0 ? NETHER : OVERWORLD; + return currentBedrockDimension == BedrockDimension.OVERWORLD_ID ? BedrockDimension.DEFAULT_NETHER_ID : BedrockDimension.OVERWORLD_ID; } - public static boolean isCustomBedrockNetherId() { - return BEDROCK_NETHER_ID == 2; - } } diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 53aefde1e..8e5a57fae 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -25,6 +25,10 @@ package org.geysermc.geyser.util; +import java.util.Locale; +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.Vector3f; import org.cloudburstmc.protocol.bedrock.data.GameType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; @@ -38,13 +42,13 @@ import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; 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.entity.type.EntityType; -import java.util.Locale; - public final class EntityUtils { /** * A constant array of the two hands that a player can interact with an entity. @@ -290,6 +294,32 @@ public final class EntityUtils { }; } + private static String translatedEntityName(@NonNull String namespace, @NonNull String name, @NonNull GeyserSession session) { + // MinecraftLocale would otherwise invoke getBootstrap (which doesn't exist) and create some folders, + // so use the default fallback value as used in Minecraft Java + if (EnvironmentUtils.isUnitTesting) { + return "entity." + namespace + "." + name; + } + return MinecraftLocale.getLocaleString("entity." + namespace + "." + name, session.locale()); + } + + public static String translatedEntityName(@NonNull Key type, @NonNull GeyserSession session) { + return translatedEntityName(type.namespace(), type.value(), session); + } + + public static String translatedEntityName(@Nullable EntityType type, @NonNull GeyserSession session) { + if (type == EntityType.PLAYER) { + return "Player"; // the player's name is always shown instead + } + // default fallback value as used in Minecraft Java + if (type == null) { + return "entity.unregistered_sadface"; + } + // this works at least with all 1.20.5 entities, except the killer bunny since that's not an entity type. + String typeName = type.name().toLowerCase(Locale.ROOT); + return translatedEntityName("minecraft", typeName, session); + } + private EntityUtils() { } } diff --git a/core/src/main/java/org/geysermc/geyser/util/EnvironmentUtils.java b/core/src/main/java/org/geysermc/geyser/util/EnvironmentUtils.java new file mode 100644 index 000000000..909398bf4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/EnvironmentUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +public final class EnvironmentUtils { + public static final boolean isUnitTesting = isUnitTesting(); + + private EnvironmentUtils() {} + + private static boolean isUnitTesting() { + for (StackTraceElement element : Thread.currentThread().getStackTrace()) { + if (element.getClassName().startsWith("org.junit.")) { + return true; + } + } + return false; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java index c8423c3be..87ed8af02 100644 --- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java @@ -100,6 +100,18 @@ public class FileUtils { return file; } + /** + * Open the specified file or copy if from resources + * + * @param file File to open + * @param name Name of the resource get if needed + * @return File handle of the specified file + * @throws IOException if the file failed to copy from resource + */ + public static File fileOrCopiedFromResource(File file, String name, GeyserBootstrap bootstrap) throws IOException { + return fileOrCopiedFromResource(file, name, Function.identity(), bootstrap); + } + /** * Writes the given data to the specified file on disk * diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 48ade52e2..a0bd5a4c7 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -32,6 +32,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerHotbarPacket; @@ -57,6 +59,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; 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.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import org.jetbrains.annotations.Contract; @@ -91,7 +94,7 @@ public class InventoryUtils { public static void displayInventory(GeyserSession session, Inventory inventory) { InventoryTranslator translator = session.getInventoryTranslator(); - if (translator != null && translator.prepareInventory(session, inventory)) { + if (translator.prepareInventory(session, inventory)) { if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { session.scheduleInEventLoop(() -> { Inventory openInv = session.getOpenInventory(); @@ -110,7 +113,11 @@ public class InventoryUtils { inventory.setDisplayed(true); } } else { + // Can occur if we e.g. did not find a spot to put a fake container in + ServerboundContainerClosePacket closePacket = new ServerboundContainerClosePacket(inventory.getJavaId()); + session.sendDownstreamGamePacket(closePacket); session.setOpenInventory(null); + session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); } } @@ -154,7 +161,7 @@ public class InventoryUtils { @Nullable public static Vector3i findAvailableWorldSpace(GeyserSession session) { // Check if a fake block can be placed, either above the player or beneath. - BedrockDimension dimension = session.getChunkCache().getBedrockDimension(); + BedrockDimension dimension = session.getBedrockDimension(); int minY = dimension.minY(), maxY = minY + dimension.height(); Vector3i flatPlayerPosition = session.getPlayerEntity().getPosition().toInt(); Vector3i position = flatPlayerPosition.add(Vector3i.UP); @@ -175,6 +182,7 @@ public class InventoryUtils { cursorPacket.setContainerId(ContainerId.UI); cursorPacket.setSlot(0); cursorPacket.setItem(session.getPlayerInventory().getCursor().getItemData(session)); + cursorPacket.setContainerNameData(new FullContainerName(ContainerSlotType.ANVIL_INPUT, null)); session.sendUpstreamPacket(cursorPacket); } @@ -248,6 +256,12 @@ public class InventoryUtils { .count(1).build(); } + public static IntFunction getTotemOfUndying() { + return protocolVersion -> ItemData.builder() + .definition(Registries.ITEMS.forVersion(protocolVersion).getStoredItems().totem().getBedrockDefinition()) + .count(1).build(); + } + /** * See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}. * diff --git a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java index 478a6ef96..d3024be65 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -28,7 +28,7 @@ package org.geysermc.geyser.util; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket; import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult; @@ -203,7 +203,7 @@ public class LoginEncryptionUtils { /** * Shows the code that a user must input into their browser */ - public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) { + public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, StepMsaDeviceCode.MsaDeviceCode msCode) { String locale = session.locale(); StringBuilder message = new StringBuilder("%xbox.signin.website\n") @@ -212,7 +212,7 @@ public class LoginEncryptionUtils { .append(ChatColor.RESET) .append("\n%xbox.signin.enterCode\n") .append(ChatColor.GREEN) - .append(msCode.user_code); + .append(msCode.getUserCode()); int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout(); if (timeout != 0) { message.append("\n\n") diff --git a/core/src/main/java/org/geysermc/geyser/util/MinecraftAuthLogger.java b/core/src/main/java/org/geysermc/geyser/util/MinecraftAuthLogger.java new file mode 100644 index 000000000..4e928d47e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/MinecraftAuthLogger.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +import net.raphimc.minecraftauth.util.logging.ILogger; +import org.geysermc.geyser.GeyserImpl; + +public class MinecraftAuthLogger implements ILogger { + + public static final MinecraftAuthLogger INSTANCE = new MinecraftAuthLogger(); + + @Override + public void info(String message) { + GeyserImpl.getInstance().getLogger().debug(message); + } + + @Override + public void warn(String message) { + GeyserImpl.getInstance().getLogger().warning(message); + } + + @Override + public void error(String message) { + GeyserImpl.getInstance().getLogger().error(message); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java index ed97408b9..cb6ad6f0c 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -25,14 +25,17 @@ package org.geysermc.geyser.util; +import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket; import org.geysermc.cumulus.component.DropdownComponent; import org.geysermc.cumulus.form.CustomForm; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Permissions; import org.geysermc.geyser.level.GameRule; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; public class SettingsUtils { /** @@ -79,7 +82,7 @@ public class SettingsUtils { } } - boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules"); + boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission(Permissions.SETTINGS_GAMERULES); if (showGamerules) { builder.label("geyser.settings.title.game_rules") .translator(MinecraftLocale::getLocaleString); // we need translate gamerules next @@ -96,6 +99,7 @@ public class SettingsUtils { } builder.validResultHandler((response) -> { + applyDifficultyFix(session); if (showClientSettings) { // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. if (showCoordinates) { @@ -134,9 +138,21 @@ public class SettingsUtils { } }); + builder.closedOrInvalidResultHandler($ -> applyDifficultyFix(session)); + return builder.build(); } + private static void applyDifficultyFix(GeyserSession session) { + // Peaceful difficulty allows always eating food - hence, we just do not send it to Bedrock. + // Since we sent the real difficulty before opening the server settings form, let's restore it to our workaround here + if (session.getWorldCache().getDifficulty() == Difficulty.PEACEFUL) { + SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket(); + setDifficultyPacket.setDifficulty(Difficulty.EASY.ordinal()); + session.sendUpstreamPacket(setDifficultyPacket); + } + } + private static String translateEntry(String key, String locale) { if (key.startsWith("%")) { // Bedrock will translate diff --git a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java index 72fcb4fa6..9847c0cfc 100644 --- a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java @@ -107,7 +107,7 @@ public class StatisticsUtils { for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof BreakItemStatistic statistic) { - String item = itemRegistry.get(statistic.getId()).javaIdentifier(); + Item item = itemRegistry.get(statistic.getId()); content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } @@ -117,7 +117,7 @@ public class StatisticsUtils { for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof CraftItemStatistic statistic) { - String item = itemRegistry.get(statistic.getId()).javaIdentifier(); + Item item = itemRegistry.get(statistic.getId()); content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } @@ -127,7 +127,7 @@ public class StatisticsUtils { for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof UseItemStatistic statistic) { - String item = itemRegistry.get(statistic.getId()).javaIdentifier(); + Item item = itemRegistry.get(statistic.getId()); content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } @@ -137,7 +137,7 @@ public class StatisticsUtils { for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof PickupItemStatistic statistic) { - String item = itemRegistry.get(statistic.getId()).javaIdentifier(); + Item item = itemRegistry.get(statistic.getId()); content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } @@ -147,7 +147,7 @@ public class StatisticsUtils { for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof DropItemStatistic statistic) { - String item = itemRegistry.get(statistic.getId()).javaIdentifier(); + Item item = itemRegistry.get(statistic.getId()); content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } @@ -208,14 +208,8 @@ public class StatisticsUtils { * @param language the language to search in * @return the full name of the item */ - private static String getItemTranslateKey(String item, String language) { - item = item.replace("minecraft:", "item.minecraft."); - String translatedItem = MinecraftLocale.getLocaleString(item, language); - if (translatedItem.equals(item)) { - // Didn't translate; must be a block - translatedItem = MinecraftLocale.getLocaleString(item.replace("item.", "block."), language); - } - return translatedItem; + private static String getItemTranslateKey(Item item, String language) { + return MinecraftLocale.getLocaleString(item.translationKey(), language); } private static String translate(String keys, String locale) { diff --git a/core/src/main/resources/bedrock/biome_definitions.dat b/core/src/main/resources/bedrock/biome_definitions.dat index dfee570e4..3ae94a5c8 100644 Binary files a/core/src/main/resources/bedrock/biome_definitions.dat and b/core/src/main/resources/bedrock/biome_definitions.dat differ diff --git a/core/src/main/resources/bedrock/block_palette.1_21_20.nbt b/core/src/main/resources/bedrock/block_palette.1_21_20.nbt new file mode 100644 index 000000000..521ea3cc6 Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_21_20.nbt differ diff --git a/core/src/main/resources/bedrock/block_palette.1_21_30.nbt b/core/src/main/resources/bedrock/block_palette.1_21_30.nbt new file mode 100644 index 000000000..bb19c11ca Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_21_30.nbt differ diff --git a/core/src/main/resources/bedrock/creative_items.1_21_20.json b/core/src/main/resources/bedrock/creative_items.1_21_20.json new file mode 100644 index 000000000..ce008aa44 --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_21_20.json @@ -0,0 +1,6214 @@ +{ + "items": [ + { + "id": "minecraft:oak_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19wbGFua3MECQBuYW1lX2hhc2ilMDLR92rQ4wMKAG5ldHdvcmtfaWS2GotyCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAwAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9wbGFua3MECQBuYW1lX2hhc2iumBkmFGFE8gMKAG5ldHdvcmtfaWSo8TFgCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX3BsYW5rcwQJAG5hbWVfaGFzaLrrAKJqV2WFAwoAbmV0d29ya19pZL+e3ZAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAwAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9wbGFua3MECQBuYW1lX2hhc2iBM3k4T3FAugMKAG5ldHdvcmtfaWSXUmBCCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAwAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9wbGFua3MECQBuYW1lX2hhc2g60edJxO5/aAMKAG5ldHdvcmtfaWTUXozECgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX3BsYW5rcwQJAG5hbWVfaGFzaAr64wkQ9cA7AwoAbmV0d29ya19pZFbMeR0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX3BsYW5rcwQJAG5hbWVfaGFzaPvLtcEA0F8xAwoAbmV0d29ya19pZEvnlCYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9wbGFua3MECQBuYW1lX2hhc2hNIvVh/lVW7gMKAG5ldHdvcmtfaWQTXpRoCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19wbGFua3MECQBuYW1lX2hhc2gYnjNz7SCCjgMKAG5ldHdvcmtfaWTi8ySSCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_mosaic", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT8AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19tb3NhaWMECQBuYW1lX2hhc2izSEgiMKOp/AMKAG5ldHdvcmtfaWQZ/p8xCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:crimson_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fcGxhbmtzBAkAbmFtZV9oYXNoJc5IKqNXJnwDCgBuZXR3b3JrX2lkwtJDdQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTyAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9wbGFua3MECQBuYW1lX2hhc2g3yGXEWhe6LgMKAG5ldHdvcmtfaWStTABvCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWSE4JosCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCwBjb2JibGVzdG9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWTUvV6XCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlEQBtb3NzeV9jb2JibGVzdG9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWT4opb2CgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlBwBncmFuaXRlCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWQAMQTVCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlBwBkaW9yaXRlCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWQIbDOcCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCABhbmRlc2l0ZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWSZKhusCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCQBzYW5kc3RvbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWSp4zgCCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlDQByZWRfc2FuZHN0b25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWRbqVHTCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCwBzdG9uZV9icmljawgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWRr0ZT/CgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlEQBtb3NzeV9zdG9uZV9icmljawgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWRnLis3CgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlBQBicmljawgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWQNLzfSCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlDABuZXRoZXJfYnJpY2sIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWQ5h0xwCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlEAByZWRfbmV0aGVyX2JyaWNrCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWS9J0B2CgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCQBlbmRfYnJpY2sIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWRPbkJeCgYAc3RhdGVzCA8Ad2FsbF9ibG9ja190eXBlCgBwcmlzbWFyaW5lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blackstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAgAACAQAbmFtZRkAbWluZWNyYWZ0OmJsYWNrc3RvbmVfd2FsbAQJAG5hbWVfaGFzaMP8XppUSU1RAwoAbmV0d29ya19pZMbeBBsKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAgAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfd2FsbAQJAG5hbWVfaGFzaP6SwV08YwzAAwoAbmV0d29ya19pZAJLsz8KBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQVAgAACAQAbmFtZSgAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfd2FsbAQJAG5hbWVfaGFzaBBIDZbHxiEzAwoAbmV0d29ya19pZEbLV8cKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobbled_deepslate_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AgAACAQAbmFtZSAAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3dhbGwECQBuYW1lX2hhc2iECY5oKxeT+gMKAG5ldHdvcmtfaWRCnPrFCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_tile_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3dhbGwECQBuYW1lX2hhc2jz7N+PeuEXgQMKAG5ldHdvcmtfaWTqw4s4CgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:polished_deepslate_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAgAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV93YWxsBAkAbmFtZV9oYXNoHxjTdj9pevMDCgBuZXR3b3JrX2lkIvBYYwoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSJAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja193YWxsBAkAbmFtZV9oYXNoEs3EQrjroyEDCgBuZXR3b3JrX2lkwlrCGwoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:mud_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAgAACAQAbmFtZRgAbWluZWNyYWZ0Om11ZF9icmlja193YWxsBAkAbmFtZV9oYXNov9b98ATpUSwDCgBuZXR3b3JrX2lkH/1WZQoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oak_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAAAACAQAbmFtZRMAbWluZWNyYWZ0Om9ha19mZW5jZQQJAG5hbWVfaGFzaGEmid7AaCWRAwoAbmV0d29ya19pZDvPEXcKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:spruce_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAwAACAQAbmFtZRYAbWluZWNyYWZ0OnNwcnVjZV9mZW5jZQQJAG5hbWVfaGFzaPQCm+aX1ZQeAwoAbmV0d29ya19pZD1QUEoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:birch_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/AwAACAQAbmFtZRUAbWluZWNyYWZ0OmJpcmNoX2ZlbmNlBAkAbmFtZV9oYXNo6CJ2ATpANfgDCgBuZXR3b3JrX2lkmCUV2QoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:jungle_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBAwAACAQAbmFtZRYAbWluZWNyYWZ0Omp1bmdsZV9mZW5jZQQJAG5hbWVfaGFzaOX4cD9uAmsdAwoAbmV0d29ya19pZHz1VxkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:acacia_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+AwAACAQAbmFtZRYAbWluZWNyYWZ0OmFjYWNpYV9mZW5jZQQJAG5hbWVfaGFzaGjn+RlKVDH6AwoAbmV0d29ya19pZNVGubwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dark_oak_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRAAwAACAQAbmFtZRgAbWluZWNyYWZ0OmRhcmtfb2FrX2ZlbmNlBAkAbmFtZV9oYXNoGPj0gCgM0c0DCgBuZXR3b3JrX2lk2w+gEwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mangrove_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hbmdyb3ZlX2ZlbmNlBAkAbmFtZV9oYXNowwAd7tPu9bsDCgBuZXR3b3JrX2lkKEcd0goGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cherry_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAwAACAQAbmFtZRYAbWluZWNyYWZ0OmNoZXJyeV9mZW5jZQQJAG5hbWVfaGFzaFmtUfHfTxcxAwoAbmV0d29ya19pZPCBxAIKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bamboo_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJhbWJvb19mZW5jZQQJAG5hbWVfaGFzaCKRbxfXsfkiAwoAbmV0d29ya19pZJNXKFcKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:nether_brick_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRxAAAACAQAbmFtZRwAbWluZWNyYWZ0Om5ldGhlcl9icmlja19mZW5jZQQJAG5hbWVfaGFzaA6030ngawxcAwoAbmV0d29ya19pZLnjLF4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AQAACAQAbmFtZRcAbWluZWNyYWZ0OmNyaW1zb25fZmVuY2UECQBuYW1lX2hhc2jhUhKv1HGj9AMKAG5ldHdvcmtfaWR3OH3OCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:warped_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQAAgAACAQAbmFtZRYAbWluZWNyYWZ0OndhcnBlZF9mZW5jZQQJAG5hbWVfaGFzaJfb3/YuKmOWAwoAbmV0d29ya19pZCpaGC8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAAAACAQAbmFtZRQAbWluZWNyYWZ0OmZlbmNlX2dhdGUECQBuYW1lX2hhc2hTxpjEDmRzAwMKAG5ldHdvcmtfaWR+T9kTCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:spruce_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS3AAAACAQAbmFtZRsAbWluZWNyYWZ0OnNwcnVjZV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoanTVB84HRbkDCgBuZXR3b3JrX2lkEnw5egoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:birch_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4AAAACAQAbmFtZRoAbWluZWNyYWZ0OmJpcmNoX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2jmfPklI8azSwMKAG5ldHdvcmtfaWQL77/BCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:jungle_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS5AAAACAQAbmFtZRsAbWluZWNyYWZ0Omp1bmdsZV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNobYVQkfBomIcDCgBuZXR3b3JrX2lkA1zgtgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:acacia_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS7AAAACAQAbmFtZRsAbWluZWNyYWZ0OmFjYWNpYV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoZnrLUx/XSekDCgBuZXR3b3JrX2lkHg/kTgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dark_oak_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS6AAAACAQAbmFtZR0AbWluZWNyYWZ0OmRhcmtfb2FrX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2j2PTvdJJHcVQMKAG5ldHdvcmtfaWTwjOCeCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mangrove_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAgAACAQAbmFtZR0AbWluZWNyYWZ0Om1hbmdyb3ZlX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2i/kOhBKiI/dAMKAG5ldHdvcmtfaWSfweCSCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cherry_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAwAACAQAbmFtZRsAbWluZWNyYWZ0OmNoZXJyeV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoKWLgCk0z+PsDCgBuZXR3b3JrX2lk/9bTZQoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bamboo_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJhbWJvb19mZW5jZV9nYXRlBAkAbmFtZV9oYXNopH1JrUgwdIADCgBuZXR3b3JrX2lkzIpPywoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAgAACAQAbmFtZRwAbWluZWNyYWZ0OmNyaW1zb25fZmVuY2VfZ2F0ZQQJAG5hbWVfaGFzaHE3Gfd0Z2d2AwoAbmV0d29ya19pZDQzVbEKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQsAaW5fd2FsbF9iaXQAAQgAb3Blbl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:warped_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAgAACAQAbmFtZRsAbWluZWNyYWZ0OndhcnBlZF9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoy0oIBjDIG4kDCgBuZXR3b3JrX2lkkf+/3QoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:normal_stone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzAQAACAQAbmFtZR0AbWluZWNyYWZ0Om5vcm1hbF9zdG9uZV9zdGFpcnMECQBuYW1lX2hhc2hAEktZZOkGIwMKAG5ldHdvcmtfaWQeH1ALCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX3N0YWlycwQJAG5hbWVfaGFzaNRjqVC5GRVDAwoAbmV0d29ya19pZDcCv+MKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mossy_cobblestone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSyAQAACAQAbmFtZSIAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lX3N0YWlycwQJAG5hbWVfaGFzaMVSTq5z9n1RAwoAbmV0d29ya19pZFIfrhkKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:oak_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1AAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19zdGFpcnMECQBuYW1lX2hhc2jk/HFzdXy0FQMKAG5ldHdvcmtfaWQJjyzBCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAAAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9zdGFpcnMECQBuYW1lX2hhc2iznygw7uBPBQMKAG5ldHdvcmtfaWTv+is3CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAAAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX3N0YWlycwQJAG5hbWVfaGFzaPfhbL619a3GAwoAbmV0d29ya19pZFyPlHAKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAAAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9zdGFpcnMECQBuYW1lX2hhc2jodJsHUbOVxQMKAG5ldHdvcmtfaWR0z5d4CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAAAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9zdGFpcnMECQBuYW1lX2hhc2h3x1NmD43IqQMKAG5ldHdvcmtfaWS7Jwz6CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX3N0YWlycwQJAG5hbWVfaGFzaMfwkbYPbNmAAwoAbmV0d29ya19pZCmBYKAKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX3N0YWlycwQJAG5hbWVfaGFzaNpUDY+uGMpyAwoAbmV0d29ya19pZChzUAsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9zdGFpcnMECQBuYW1lX2hhc2jMtr0v9JY4zwMKAG5ldHdvcmtfaWRQwq31CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19zdGFpcnMECQBuYW1lX2hhc2jFOzWL8PalKwMKAG5ldHdvcmtfaWTVPh42CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_mosaic_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKAwAACAQAbmFtZR4AbWluZWNyYWZ0OmJhbWJvb19tb3NhaWNfc3RhaXJzBAkAbmFtZV9oYXNoNLPiveSHPaoDCgBuZXR3b3JrX2lk44PHjgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAAAACAQAbmFtZRwAbWluZWNyYWZ0OnN0b25lX2JyaWNrX3N0YWlycwQJAG5hbWVfaGFzaN6tQViRo5cwAwoAbmV0d29ya19pZDMyMgIKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mossy_stone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAQAACAQAbmFtZSIAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrX3N0YWlycwQJAG5hbWVfaGFzaIB/Zv5YBPuYAwoAbmV0d29ya19pZANTOsMKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAAAACAQAbmFtZRoAbWluZWNyYWZ0OnNhbmRzdG9uZV9zdGFpcnMECQBuYW1lX2hhc2hOyA0BoYUOPQMKAG5ldHdvcmtfaWSV/834CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:smooth_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSwAQAACAQAbmFtZSEAbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNoB+CuCd8Ruz8DCgBuZXR3b3JrX2lksR+m8QoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:red_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS0AAAACAQAbmFtZR4AbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNoPs0LpHPL24YDCgBuZXR3b3JrX2lkLYVt3woGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvAQAACAQAbmFtZSUAbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lX3N0YWlycwQJAG5hbWVfaGFzaBvjtQv5pf+MAwoAbmV0d29ya19pZMHNND8KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:granite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAQAACAQAbmFtZRgAbWluZWNyYWZ0OmdyYW5pdGVfc3RhaXJzBAkAbmFtZV9oYXNoGzpvtoqKQjgDCgBuZXR3b3JrX2lkPkcB1goGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_granite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAQAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGVfc3RhaXJzBAkAbmFtZV9oYXNo3PvbSfEQklIDCgBuZXR3b3JrX2lkMmEm3AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:diorite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAQAACAQAbmFtZRgAbWluZWNyYWZ0OmRpb3JpdGVfc3RhaXJzBAkAbmFtZV9oYXNoi73T8VQuZmcDCgBuZXR3b3JrX2lk6i6nBQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_diorite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAQAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGVfc3RhaXJzBAkAbmFtZV9oYXNoFKRJd5Wk5L0DCgBuZXR3b3JrX2lkbt2ioAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:andesite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAQAACAQAbmFtZRkAbWluZWNyYWZ0OmFuZGVzaXRlX3N0YWlycwQJAG5hbWVfaGFzaO5w2FKBw76EAwoAbmV0d29ya19pZKhXEgUKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:polished_andesite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAQAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlX3N0YWlycwQJAG5hbWVfaGFzaNcZZ/zmLInIAwoAbmV0d29ya19pZJTHrlEKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAAAACAQAbmFtZRYAbWluZWNyYWZ0OmJyaWNrX3N0YWlycwQJAG5hbWVfaGFzaMyt+cRDk5O2AwoAbmV0d29ya19pZNeMh58KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:nether_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRyAAAACAQAbmFtZR0AbWluZWNyYWZ0Om5ldGhlcl9icmlja19zdGFpcnMECQBuYW1lX2hhc2jRqIoOXgifBAMKAG5ldHdvcmtfaWQDiw5yCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_nether_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS3AQAACAQAbmFtZSEAbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNogQvosSbcj7kDCgBuZXR3b3JrX2lkx2IMtAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:end_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSxAQAACAQAbmFtZRoAbWluZWNyYWZ0OmVuZF9icmlja19zdGFpcnMECQBuYW1lX2hhc2hmlAk+QhsUsQMKAG5ldHdvcmtfaWTN7KFaCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:quartz_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAAAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9zdGFpcnMECQBuYW1lX2hhc2hmvpvOqGi6egMKAG5ldHdvcmtfaWRmUTh7CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:smooth_quartz_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4AQAACAQAbmFtZR4AbWluZWNyYWZ0OnNtb290aF9xdWFydHpfc3RhaXJzBAkAbmFtZV9oYXNoNZZ9rX0qZOsDCgBuZXR3b3JrX2lkzsgQyQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:purpur_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAAAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnB1cl9zdGFpcnMECQBuYW1lX2hhc2ifwDxeezXD7gMKAG5ldHdvcmtfaWTT+rxiCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:prismarine_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAQAACAQAbmFtZRsAbWluZWNyYWZ0OnByaXNtYXJpbmVfc3RhaXJzBAkAbmFtZV9oYXNooTHSZ+IrYtcDCgBuZXR3b3JrX2lkxTJfeAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dark_prismarine_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAQAACAQAbmFtZSAAbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZV9zdGFpcnMECQBuYW1lX2hhc2hIciLmam4o4AMKAG5ldHdvcmtfaWTVu7TCCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:prismarine_bricks_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAQAACAQAbmFtZSIAbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tzX3N0YWlycwQJAG5hbWVfaGFzaNIjq1oBlZMMAwoAbmV0d29ya19pZGEFwLYKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fc3RhaXJzBAkAbmFtZV9oYXNoZJqIzCBpCq4DCgBuZXR3b3JrX2lktXE00AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9zdGFpcnMECQBuYW1lX2hhc2hOkY27jLD4RQMKAG5ldHdvcmtfaWQ+E5VrCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blackstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAgAACAQAbmFtZRsAbWluZWNyYWZ0OmJsYWNrc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNokdoUb76p9McDCgBuZXR3b3JrX2lk5fWI5goGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNolCFtFIE8MmADCgBuZXR3b3JrX2lkGTf7sgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQSAgAACAQAbmFtZSoAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNonks6UlfpOmkDCgBuZXR3b3JrX2lkgYeOdAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAgAACAQAbmFtZRsAbWluZWNyYWZ0OmN1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoHfoAXYq5G3MDCgBuZXR3b3JrX2lkeetf7woGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:exposed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAgAACAQAbmFtZSMAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcl9zdGFpcnMECQBuYW1lX2hhc2howneQGtZ9cgMKAG5ldHdvcmtfaWSg73zdCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:weathered_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAgAACAQAbmFtZSUAbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyX3N0YWlycwQJAG5hbWVfaGFzaP+R5loXxrVgAwoAbmV0d29ya19pZOnbRf4KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:oxidized_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAgAACAQAbmFtZSQAbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNo6Jeoq5rsPxsDCgBuZXR3b3JrX2lkmRjDnQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAgAACAQAbmFtZSEAbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoh07CQj0/SR8DCgBuZXR3b3JrX2lkmYqoqAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_exposed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAgAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcl9zdGFpcnMECQBuYW1lX2hhc2guVct1ilmxTwMKAG5ldHdvcmtfaWQgCPROCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_weathered_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAgAACAQAbmFtZSsAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyX3N0YWlycwQJAG5hbWVfaGFzaPXC8Sz/phCpAwoAbmV0d29ya19pZHlwHVsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS/AgAACAQAbmFtZSoAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoaqGdkuhxVZUDCgBuZXR3b3JrX2lkYQXzzgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobbled_deepslate_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8AgAACAQAbmFtZSIAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3N0YWlycwQJAG5hbWVfaGFzaPIfa+TpyJcIAwoAbmV0d29ya19pZJUvOYIKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_tile_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3N0YWlycwQJAG5hbWVfaGFzaGFRFzB72mN2AwoAbmV0d29ya19pZJEOgIsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:polished_deepslate_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAgAACAQAbmFtZSMAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV9zdGFpcnMECQBuYW1lX2hhc2iNCYxVik9sGAMKAG5ldHdvcmtfaWSRVPnYCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAgAACAQAbmFtZSAAbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja19zdGFpcnMECQBuYW1lX2hhc2hIasOahEf83wMKAG5ldHdvcmtfaWQ1qEDCCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:mud_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAgAACAQAbmFtZRoAbWluZWNyYWZ0Om11ZF9icmlja19zdGFpcnMECQBuYW1lX2hhc2gt3qxK1NWajAMKAG5ldHdvcmtfaWSm9N3MCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:wooden_door" + }, + { + "id": "minecraft:spruce_door" + }, + { + "id": "minecraft:birch_door" + }, + { + "id": "minecraft:jungle_door" + }, + { + "id": "minecraft:acacia_door" + }, + { + "id": "minecraft:dark_oak_door" + }, + { + "id": "minecraft:mangrove_door" + }, + { + "id": "minecraft:cherry_door" + }, + { + "id": "minecraft:bamboo_door" + }, + { + "id": "minecraft:iron_door" + }, + { + "id": "minecraft:crimson_door" + }, + { + "id": "minecraft:warped_door" + }, + { + "id": "minecraft:trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAAAACAQAbmFtZRIAbWluZWNyYWZ0OnRyYXBkb29yBAkAbmFtZV9oYXNotYiAJGtN0xADCgBuZXR3b3JrX2lkyTAWkAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAQAACAQAbmFtZRkAbWluZWNyYWZ0OnNwcnVjZV90cmFwZG9vcgQJAG5hbWVfaGFzaOwlfbgBkUW4AwoAbmV0d29ya19pZPHy1K0KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:birch_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAQAACAQAbmFtZRgAbWluZWNyYWZ0OmJpcmNoX3RyYXBkb29yBAkAbmFtZV9oYXNoSLtLweOLJ7wDCgBuZXR3b3JrX2lkeJWDfgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:jungle_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSTAQAACAQAbmFtZRkAbWluZWNyYWZ0Omp1bmdsZV90cmFwZG9vcgQJAG5hbWVfaGFzaDP/TnM9wyCIAwoAbmV0d29ya19pZEy2fJoKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:acacia_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAQAACAQAbmFtZRkAbWluZWNyYWZ0OmFjYWNpYV90cmFwZG9vcgQJAG5hbWVfaGFzaMj8xi3vmEKOAwoAbmV0d29ya19pZOHj8E8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dark_oak_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAQAACAQAbmFtZRsAbWluZWNyYWZ0OmRhcmtfb2FrX3RyYXBkb29yBAkAbmFtZV9oYXNomB2GGJQ2aOMDCgBuZXR3b3JrX2lko5ZHTwoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:mangrove_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAgAACAQAbmFtZRsAbWluZWNyYWZ0Om1hbmdyb3ZlX3RyYXBkb29yBAkAbmFtZV9oYXNooV3kQsQUUmkDCgBuZXR3b3JrX2lkkF/mxAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cherry_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAwAACAQAbmFtZRkAbWluZWNyYWZ0OmNoZXJyeV90cmFwZG9vcgQJAG5hbWVfaGFzaH/PefpfdHgtAwoAbmV0d29ya19pZOA7eNgKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:bamboo_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAwAACAQAbmFtZRkAbWluZWNyYWZ0OmJhbWJvb190cmFwZG9vcgQJAG5hbWVfaGFzaJrEOpsTwtKCAwoAbmV0d29ya19pZLvbPz8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:iron_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAAAACAQAbmFtZRcAbWluZWNyYWZ0Omlyb25fdHJhcGRvb3IECQBuYW1lX2hhc2gwA+IumsEiGQMKAG5ldHdvcmtfaWTvSVl/CgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT1AQAACAQAbmFtZRoAbWluZWNyYWZ0OmNyaW1zb25fdHJhcGRvb3IECQBuYW1lX2hhc2jHXufTnwUkYgMKAG5ldHdvcmtfaWQLjMYVCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:warped_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT2AQAACAQAbmFtZRkAbWluZWNyYWZ0OndhcnBlZF90cmFwZG9vcgQJAG5hbWVfaGFzaA20wG/+vkd6AwoAbmV0d29ya19pZHKR/hYKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:iron_bars", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAAAACAQAbmFtZRMAbWluZWNyYWZ0Omlyb25fYmFycwQJAG5hbWVfaGFzaPuefWSNAe56AwoAbmV0d29ya19pZN2LB5IKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmdsYXNzBAkAbmFtZV9oYXNowGJByfWff6gDCgBuZXR3b3JrX2lk0hdLNwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:white_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAAAACAQAbmFtZR0AbWluZWNyYWZ0OndoaXRlX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2iHubqoMbu9fAMKAG5ldHdvcmtfaWRndBrUCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:light_gray_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAwAACAQAbmFtZSIAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaKKa+LrRsHQhAwoAbmV0d29ya19pZEv2giYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:gray_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSmAwAACAQAbmFtZRwAbWluZWNyYWZ0OmdyYXlfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaIETy7Y/HZREAwoAbmV0d29ya19pZDomVrUKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:black_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAwAACAQAbmFtZR0AbWluZWNyYWZ0OmJsYWNrX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2iV6BCwpfDMmwMKAG5ldHdvcmtfaWSV7doJCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAwAACAQAbmFtZR0AbWluZWNyYWZ0OmJyb3duX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2igsEiq5np8JgMKAG5ldHdvcmtfaWRMzE/lCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAwAACAQAbmFtZRsAbWluZWNyYWZ0OnJlZF9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNoCa2J12/lQoIDCgBuZXR3b3JrX2lk283lWAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:orange_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAwAACAQAbmFtZR4AbWluZWNyYWZ0Om9yYW5nZV9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNozgjAuvzhxGsDCgBuZXR3b3JrX2lkW5CkhQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:yellow_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAwAACAQAbmFtZR4AbWluZWNyYWZ0OnllbGxvd19zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNo7EbHMd5WVugDCgBuZXR3b3JrX2lkkdDyXQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lime_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAwAACAQAbmFtZRwAbWluZWNyYWZ0OmxpbWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaBtZA1nZtwcFAwoAbmV0d29ya19pZDxX85UKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:green_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAwAACAQAbmFtZR0AbWluZWNyYWZ0OmdyZWVuX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2h91ptDgbehWwMKAG5ldHdvcmtfaWTlDhnECgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cyan_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAwAACAQAbmFtZRwAbWluZWNyYWZ0OmN5YW5fc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaBkIYQ8nQLqbAwoAbmV0d29ya19pZOL1lHsKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_blue_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAwAACAQAbmFtZSIAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaLt05n1G0fiSAwoAbmV0d29ya19pZNbwulIKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blue_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAwAACAQAbmFtZRwAbWluZWNyYWZ0OmJsdWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaPhLocSfzduRAwoAbmV0d29ya19pZENsjFwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:purple_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAwAACAQAbmFtZR4AbWluZWNyYWZ0OnB1cnBsZV9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNoJk0DhRO0szUDCgBuZXR3b3JrX2lkD98ZxgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:magenta_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAwAACAQAbmFtZR8AbWluZWNyYWZ0Om1hZ2VudGFfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaFEDeFiJj3zSAwoAbmV0d29ya19pZG+iFRoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:pink_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBpbmtfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaDijTX87ywxhAwoAbmV0d29ya19pZKdEricKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:tinted_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAgAACAQAbmFtZRYAbWluZWNyYWZ0OnRpbnRlZF9nbGFzcwQJAG5hbWVfaGFzaAFZWSamk6KdAwoAbmV0d29ya19pZGSvWX8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdsYXNzX3BhbmUECQBuYW1lX2hhc2gRSBHwNMQ4gQMKAG5ldHdvcmtfaWRGwixuCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:white_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAAAACAQAbmFtZSIAbWluZWNyYWZ0OndoaXRlX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaHgxQmgJVtRrAwoAbmV0d29ya19pZBEr/DYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_gray_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSJAwAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNon0aQw9lNkSEDCgBuZXR3b3JrX2lk9dp5VgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gray_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAwAACAQAbmFtZSEAbWluZWNyYWZ0OmdyYXlfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNors74IIw+2MMDCgBuZXR3b3JrX2lkmrGO5woGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:black_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAwAACAQAbmFtZSIAbWluZWNyYWZ0OmJsYWNrX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaOK/5ZRRd+M1AwoAbmV0d29ya19pZDv++oQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brown_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSNAwAACAQAbmFtZSIAbWluZWNyYWZ0OmJyb3duX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaLHeGJyRFTIWAwoAbmV0d29ya19pZMz9L0wKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAwAACAQAbmFtZSAAbWluZWNyYWZ0OnJlZF9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2gGr4x6JheAywMKAG5ldHdvcmtfaWQBjCTmCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:orange_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAwAACAQAbmFtZSMAbWluZWNyYWZ0Om9yYW5nZV9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2hbHxPD2gEbEAMKAG5ldHdvcmtfaWSt/7a5CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:yellow_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAwAACAQAbmFtZSMAbWluZWNyYWZ0OnllbGxvd19zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2g9tl4aOCyZBwMKAG5ldHdvcmtfaWTXRAS7CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lime_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAwAACAQAbmFtZSEAbWluZWNyYWZ0OmxpbWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNo3CtUyLwoGegDCgBuZXR3b3JrX2lkYJDnggoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:green_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSOAwAACAQAbmFtZSIAbWluZWNyYWZ0OmdyZWVuX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaJo6YP7IMy9SAwoAbmV0d29ya19pZHOnixoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cyan_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAwAACAQAbmFtZSEAbWluZWNyYWZ0OmN5YW5fc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoti97c6QrbLQDCgBuZXR3b3JrX2lkUqFUeQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_blue_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAwAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNovDg/gQle104DCgBuZXR3b3JrX2lkFuy4MQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:blue_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSMAwAACAQAbmFtZSEAbWluZWNyYWZ0OmJsdWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoGc57tiexbQMDCgBuZXR3b3JrX2lk1eBLUAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:purple_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAwAACAQAbmFtZSMAbWluZWNyYWZ0OnB1cnBsZV9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2hDJHYdd0FdfQMKAG5ldHdvcmtfaWSNsdK5CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:magenta_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAwAACAQAbmFtZSQAbWluZWNyYWZ0Om1hZ2VudGFfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNo3pcOw5bs5XoDCgBuZXR3b3JrX2lkVbOR7AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAwAACAQAbmFtZSEAbWluZWNyYWZ0OnBpbmtfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoWRhSACMWgswDCgBuZXR3b3JrX2lkIR92xwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:ladder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBAAAACAQAbmFtZRAAbWluZWNyYWZ0OmxhZGRlcgQJAG5hbWVfaGFzaKBhqheJVOz+AwoAbmV0d29ya19pZCgvzlsKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:scaffolding", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAQAACAQAbmFtZRUAbWluZWNyYWZ0OnNjYWZmb2xkaW5nBAkAbmFtZV9oYXNoYrkevrqcljwDCgBuZXR3b3JrX2lkD13mlAoGAHN0YXRlcwMJAHN0YWJpbGl0eQAAAAABDwBzdGFiaWxpdHlfY2hlY2sAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:smooth_stone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAAAACAQAbmFtZRsAbWluZWNyYWZ0OnNtb290aF9zdG9uZV9zbGFiBAkAbmFtZV9oYXNon5I1yVw74uMDCgBuZXR3b3JrX2lkqvjcBQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:normal_stone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCBAAACAQAbmFtZRsAbWluZWNyYWZ0Om5vcm1hbF9zdG9uZV9zbGFiBAkAbmFtZV9oYXNoIvsjJLQdolcDCgBuZXR3b3JrX2lkC1zqRQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoBAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3NsYWIECQBuYW1lX2hhc2h5CXtW7vlQVgMKAG5ldHdvcmtfaWRDGyj2CgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mossy_cobblestone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR3BAAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lX3NsYWIECQBuYW1lX2hhc2ijm1BCwB82VgMKAG5ldHdvcmtfaWR7ByMGCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oak_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ha19zbGFiBAkAbmFtZV9oYXNoJp1Cp1M4jlwDCgBuZXR3b3JrX2lkZH6+owoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:spruce_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjBAAACAQAbmFtZRUAbWluZWNyYWZ0OnNwcnVjZV9zbGFiBAkAbmFtZV9oYXNodQi70jB238cDCgBuZXR3b3JrX2lkrriOYQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:birch_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJpcmNoX3NsYWIECQBuYW1lX2hhc2gZPpfMxoOsTAMKAG5ldHdvcmtfaWThR9jyCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:jungle_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQlBAAACAQAbmFtZRUAbWluZWNyYWZ0Omp1bmdsZV9zbGFiBAkAbmFtZV9oYXNo6gLs79NXak4DCgBuZXR3b3JrX2lk5ZiKgwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:acacia_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmBAAACAQAbmFtZRUAbWluZWNyYWZ0OmFjYWNpYV9zbGFiBAkAbmFtZV9oYXNomSdFmDnv4OUDCgBuZXR3b3JrX2lkHttaXAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dark_oak_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnBAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhcmtfb2FrX3NsYWIECQBuYW1lX2hhc2hJjTohRFyhIQMKAG5ldHdvcmtfaWRMzDTyCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mangrove_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAgAACAQAbmFtZRcAbWluZWNyYWZ0Om1hbmdyb3ZlX3NsYWIECQBuYW1lX2hhc2jYCcmhJPeNMwMKAG5ldHdvcmtfaWQx6U1yCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cherry_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQaAwAACAQAbmFtZRUAbWluZWNyYWZ0OmNoZXJyeV9zbGFiBAkAbmFtZV9oYXNoTt0MmVn/mqoDCgBuZXR3b3JrX2lk2VVsZQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bamboo_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQAAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJhbWJvb19zbGFiBAkAbmFtZV9oYXNoo1xuFqINeLYDCgBuZXR3b3JrX2lkVC+0twoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bamboo_mosaic_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLAwAACAQAbmFtZRwAbWluZWNyYWZ0OmJhbWJvb19tb3NhaWNfc2xhYgQJAG5hbWVfaGFzaNbVRBZ/ChI3AwoAbmV0d29ya19pZOLZHFMKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqBAAACAQAbmFtZRoAbWluZWNyYWZ0OnN0b25lX2JyaWNrX3NsYWIECQBuYW1lX2hhc2js6EexuKuzrQMKAG5ldHdvcmtfaWRSsMxaCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mossy_stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAQAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrX3NsYWIECQBuYW1lX2hhc2hiA4kFUl4tHAMKAG5ldHdvcmtfaWS6joSOCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnBAAACAQAbmFtZRgAbWluZWNyYWZ0OnNhbmRzdG9uZV9zbGFiBAkAbmFtZV9oYXNo/GMI0MZnrhsDCgBuZXR3b3JrX2lkFP8WmwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cut_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDBAAACAQAbmFtZRwAbWluZWNyYWZ0OmN1dF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaE+zxVQweViJAwoAbmV0d29ya19pZHsu74YKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:smooth_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4BAAACAQAbmFtZR8AbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaIkmsO1gw3gnAwoAbmV0d29ya19pZFSiwP0KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS2AAAACAQAbmFtZRwAbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaEyDjeWlUHItAwoAbmV0d29ya19pZIT4rmwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cut_red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEBAAACAQAbmFtZSAAbWluZWNyYWZ0OmN1dF9yZWRfc2FuZHN0b25lX3NsYWIECQBuYW1lX2hhc2hTVRS++snU3wMKAG5ldHdvcmtfaWSvIAviCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6BAAACAQAbmFtZSMAbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lX3NsYWIECQBuYW1lX2hhc2i9iN2UK272tgMKAG5ldHdvcmtfaWRUZrwJCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:granite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/BAAACAQAbmFtZRYAbWluZWNyYWZ0OmdyYW5pdGVfc2xhYgQJAG5hbWVfaGFzaL0HprlAhhZwAwoAbmV0d29ya19pZIcIdc8KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_granite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSABAAACAQAbmFtZR8AbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGVfc2xhYgQJAG5hbWVfaGFzaP6bXk5w2dGrAwoAbmV0d29ya19pZCsRy1cKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:diorite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9BAAACAQAbmFtZRYAbWluZWNyYWZ0OmRpb3JpdGVfc2xhYgQJAG5hbWVfaGFzaM3ppS8v55sNAwoAbmV0d29ya19pZB+Pv9oKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_diorite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+BAAACAQAbmFtZR8AbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGVfc2xhYgQJAG5hbWVfaGFzaLZlyJLkMPhyAwoAbmV0d29ya19pZFM0HYwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:andesite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8BAAACAQAbmFtZRcAbWluZWNyYWZ0OmFuZGVzaXRlX3NsYWIECQBuYW1lX2hhc2icIrtuy/aosAMKAG5ldHdvcmtfaWTtXTtYCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_andesite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7BAAACAQAbmFtZSAAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlX3NsYWIECQBuYW1lX2hhc2j56zJOfCF+3wMKAG5ldHdvcmtfaWRBs69FCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJyaWNrX3NsYWIECQBuYW1lX2hhc2hO/Da4jU2v4wMKAG5ldHdvcmtfaWRG/qphCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:nether_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsBAAACAQAbmFtZRsAbWluZWNyYWZ0Om5ldGhlcl9icmlja19zbGFiBAkAbmFtZV9oYXNonymoa2zbbqMDCgBuZXR3b3JrX2lkquvR1QoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_nether_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5BAAACAQAbmFtZR8AbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaG89ujUk3Y64AwoAbmV0d29ya19pZEZIunAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:end_stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAQAACAQAbmFtZR4AbWluZWNyYWZ0OmVuZF9zdG9uZV9icmlja19zbGFiBAkAbmFtZV9oYXNo4tkxQtl+IyQDCgBuZXR3b3JrX2lkhByH/woGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:quartz_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrBAAACAQAbmFtZRUAbWluZWNyYWZ0OnF1YXJ0el9zbGFiBAkAbmFtZV9oYXNo9JMj3upfsbwDCgBuZXR3b3JrX2lkn2g2VAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:smooth_quartz_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBBAAACAQAbmFtZRwAbWluZWNyYWZ0OnNtb290aF9xdWFydHpfc2xhYgQJAG5hbWVfaGFzaHOSJv8ve0nmAwoAbmV0d29ya19pZFMk/JsKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:purpur_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRzBAAACAQAbmFtZRUAbWluZWNyYWZ0OnB1cnB1cl9zbGFiBAkAbmFtZV9oYXNo4XeWbKpx2ScDCgBuZXR3b3JrX2lkRkga5goGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:prismarine_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0BAAACAQAbmFtZRkAbWluZWNyYWZ0OnByaXNtYXJpbmVfc2xhYgQJAG5hbWVfaGFzaI9x+1fY8QRfAwoAbmV0d29ya19pZBTUZhwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_prismarine_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR1BAAACAQAbmFtZR4AbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZV9zbGFiBAkAbmFtZV9oYXNoSsZGDkEL5ZUDCgBuZXR3b3JrX2lkNLQ8VwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:prismarine_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR2BAAACAQAbmFtZR8AbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaB1FSbVi97xJAwoAbmV0d29ya19pZEBwwFMKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:crimson_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAgAACAQAbmFtZRYAbWluZWNyYWZ0OmNyaW1zb25fc2xhYgQJAG5hbWVfaGFzaKZ+EfP0ZYOZAwoAbmV0d29ya19pZAxRUWAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:warped_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIAgAACAQAbmFtZRUAbWluZWNyYWZ0OndhcnBlZF9zbGFiBAkAbmFtZV9oYXNo/AT0e/Z9W7UDCgBuZXR3b3JrX2lk1yq11AoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blackstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAgAACAQAbmFtZRkAbWluZWNyYWZ0OmJsYWNrc3RvbmVfc2xhYgQJAG5hbWVfaGFzaF/DD4ZUlNgtAwoAbmV0d29ya19pZGy1DjwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_blackstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkAgAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfc2xhYgQJAG5hbWVfaGFzaDYnuUs86EWfAwoAbmV0d29ya19pZJj2bXIKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_blackstone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbAgAACAQAbmFtZSgAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaKySLqvHc4xXAwoAbmV0d29ya19pZOyWX94KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoAgAACAQAbmFtZRkAbWluZWNyYWZ0OmN1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaDsNpb2qs4iBAwoAbmV0d29ya19pZOTm2nsKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:exposed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpAgAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcl9zbGFiBAkAbmFtZV9oYXNoahQ5OwIQb7kDCgBuZXR3b3JrX2lkrUlZLwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:weathered_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAgAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyX3NsYWIECQBuYW1lX2hhc2hBIuGIOVVXogMKAG5ldHdvcmtfaWQgnaDiCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oxidized_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAgAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaOptj9ycfpaDAwoAbmV0d29ya19pZMzFSRgKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAgAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaAlx6DZOCTHzAwoAbmV0d29ya19pZFRBvDAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_exposed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAgAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcl9zbGFiBAkAbmFtZV9oYXNo3KqS5OnhtRIDCgBuZXR3b3JrX2lkHTGcTgoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_weathered_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAgAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyX3NsYWIECQBuYW1lX2hhc2gzZ1oX0HCFtwMKAG5ldHdvcmtfaWSgJR+XCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTAAgAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaMjjTnLu1KcqAwoAbmV0d29ya19pZIxsnFYKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cobbled_deepslate_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AgAACAQAbmFtZSAAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3NsYWIECQBuYW1lX2hhc2gwJIVWK1TM2QMKAG5ldHdvcmtfaWTYAoX5CgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_deepslate_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/AgAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV9zbGFiBAkAbmFtZV9oYXNoC/Adiz8k6RYDCgBuZXR3b3JrX2lkuFYMAAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_tile_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3NsYWIECQBuYW1lX2hhc2hPydV6emzIXAMKAG5ldHdvcmtfaWQwlbFCCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:deepslate_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja19zbGFiBAkAbmFtZV9oYXNoSv62V7iw10UDCgBuZXR3b3JrX2lkWMoragoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mud_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAgAACAQAbmFtZRgAbWluZWNyYWZ0Om11ZF9icmlja19zbGFiBAkAbmFtZV9oYXNoq/tGBQWkv08DCgBuZXR3b3JrX2lkl4nnMwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brick_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAAAACAQAbmFtZRUAbWluZWNyYWZ0OmJyaWNrX2Jsb2NrBAkAbmFtZV9oYXNo5Qc2E005S3oDCgBuZXR3b3JrX2lkqeGWRgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chiseled_nether_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAgAACAQAbmFtZSAAbWluZWNyYWZ0OmNoaXNlbGVkX25ldGhlcl9icmlja3MECQBuYW1lX2hhc2g31SBPTcUK1QMKAG5ldHdvcmtfaWS8TJ+TCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cracked_nether_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAgAACAQAbmFtZR8AbWluZWNyYWZ0OmNyYWNrZWRfbmV0aGVyX2JyaWNrcwQJAG5hbWVfaGFzaAdC6eKzXT5tAwoAbmV0d29ya19pZIUSejwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:quartz_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAgAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9icmlja3MECQBuYW1lX2hhc2jSZO590dd8sAMKAG5ldHdvcmtfaWSc5xCLCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX2JyaWNrcwQJAG5hbWVfaGFzaGAiQu8VWVJRAwoAbmV0d29ya19pZH2DjXUKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mossy_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjBAAACAQAbmFtZRwAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaIZBO00MONRIAwoAbmV0d29ya19pZL2WDrAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cracked_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkBAAACAQAbmFtZR4AbWluZWNyYWZ0OmNyYWNrZWRfc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNocIkAp6riMz4DCgBuZXR3b3JrX2lkTWGeCwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chiseled_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlBAAACAQAbmFtZR8AbWluZWNyYWZ0OmNoaXNlbGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaMB2FPLLADkEAwoAbmV0d29ya19pZOIPn0IKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:end_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTOAAAACAQAbmFtZRQAbWluZWNyYWZ0OmVuZF9icmlja3MECQBuYW1lX2hhc2hIUFfxNLZaFgMKAG5ldHdvcmtfaWQ/vDihCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:prismarine_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzBAAACAQAbmFtZRsAbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tzBAkAbmFtZV9oYXNozeGe3/7s5fcDCgBuZXR3b3JrX2lkj/iBnAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNoIHgsgIdzKXcDCgBuZXR3b3JrX2lkUw9b3woGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cracked_polished_blackstone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAgAACAQAbmFtZSwAbWluZWNyYWZ0OmNyYWNrZWRfcG9saXNoZWRfYmxhY2tzdG9uZV9icmlja3MECQBuYW1lX2hhc2jQIO1GQDk80AMKAG5ldHdvcmtfaWQ3UlRYCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:gilded_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAgAACAQAbmFtZRsAbWluZWNyYWZ0OmdpbGRlZF9ibGFja3N0b25lBAkAbmFtZV9oYXNoNoWt1ocG0HEDCgBuZXR3b3JrX2lktL8gUwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chiseled_polished_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAgAACAQAbmFtZSYAbWluZWNyYWZ0OmNoaXNlbGVkX3BvbGlzaGVkX2JsYWNrc3RvbmUECQBuYW1lX2hhc2gzFa+kEjCJgAMKAG5ldHdvcmtfaWR2NJX2CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate_tiles", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAgAACAQAbmFtZRkAbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlcwQJAG5hbWVfaGFzaGcLLx3NXAFvAwoAbmV0d29ya19pZI/G/xYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cracked_deepslate_tiles", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAgAACAQAbmFtZSEAbWluZWNyYWZ0OmNyYWNrZWRfZGVlcHNsYXRlX3RpbGVzBAkAbmFtZV9oYXNo9zWgkFuMM1QDCgBuZXR3b3JrX2lkGwY6OgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:deepslate_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAgAACAQAbmFtZRoAbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja3MECQBuYW1lX2hhc2gucvFmPdZxigMKAG5ldHdvcmtfaWSH4HDPCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cracked_deepslate_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAgAACAQAbmFtZSIAbWluZWNyYWZ0OmNyYWNrZWRfZGVlcHNsYXRlX2JyaWNrcwQJAG5hbWVfaGFzaN40aqhh9WqHAwoAbmV0d29ya19pZO9GPBQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chiseled_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAgAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaEU7/uRG8HSBAwoAbmV0d29ya19pZEqmI0EKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvYmJsZXN0b25lBAkAbmFtZV9oYXNoPoK7mGlSUz4DCgBuZXR3b3JrX2lkLm7RZwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mossy_cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwAAAACAQAbmFtZRsAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lBAkAbmFtZV9oYXNoGJ67FCbkChMDCgBuZXR3b3JrX2lk/pYs1AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cobbled_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AgAACAQAbmFtZRsAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlBAkAbmFtZV9oYXNoLUz9Y/ywmLwDCgBuZXR3b3JrX2lkNwzZ+AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS2AQAACAQAbmFtZRYAbWluZWNyYWZ0OnNtb290aF9zdG9uZQQJAG5hbWVfaGFzaMwf87/JaTNvAwoAbmV0d29ya19pZLkZICEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAAAACAQAbmFtZRMAbWluZWNyYWZ0OnNhbmRzdG9uZQQJAG5hbWVfaGFzaFEmWsEHFI1AAwoAbmV0d29ya19pZPsXMaQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chiseled_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvBAAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX3NhbmRzdG9uZQQJAG5hbWVfaGFzaPEkxMvZmemgAwoAbmV0d29ya19pZGI5NB4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cut_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSwBAAACAQAbmFtZRcAbWluZWNyYWZ0OmN1dF9zYW5kc3RvbmUECQBuYW1lX2hhc2ichLQc71njnQMKAG5ldHdvcmtfaWSmBLkRCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:smooth_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSxBAAACAQAbmFtZRoAbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmUECQBuYW1lX2hhc2huR7XTwISyCAMKAG5ldHdvcmtfaWSzWj3UCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzAAAACAQAbmFtZRcAbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmUECQBuYW1lX2hhc2jBO4Gv2v59uAMKAG5ldHdvcmtfaWRXRYxZCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:chiseled_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS7BAAACAQAbmFtZSAAbWluZWNyYWZ0OmNoaXNlbGVkX3JlZF9zYW5kc3RvbmUECQBuYW1lX2hhc2gh5sX+ON054wMKAG5ldHdvcmtfaWT6Pw1PCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cut_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS8BAAACAQAbmFtZRsAbWluZWNyYWZ0OmN1dF9yZWRfc2FuZHN0b25lBAkAbmFtZV9oYXNoaOtka4NrQ1EDCgBuZXR3b3JrX2lk3r/JPAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS9BAAACAQAbmFtZR4AbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lBAkAbmFtZV9oYXNoqsNl8x36ju4DCgBuZXR3b3JrX2lk7x5DTwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:coal_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAAAACAQAbmFtZRQAbWluZWNyYWZ0OmNvYWxfYmxvY2sECQBuYW1lX2hhc2jH8QQP3t5PiAMKAG5ldHdvcmtfaWRo+sR+CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dried_kelp_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAQAACAQAbmFtZRoAbWluZWNyYWZ0OmRyaWVkX2tlbHBfYmxvY2sECQBuYW1lX2hhc2iRoucexkrl8wMKAG5ldHdvcmtfaWQQCCrvCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:gold_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdvbGRfYmxvY2sECQBuYW1lX2hhc2iYLshvjtXzFwMKAG5ldHdvcmtfaWTDJGBcCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:iron_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAAAACAQAbmFtZRQAbWluZWNyYWZ0Omlyb25fYmxvY2sECQBuYW1lX2hhc2jYINmJQbvV/gMKAG5ldHdvcmtfaWRf7AbICgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:copper_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTAgAACAQAbmFtZRYAbWluZWNyYWZ0OmNvcHBlcl9ibG9jawQJAG5hbWVfaGFzaDVxnehsGaZ1AwoAbmV0d29ya19pZIiUodwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:copper_door" + }, + { + "id": "minecraft:copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXBAAACAQAbmFtZRkAbWluZWNyYWZ0OmNvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaO9fXio+svKVAwoAbmV0d29ya19pZMCoRjEKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AwAACAQAbmFtZRYAbWluZWNyYWZ0OmNvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaC/JEFOWnmEcAwoAbmV0d29ya19pZC6YiiMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:exposed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAgAACAQAbmFtZRgAbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyBAkAbmFtZV9oYXNoQH3Fukmu3CEDCgBuZXR3b3JrX2lk72jFIwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:exposed_copper_door" + }, + { + "id": "minecraft:exposed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYBAAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX3RyYXBkb29yBAkAbmFtZV9oYXNoYhDFUysN7qUDCgBuZXR3b3JrX2lkMzwGJgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:exposed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQABAAACAQAbmFtZR4AbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX2dyYXRlBAkAbmFtZV9oYXNolFIBYLYU0IcDCgBuZXR3b3JrX2lk4UqptAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:weathered_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAgAACAQAbmFtZRoAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXIECQBuYW1lX2hhc2hJCQXbvobv+gMKAG5ldHdvcmtfaWQwM0lJCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:weathered_copper_door" + }, + { + "id": "minecraft:weathered_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZBAAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfdHJhcGRvb3IECQBuYW1lX2hhc2hFnEC282a1tgMKAG5ldHdvcmtfaWTk70oiCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:weathered_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBBAAACAQAbmFtZSAAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfZ3JhdGUECQBuYW1lX2hhc2jB3o8enlv1RgMKAG5ldHdvcmtfaWRih2pOCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oxidized_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAgAACAQAbmFtZRkAbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcgQJAG5hbWVfaGFzaMDtJqR0G5Y7AwoAbmV0d29ya19pZGjN8bUKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:oxidized_copper_door" + }, + { + "id": "minecraft:oxidized_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQaBAAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaOJpG/XFexVwAwoAbmV0d29ya19pZPhi0J4KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oxidized_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCBAAACAQAbmFtZR8AbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaBRfNhyndve7AwoAbmV0d29ya19pZKY2cnEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAgAACAQAbmFtZRYAbWluZWNyYWZ0OndheGVkX2NvcHBlcgQJAG5hbWVfaGFzaPF+FG6Eh5fsAwoAbmV0d29ya19pZIjtz/0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_copper_door" + }, + { + "id": "minecraft:waxed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbBAAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaO0JUKUHqNU6AwoAbmV0d29ya19pZJC3ZuMKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDBAAACAQAbmFtZRwAbWluZWNyYWZ0OndheGVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaDmC92M2RO+HAwoAbmV0d29ya19pZH4og2AKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_exposed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAgAACAQAbmFtZR4AbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyBAkAbmFtZV9oYXNoig8IOc+SCikDCgBuZXR3b3JrX2lklz8yWQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_exposed_copper_door" + }, + { + "id": "minecraft:waxed_exposed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcBAAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX3RyYXBkb29yBAkAbmFtZV9oYXNoBHHxCpkUzpgDCgBuZXR3b3JrX2lkw2XBGQoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_exposed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEBAAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX2dyYXRlBAkAbmFtZV9oYXNoWmd6B+hWwiEDCgBuZXR3b3JrX2lk8d4ZQwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_weathered_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAgAACAQAbmFtZSAAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXIECQBuYW1lX2hhc2gjtPq8MOdvKgMKAG5ldHdvcmtfaWSQ9Ln9CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_weathered_copper_door" + }, + { + "id": "minecraft:waxed_weathered_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQdBAAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfdHJhcGRvb3IECQBuYW1lX2hhc2gH9Fi3JCF4egMKAG5ldHdvcmtfaWRkGU6TCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_weathered_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFBAAACAQAbmFtZSYAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfZ3JhdGUECQBuYW1lX2hhc2hXfilVFDAiYQMKAG5ldHdvcmtfaWQqTGC1CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_oxidized_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS9AgAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcgQJAG5hbWVfaGFzaMaORhsO+LzjAwoAbmV0d29ya19pZJhGfLEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_oxidized_copper_door" + }, + { + "id": "minecraft:waxed_oxidized_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeBAAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaNA/q9qAy6Z9AwoAbmV0d29ya19pZDgExS8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_oxidized_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGBAAACAQAbmFtZSUAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaEbeMT605GP4AwoAbmV0d29ya19pZOZjpkkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaAgAACAQAbmFtZRQAbWluZWNyYWZ0OmN1dF9jb3BwZXIECQBuYW1lX2hhc2hAfN3NGax3eAMKAG5ldHdvcmtfaWTnFBtYCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:exposed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAgAACAQAbmFtZRwAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcgQJAG5hbWVfaGFzaA85G3yv/w6pAwoAbmV0d29ya19pZMQhr0QKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:weathered_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcAgAACAQAbmFtZR4AbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyBAkAbmFtZV9oYXNoVgRV0fBaz88DCgBuZXR3b3JrX2lk/0cYugoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oxidized_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdAgAACAQAbmFtZR0AbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2iP8WmFWOkriwMKAG5ldHdvcmtfaWQPdce7CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReAgAACAQAbmFtZRoAbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2jumiwOZIqv2AMKAG5ldHdvcmtfaWQvuxx9CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_exposed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfAgAACAQAbmFtZSIAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcgQJAG5hbWVfaGFzaPE/OfK6IoVMAwoAbmV0d29ya19pZHy5HkcKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_weathered_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAgAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyBAkAbmFtZV9oYXNoCA1xDp11bnwDCgBuZXR3b3JrX2lkDyEDVQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS+AgAACAQAbmFtZSMAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2i1pZAsZYHLDAMKAG5ldHdvcmtfaWQ/wSkCCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT3AwAACAQAbmFtZRkAbWluZWNyYWZ0OmNoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaIsW5pmpJEuQAwoAbmV0d29ya19pZHetwrkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:exposed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT4AwAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY2hpc2VsZWRfY29wcGVyBAkAbmFtZV9oYXNoOvrLJ0UowbgDCgBuZXR3b3JrX2lkZj7cPwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:weathered_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT5AwAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jaGlzZWxlZF9jb3BwZXIECQBuYW1lX2hhc2hh+42XlsWvGAMKAG5ldHdvcmtfaWS7Cy59CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oxidized_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT6AwAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaLpTIsnfluiCAwoAbmV0d29ya19pZB9/jS8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT7AwAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaFnXvXY5OinzAwoAbmV0d29ya19pZAcKtHsKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_exposed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT8AwAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY2hpc2VsZWRfY29wcGVyBAkAbmFtZV9oYXNoHJdq+Pph6hMDCgBuZXR3b3JrX2lkdge7IAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_oxidized_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AwAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaMj49OvlTpgCAwoAbmV0d29ya19pZN/r+roKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_weathered_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AwAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jaGlzZWxlZF9jb3BwZXIECQBuYW1lX2hhc2hzuO+Sg9LYQwMKAG5ldHdvcmtfaWQ7AN7iCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHBAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvcHBlcl9idWxiBAkAbmFtZV9oYXNo41TimHOsMWcDCgBuZXR3b3JrX2lkJnZvAgoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:exposed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIBAAACAQAbmFtZR0AbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX2J1bGIECQBuYW1lX2hhc2g++f1wYLLCrAMKAG5ldHdvcmtfaWRLdMmGCgYAc3RhdGVzAQMAbGl0AAELAHBvd2VyZWRfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:weathered_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQJBAAACAQAbmFtZR8AbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfYnVsYgQJAG5hbWVfaGFzaMEtsYfwRTXlAwoAbmV0d29ya19pZAp51LQKBgBzdGF0ZXMBAwBsaXQAAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oxidized_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKBAAACAQAbmFtZR4AbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNovnrBQZs8nDIDCgBuZXR3b3JrX2lkPsj0AAoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLBAAACAQAbmFtZRsAbWluZWNyYWZ0OndheGVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNoGTg6TYllMiIDCgBuZXR3b3JrX2lk9m0WhgoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waxed_exposed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMBAAACAQAbmFtZSMAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX2J1bGIECQBuYW1lX2hhc2gI6xkPcvBDVwMKAG5ldHdvcmtfaWR7BRcACgYAc3RhdGVzAQMAbGl0AAELAHBvd2VyZWRfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:waxed_weathered_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNBAAACAQAbmFtZSUAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfYnVsYgQJAG5hbWVfaGFzaMsUnmp3/VqVAwoAbmV0d29ya19pZEoworoKBgBzdGF0ZXMBAwBsaXQAAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:waxed_oxidized_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOBAAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNoBFKxY3fjVq4DCgBuZXR3b3JrX2lkzrJ6aAoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:emerald_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAAAACAQAbmFtZRcAbWluZWNyYWZ0OmVtZXJhbGRfYmxvY2sECQBuYW1lX2hhc2hK6QunqJznNAMKAG5ldHdvcmtfaWRk5+otCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:diamond_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AAAACAQAbmFtZRcAbWluZWNyYWZ0OmRpYW1vbmRfYmxvY2sECQBuYW1lX2hhc2iGKrxuvkytFQMKAG5ldHdvcmtfaWQQeQZXCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lapis_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAAAACAQAbmFtZRUAbWluZWNyYWZ0OmxhcGlzX2Jsb2NrBAkAbmFtZV9oYXNoDZ44xdb2zVoDCgBuZXR3b3JrX2lktVy0BAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:raw_iron_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTCAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJhd19pcm9uX2Jsb2NrBAkAbmFtZV9oYXNo9XyzNIQXxvwDCgBuZXR3b3JrX2lknms1QAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:raw_copper_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTDAgAACAQAbmFtZRoAbWluZWNyYWZ0OnJhd19jb3BwZXJfYmxvY2sECQBuYW1lX2hhc2hw1KG0TNUGgwMKAG5ldHdvcmtfaWS1vGo/CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:raw_gold_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJhd19nb2xkX2Jsb2NrBAkAbmFtZV9oYXNo6YuwuLwfOBwDCgBuZXR3b3JrX2lkLiQ5gQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:quartz_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAAAACAQAbmFtZRYAbWluZWNyYWZ0OnF1YXJ0el9ibG9jawQJAG5hbWVfaGFzaCfpbqyIIvZCAwoAbmV0d29ya19pZE2axGsKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:quartz_pillar", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS5BAAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9waWxsYXIECQBuYW1lX2hhc2igp62HI+PuSwMKAG5ldHdvcmtfaWS9SGXLCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chiseled_quartz_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4BAAACAQAbmFtZR8AbWluZWNyYWZ0OmNoaXNlbGVkX3F1YXJ0el9ibG9jawQJAG5hbWVfaGFzaAftJM9mCAvaAwoAbmV0d29ya19pZFwy0s0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_quartz", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS6BAAACAQAbmFtZRcAbWluZWNyYWZ0OnNtb290aF9xdWFydHoECQBuYW1lX2hhc2hIVzzgiItGagMKAG5ldHdvcmtfaWTVWgU2CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:prismarine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAAAACAQAbmFtZRQAbWluZWNyYWZ0OnByaXNtYXJpbmUECQBuYW1lX2hhc2jcnQCHi9CspQMKAG5ldHdvcmtfaWQnuuW1CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_prismarine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSyBAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZQQJAG5hbWVfaGFzaK+rhxsgkzplAwoAbmV0d29ya19pZIdA0I0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:slime", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnNsaW1lBAkAbmFtZV9oYXNoHJiEEJx+JlkDCgBuZXR3b3JrX2lkfgfVzAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:honey_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAQAACAQAbmFtZRUAbWluZWNyYWZ0OmhvbmV5X2Jsb2NrBAkAbmFtZV9oYXNo9zLYSUlelywDCgBuZXR3b3JrX2lko+dyWgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:honeycomb_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAQAACAQAbmFtZRkAbWluZWNyYWZ0OmhvbmV5Y29tYl9ibG9jawQJAG5hbWVfaGFzaASIPuOCYd1oAwoAbmV0d29ya19pZKys4n4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:hay_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAAAACAQAbmFtZRMAbWluZWNyYWZ0OmhheV9ibG9jawQJAG5hbWVfaGFzaIB2VxKxX8EpAwoAbmV0d29ya19pZKuQSloKBgBzdGF0ZXMDCgBkZXByZWNhdGVkAAAAAAgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAAAACAQAbmFtZRQAbWluZWNyYWZ0OmJvbmVfYmxvY2sECQBuYW1lX2hhc2i4ZX576W9AWgMKAG5ldHdvcmtfaWTWGacQCgYAc3RhdGVzAwoAZGVwcmVjYXRlZAAAAAAICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:nether_brick", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRwAAAACAQAbmFtZRYAbWluZWNyYWZ0Om5ldGhlcl9icmljawQJAG5hbWVfaGFzaMxcRiheU+nXAwoAbmV0d29ya19pZMkmzloKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_nether_brick", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAAAACAQAbmFtZRoAbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2sECQBuYW1lX2hhc2j8pRO4LfoECAMKAG5ldHdvcmtfaWRpdF0YCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:netherite_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAgAACAQAbmFtZRkAbWluZWNyYWZ0Om5ldGhlcml0ZV9ibG9jawQJAG5hbWVfaGFzaMghh6Zib/ZKAwoAbmV0d29ya19pZIz0mq0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lodestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAQAACAQAbmFtZRMAbWluZWNyYWZ0OmxvZGVzdG9uZQQJAG5hbWVfaGFzaJ2gmHOTlXv8AwoAbmV0d29ya19pZEfgB4wKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAAAACAQAbmFtZRQAbWluZWNyYWZ0OndoaXRlX3dvb2wECQBuYW1lX2hhc2jRWB7vaIEDiQMKAG5ldHdvcmtfaWSO8paQCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:light_gray_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfd29vbAQJAG5hbWVfaGFzaOpdQ1a2v4b3AwoAbmV0d29ya19pZIqZCYEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:gray_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAwAACAQAbmFtZRMAbWluZWNyYWZ0OmdyYXlfd29vbAQJAG5hbWVfaGFzaLsc1Lp1xdIOAwoAbmV0d29ya19pZFUs+HgKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:black_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAwAACAQAbmFtZRQAbWluZWNyYWZ0OmJsYWNrX3dvb2wECQBuYW1lX2hhc2hP2HC6o0X4HAMKAG5ldHdvcmtfaWRUbORcCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAwAACAQAbmFtZRQAbWluZWNyYWZ0OmJyb3duX3dvb2wECQBuYW1lX2hhc2ig5IW89PrREwMKAG5ldHdvcmtfaWRjT9j8CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQrAwAACAQAbmFtZRIAbWluZWNyYWZ0OnJlZF93b29sBAkAbmFtZV9oYXNoY4TBDq+mFgUDCgBuZXR3b3JrX2lktn9lcAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:orange_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAwAACAQAbmFtZRUAbWluZWNyYWZ0Om9yYW5nZV93b29sBAkAbmFtZV9oYXNoFstfrTZfSCgDCgBuZXR3b3JrX2lk+rqywwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:yellow_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAwAACAQAbmFtZRUAbWluZWNyYWZ0OnllbGxvd193b29sBAkAbmFtZV9oYXNoTFyus2RHegcDCgBuZXR3b3JrX2lkkKBhXAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lime_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAwAACAQAbmFtZRMAbWluZWNyYWZ0OmxpbWVfd29vbAQJAG5hbWVfaGFzaNVnnzKiMxmeAwoAbmV0d29ya19pZG9b32kKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:green_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAwAACAQAbmFtZRQAbWluZWNyYWZ0OmdyZWVuX3dvb2wECQBuYW1lX2hhc2i3mElRYHIcSQMKAG5ldHdvcmtfaWSssprwCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cyan_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwAwAACAQAbmFtZRMAbWluZWNyYWZ0OmN5YW5fd29vbAQJAG5hbWVfaGFzaBNDfvHn8dqFAwoAbmV0d29ya19pZK0hAbgKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_blue_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfd29vbAQJAG5hbWVfaGFzaLWFAUfyxFPNAwoAbmV0d29ya19pZL2oEugKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blue_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAwAACAQAbmFtZRMAbWluZWNyYWZ0OmJsdWVfd29vbAQJAG5hbWVfaGFzaLjHyxxbTWCLAwoAbmV0d29ya19pZPaLdFQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:purple_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzAwAACAQAbmFtZRUAbWluZWNyYWZ0OnB1cnBsZV93b29sBAkAbmFtZV9oYXNojvFtqzjAf/4DCgBuZXR3b3JrX2lklqASNQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:magenta_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0AwAACAQAbmFtZRYAbWluZWNyYWZ0Om1hZ2VudGFfd29vbAQJAG5hbWVfaGFzaGuOHvf+Pd4yAwoAbmV0d29ya19pZI4UoDQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:pink_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1AwAACAQAbmFtZRMAbWluZWNyYWZ0OnBpbmtfd29vbAQJAG5hbWVfaGFzaPiVA2pFeoFLAwoAbmV0d29ya19pZOZRO6oKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAAAACAQAbmFtZRYAbWluZWNyYWZ0OndoaXRlX2NhcnBldAQJAG5hbWVfaGFzaNeMHTI1fWPXAwoAbmV0d29ya19pZEahDFcKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_gray_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAwAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY2FycGV0BAkAbmFtZV9oYXNoHPw6ArBAsP0DCgBuZXR3b3JrX2lkQoAeUAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gray_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaAwAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXlfY2FycGV0BAkAbmFtZV9oYXNoZVR0OI+1VRADCgBuZXR3b3JrX2lkETF4WwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:black_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJsYWNrX2NhcnBldAQJAG5hbWVfaGFzaOk7LP9NptyhAwoAbmV0d29ya19pZFjmXtIKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brown_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJyb3duX2NhcnBldAQJAG5hbWVfaGFzaNaXFyOsAvIvAwoAbmV0d29ya19pZHPjFuoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAwAACAQAbmFtZRQAbWluZWNyYWZ0OnJlZF9jYXJwZXQECQBuYW1lX2hhc2i9eSKBf6SO3wMKAG5ldHdvcmtfaWQuhI/KCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:orange_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAwAACAQAbmFtZRcAbWluZWNyYWZ0Om9yYW5nZV9jYXJwZXQECQBuYW1lX2hhc2hIUkO4HlAdygMKAG5ldHdvcmtfaWSyKV9OCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:yellow_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAwAACAQAbmFtZRcAbWluZWNyYWZ0OnllbGxvd19jYXJwZXQECQBuYW1lX2hhc2hSDKX3scCamwMKAG5ldHdvcmtfaWT8nq+ECgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lime_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAwAACAQAbmFtZRUAbWluZWNyYWZ0OmxpbWVfY2FycGV0BAkAbmFtZV9oYXNo+6KFOpzsib4DCgBuZXR3b3JrX2lkT+DS4woGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:green_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAwAACAQAbmFtZRYAbWluZWNyYWZ0OmdyZWVuX2NhcnBldAQJAG5hbWVfaGFzaCHPMP9ltqFJAwoAbmV0d29ya19pZBgwAvAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cyan_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcAwAACAQAbmFtZRUAbWluZWNyYWZ0OmN5YW5fY2FycGV0BAkAbmFtZV9oYXNobXf62dQBJj8DCgBuZXR3b3JrX2lkKVppLgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_blue_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAwAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY2FycGV0BAkAbmFtZV9oYXNo20l4oktdZ3sDCgBuZXR3b3JrX2lkjdeMiwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:blue_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfY2FycGV0BAkAbmFtZV9oYXNo3p3lsW0eQwsDCgBuZXR3b3JrX2lkAovdPQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:purple_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdAwAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnBsZV9jYXJwZXQECQBuYW1lX2hhc2jwIA9pW/qp7QMKAG5ldHdvcmtfaWTqJqhjCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:magenta_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAwAACAQAbmFtZRgAbWluZWNyYWZ0Om1hZ2VudGFfY2FycGV0BAkAbmFtZV9oYXNoFXT36YNNZhMDCgBuZXR3b3JrX2lk+tqsGAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAwAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfY2FycGV0BAkAbmFtZV9oYXNoHll72oqk+OoDCgBuZXR3b3JrX2lkrnBYDwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:white_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTtAAAACAQAbmFtZR8AbWluZWNyYWZ0OndoaXRlX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaFUk9iXVjwV8AwoAbmV0d29ya19pZJPZY8AKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_gray_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAwAACAQAbmFtZSQAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNo7EUk30hmUtYDCgBuZXR3b3JrX2lkh8jVIwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gray_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAwAACAQAbmFtZR4AbWluZWNyYWZ0OmdyYXlfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoW77af6WihdwDCgBuZXR3b3JrX2lkSsqC1woGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:black_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTSAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJsYWNrX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaAfWYp0xtgcfAwoAbmV0d29ya19pZMWTC8EKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brown_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTPAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJyb3duX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaB74EeiLO46XAwoAbmV0d29ya19pZEDHKqwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTRAwAACAQAbmFtZR0AbWluZWNyYWZ0OnJlZF9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2gjFut6Z/VH1gMKAG5ldHdvcmtfaWSvcmwYCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:orange_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAwAACAQAbmFtZSAAbWluZWNyYWZ0Om9yYW5nZV9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2gADDj2IJiw+gMKAG5ldHdvcmtfaWTHph0FCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:yellow_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTHAwAACAQAbmFtZSAAbWluZWNyYWZ0OnllbGxvd19jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2iy6qKNn3ob5wMKAG5ldHdvcmtfaWQZAI39CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lime_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAwAACAQAbmFtZR4AbWluZWNyYWZ0OmxpbWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNo4dYIPslbXPUDCgBuZXR3b3JrX2lk2O8X0AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:green_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTQAwAACAQAbmFtZR8AbWluZWNyYWZ0OmdyZWVuX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaM/c9x2aJh3HAwoAbmV0d29ya19pZA0VfBMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cyan_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTMAwAACAQAbmFtZR4AbWluZWNyYWZ0OmN5YW5fY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNok+xKAe7XXjoDCgBuZXR3b3JrX2lkmkn6uwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_blue_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTGAwAACAQAbmFtZSQAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNogScpIQceyAEDCgBuZXR3b3JrX2lkOmVSbgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:blue_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTOAwAACAQAbmFtZR4AbWluZWNyYWZ0OmJsdWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoFp7mmeL86r0DCgBuZXR3b3JrX2lkS3b3RQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:purple_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAwAACAQAbmFtZSAAbWluZWNyYWZ0OnB1cnBsZV9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2iYcVU04hoStwMKAG5ldHdvcmtfaWQXimEjCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:magenta_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAwAACAQAbmFtZSEAbWluZWNyYWZ0Om1hZ2VudGFfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoy/70q6VPsWgDCgBuZXR3b3JrX2lkf9mxQwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAwAACAQAbmFtZR4AbWluZWNyYWZ0OnBpbmtfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoVikSAf8DwV0DCgBuZXR3b3JrX2lku2MivwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:white_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTsAAAACAQAbmFtZRgAbWluZWNyYWZ0OndoaXRlX2NvbmNyZXRlBAkAbmFtZV9oYXNo6zAp7lsLlvkDCgBuZXR3b3JrX2lk3MAYQAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_gray_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AwAACAQAbmFtZR0AbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY29uY3JldGUECQBuYW1lX2hhc2hEtet5wuDIKAMKAG5ldHdvcmtfaWQISs02CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:gray_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AwAACAQAbmFtZRcAbWluZWNyYWZ0OmdyYXlfY29uY3JldGUECQBuYW1lX2hhc2j92INnb0a83AMKAG5ldHdvcmtfaWQj8RHwCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:black_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAwAACAQAbmFtZRgAbWluZWNyYWZ0OmJsYWNrX2NvbmNyZXRlBAkAbmFtZV9oYXNo2X7NDIQmZ70DCgBuZXR3b3JrX2lk2uiVDQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:brown_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AwAACAQAbmFtZRgAbWluZWNyYWZ0OmJyb3duX2NvbmNyZXRlBAkAbmFtZV9oYXNoeka02BwXf6oDCgBuZXR3b3JrX2lkYf+xDQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:red_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAwAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZF9jb25jcmV0ZQQJAG5hbWVfaGFzaPWmNowLGubqAwoAbmV0d29ya19pZKwyx58KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:orange_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRzAwAACAQAbmFtZRkAbWluZWNyYWZ0Om9yYW5nZV9jb25jcmV0ZQQJAG5hbWVfaGFzaAgE8XmaAi6+AwoAbmV0d29ya19pZMDQNz8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:yellow_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR2AwAACAQAbmFtZRkAbWluZWNyYWZ0OnllbGxvd19jb25jcmV0ZQQJAG5hbWVfaGFzaE6ONfJPBd0+AwoAbmV0d29ya19pZMarutwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lime_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR3AwAACAQAbmFtZRcAbWluZWNyYWZ0OmxpbWVfY29uY3JldGUECQBuYW1lX2hhc2gnd8JW6wmJcAMKAG5ldHdvcmtfaWTd47aoCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:green_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/AwAACAQAbmFtZRgAbWluZWNyYWZ0OmdyZWVuX2NvbmNyZXRlBAkAbmFtZV9oYXNokbFxRKchQZkDCgBuZXR3b3JrX2lkmhZWUgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cyan_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AwAACAQAbmFtZRcAbWluZWNyYWZ0OmN5YW5fY29uY3JldGUECQBuYW1lX2hhc2hFRrWJ33qj1wMKAG5ldHdvcmtfaWQbi5b8CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:light_blue_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR1AwAACAQAbmFtZR0AbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY29uY3JldGUECQBuYW1lX2hhc2gHAe0kl0SE4AMKAG5ldHdvcmtfaWRL/GbSCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blue_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AwAACAQAbmFtZRcAbWluZWNyYWZ0OmJsdWVfY29uY3JldGUECQBuYW1lX2hhc2hiay301nnj1wMKAG5ldHdvcmtfaWRMvFXNCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:purple_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8AwAACAQAbmFtZRkAbWluZWNyYWZ0OnB1cnBsZV9jb25jcmV0ZQQJAG5hbWVfaGFzaHBHflsPIwdXAwoAbmV0d29ya19pZCyKA5gKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:magenta_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0AwAACAQAbmFtZRoAbWluZWNyYWZ0Om1hZ2VudGFfY29uY3JldGUECQBuYW1lX2hhc2gN7LuB/OvdZAMKAG5ldHdvcmtfaWTc6ZOdCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:pink_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AwAACAQAbmFtZRcAbWluZWNyYWZ0OnBpbmtfY29uY3JldGUECQBuYW1lX2hhc2ii2G5F0u3SOAMKAG5ldHdvcmtfaWSszGgrCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:clay", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSAAAACAQAbmFtZQ4AbWluZWNyYWZ0OmNsYXkECQBuYW1lX2hhc2j/S6sKXRcpzwMKAG5ldHdvcmtfaWRmsb8nCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:hardened_clay", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAAAACAQAbmFtZRcAbWluZWNyYWZ0OmhhcmRlbmVkX2NsYXkECQBuYW1lX2hhc2jrnRwCJ0krJAMKAG5ldHdvcmtfaWRBCOrrCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:white_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSfAAAACAQAbmFtZRoAbWluZWNyYWZ0OndoaXRlX3RlcnJhY290dGEECQBuYW1lX2hhc2j3RSdgmnAIewMKAG5ldHdvcmtfaWSimKw+CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:light_gray_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAwAACAQAbmFtZR8AbWluZWNyYWZ0OmxpZ2h0X2dyYXlfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAz1Ri3wIxomAwoAbmV0d29ya19pZH5qgOcKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:gray_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAwAACAQAbmFtZRkAbWluZWNyYWZ0OmdyYXlfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAXdSLAaNZ9vAwoAbmV0d29ya19pZM1QDV0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:black_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJsYWNrX3RlcnJhY290dGEECQBuYW1lX2hhc2jxssdv5vlbpgMKAG5ldHdvcmtfaWRE3Ru/CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJyb3duX3RlcnJhY290dGEECQBuYW1lX2hhc2gG4kPenmOF9gMKAG5ldHdvcmtfaWQ/i0iNCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAwAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNo7fX56HXFejEDCgBuZXR3b3JrX2lk8tTF8QoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:orange_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAwAACAQAbmFtZRsAbWluZWNyYWZ0Om9yYW5nZV90ZXJyYWNvdHRhBAkAbmFtZV9oYXNo0Hjmql3sruMDCgBuZXR3b3JrX2lklmqmkAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:yellow_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAwAACAQAbmFtZRsAbWluZWNyYWZ0OnllbGxvd190ZXJyYWNvdHRhBAkAbmFtZV9oYXNoqkyKKrmA3VcDCgBuZXR3b3JrX2lkaM/orAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lime_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpbWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaANjADFOF9v7AwoAbmV0d29ya19pZJt0XsgKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:green_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAwAACAQAbmFtZRoAbWluZWNyYWZ0OmdyZWVuX3RlcnJhY290dGEECQBuYW1lX2hhc2j5Ybq36yYwRQMKAG5ldHdvcmtfaWQ8kGdHCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cyan_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAwAACAQAbmFtZRkAbWluZWNyYWZ0OmN5YW5fdGVycmFjb3R0YQQJAG5hbWVfaGFzaN09COzMuHwAAwoAbmV0d29ya19pZIWPCzoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_blue_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAwAACAQAbmFtZR8AbWluZWNyYWZ0OmxpZ2h0X2JsdWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaOMytez7cOZiAwoAbmV0d29ya19pZFHK1UsKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blue_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAwAACAQAbmFtZRkAbWluZWNyYWZ0OmJsdWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaF6inyTK5RpAAwoAbmV0d29ya19pZF5mVZIKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:purple_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAwAACAQAbmFtZRsAbWluZWNyYWZ0OnB1cnBsZV90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoKF7YG61yTbEDCgBuZXR3b3JrX2lkhtRDlwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:magenta_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAwAACAQAbmFtZRwAbWluZWNyYWZ0Om1hZ2VudGFfdGVycmFjb3R0YQQJAG5hbWVfaGFzaLWvtpAVtztyAwoAbmV0d29ya19pZN5SoakKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:pink_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAwAACAQAbmFtZRkAbWluZWNyYWZ0OnBpbmtfdGVycmFjb3R0YQQJAG5hbWVfaGFzaJ7mzvyzSQZTAwoAbmV0d29ya19pZDJWe4YKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAAAACAQAbmFtZSEAbWluZWNyYWZ0OndoaXRlX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoiVzCdoHAJo0DCgBuZXR3b3JrX2lkIlj9AAoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:silver_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAAAACAQAbmFtZSIAbWluZWNyYWZ0OnNpbHZlcl9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAVsA0CnhzA4AwoAbmV0d29ya19pZPnxtJEKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gray_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAAAACAQAbmFtZSAAbWluZWNyYWZ0OmdyYXlfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2jvLZt9u/lF/AMKAG5ldHdvcmtfaWQVU8eFCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:black_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAAAACAQAbmFtZSEAbWluZWNyYWZ0OmJsYWNrX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoe8I4xAXbO5UDCgBuZXR3b3JrX2lk2Icb9AoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAAAACAQAbmFtZSEAbWluZWNyYWZ0OmJyb3duX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoSiNZOobbpjoDCgBuZXR3b3JrX2lkJy0jwgoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAAAACAQAbmFtZR8AbWluZWNyYWZ0OnJlZF9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaBdWFGLmCLFVAwoAbmV0d29ya19pZMYBJSEKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:orange_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAAAACAQAbmFtZSIAbWluZWNyYWZ0Om9yYW5nZV9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaMyJMrnPr7szAwoAbmV0d29ya19pZN6+7TUKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:yellow_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAAAACAQAbmFtZSIAbWluZWNyYWZ0OnllbGxvd19nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaN6NaIhf6m0uAwoAbmV0d29ya19pZKRHXeoKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lime_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAAAACAQAbmFtZSAAbWluZWNyYWZ0OmxpbWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2iF3E68/rB2EAMKAG5ldHdvcmtfaWSP7qQWCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:green_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAAAACAQAbmFtZSEAbWluZWNyYWZ0OmdyZWVuX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNow5mo8aQDFboDCgBuZXR3b3JrX2lkoF11kgoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:cyan_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAAAACAQAbmFtZSAAbWluZWNyYWZ0OmN5YW5fZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2gnNB+cCFRJhwMKAG5ldHdvcmtfaWT9buMtCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_blue_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAAAACAQAbmFtZSYAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2gladnCDBKCigMKAG5ldHdvcmtfaWS5CszFCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blue_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAAAACAQAbmFtZSAAbWluZWNyYWZ0OmJsdWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2giOZK+2nB1igMKAG5ldHdvcmtfaWR+e22CCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:purple_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAAAACAQAbmFtZSIAbWluZWNyYWZ0OnB1cnBsZV9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaIQU03txeAfHAwoAbmV0d29ya19pZLKbSE4KBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:magenta_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAAAACAQAbmFtZSMAbWluZWNyYWZ0Om1hZ2VudGFfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2i/SNqDJbfjMgMKAG5ldHdvcmtfaWQKf9UvCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:pink_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAAAACAQAbmFtZSAAbWluZWNyYWZ0OnBpbmtfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2hik8DVt4g+twMKAG5ldHdvcmtfaWTKzav2CgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:purpur_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAAAACAQAbmFtZRYAbWluZWNyYWZ0OnB1cnB1cl9ibG9jawQJAG5hbWVfaGFzaAgLwnUZGlzsAwoAbmV0d29ya19pZLD8ox4KBgBzdGF0ZXMICwBjaGlzZWxfdHlwZQcAZGVmYXVsdAgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:purpur_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAAAACAQAbmFtZRYAbWluZWNyYWZ0OnB1cnB1cl9ibG9jawQJAG5hbWVfaGFzaAgLwnUZGlzsAwoAbmV0d29ya19pZPSAFFsKBgBzdGF0ZXMICwBjaGlzZWxfdHlwZQUAbGluZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:packed_mud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAgAACAQAbmFtZRQAbWluZWNyYWZ0OnBhY2tlZF9tdWQECQBuYW1lX2hhc2gHOMa121h4FgMKAG5ldHdvcmtfaWTUb6LyCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:mud_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAgAACAQAbmFtZRQAbWluZWNyYWZ0Om11ZF9icmlja3MECQBuYW1lX2hhc2iDL/SVl/PewQMKAG5ldHdvcmtfaWSkBjaDCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:nether_wart_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAAAACAQAbmFtZRsAbWluZWNyYWZ0Om5ldGhlcl93YXJ0X2Jsb2NrBAkAbmFtZV9oYXNo9XGS4GNnlV4DCgBuZXR3b3JrX2lkh3apIgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_wart_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAQAACAQAbmFtZRsAbWluZWNyYWZ0OndhcnBlZF93YXJ0X2Jsb2NrBAkAbmFtZV9oYXNo9IqDS9yUPJoDCgBuZXR3b3JrX2lkMpKAbAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:shroomlight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAQAACAQAbmFtZRUAbWluZWNyYWZ0OnNocm9vbWxpZ2h0BAkAbmFtZV9oYXNoZHCHcHX/HYADCgBuZXR3b3JrX2lkLG2JiwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:crimson_nylium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fbnlsaXVtBAkAbmFtZV9oYXNoOr6DJYW2bFYDCgBuZXR3b3JrX2lkuWpRDgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_nylium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9ueWxpdW0ECQBuYW1lX2hhc2g0Zf89cfr3rwMKAG5ldHdvcmtfaWSu/kekCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:netherrack", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAAAACAQAbmFtZRQAbWluZWNyYWZ0Om5ldGhlcnJhY2sECQBuYW1lX2hhc2i/r5ZyRsvPyQMKAG5ldHdvcmtfaWTAiTOACgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhc2FsdAQJAG5hbWVfaGFzaH+UQO2yWodiAwoAbmV0d29ya19pZBPNSV4KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAQAACAQAbmFtZRkAbWluZWNyYWZ0OnBvbGlzaGVkX2Jhc2FsdAQJAG5hbWVfaGFzaMS+L0gMnRcBAwoAbmV0d29ya19pZF+/mHwKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:smooth_basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AgAACAQAbmFtZRcAbWluZWNyYWZ0OnNtb290aF9iYXNhbHQECQBuYW1lX2hhc2jKPUdz89kuNAMKAG5ldHdvcmtfaWTkb/oVCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:soul_soil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAQAACAQAbmFtZRMAbWluZWNyYWZ0OnNvdWxfc29pbAQJAG5hbWVfaGFzaC1/87ccutuTAwoAbmV0d29ya19pZKc63SMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dirt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAAAACAQAbmFtZQ4AbWluZWNyYWZ0OmRpcnQECQBuYW1lX2hhc2hXp6jnXAe+kQMKAG5ldHdvcmtfaWSG706CCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:coarse_dirt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTBBAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvYXJzZV9kaXJ0BAkAbmFtZV9oYXNosd+cah7WSmoDCgBuZXR3b3JrX2lkgS5RcAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:farmland", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AAAACAQAbmFtZRIAbWluZWNyYWZ0OmZhcm1sYW5kBAkAbmFtZV9oYXNoxyQ5ag7LolADCgBuZXR3b3JrX2lkX618FQoGAHN0YXRlcwMSAG1vaXN0dXJpemVkX2Ftb3VudAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:grass_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAAAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXNzX2Jsb2NrBAkAbmFtZV9oYXNojPyGp3/CSZwDCgBuZXR3b3JrX2lktCgx3goGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:grass_path", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTGAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdyYXNzX3BhdGgECQBuYW1lX2hhc2i0/KZV8Qsy+gMKAG5ldHdvcmtfaWT7CcdzCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:podzol", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTzAAAACAQAbmFtZRAAbWluZWNyYWZ0OnBvZHpvbAQJAG5hbWVfaGFzaBzqokRjH4Z1AwoAbmV0d29ya19pZPPS/GUKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mycelium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAAAACAQAbmFtZRIAbWluZWNyYWZ0Om15Y2VsaXVtBAkAbmFtZV9oYXNojTN09cKickIDCgBuZXR3b3JrX2lkLNPxXQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAgAACAQAbmFtZQ0AbWluZWNyYWZ0Om11ZAQJAG5hbWVfaGFzaPb/3P+uLy+9AwoAbmV0d29ya19pZPIUlUkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnN0b25lBAkAbmFtZV9oYXNoE3mqhJxzJycDCgBuZXR3b3JrX2lkIQ4xgAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:iron_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAAAACAQAbmFtZRIAbWluZWNyYWZ0Omlyb25fb3JlBAkAbmFtZV9oYXNoS7BYtLnfx3gDCgBuZXR3b3JrX2lk3loneQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAAAACAQAbmFtZRIAbWluZWNyYWZ0OmdvbGRfb3JlBAkAbmFtZV9oYXNoC5Y+DUGXLC4DCgBuZXR3b3JrX2lkNhvMfwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:diamond_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AAAACAQAbmFtZRUAbWluZWNyYWZ0OmRpYW1vbmRfb3JlBAkAbmFtZV9oYXNokUOJ2wZZrGQDCgBuZXR3b3JrX2lk/dChVAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lapis_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQVAAAACAQAbmFtZRMAbWluZWNyYWZ0OmxhcGlzX29yZQQJAG5hbWVfaGFzaMrmrUrSzb7qAwoAbmV0d29ya19pZMg+qK4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:redstone_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAAAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZHN0b25lX29yZQQJAG5hbWVfaGFzaFHVnp8Wc4JbAwoAbmV0d29ya19pZKDYvQoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:coal_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAAAACAQAbmFtZRIAbWluZWNyYWZ0OmNvYWxfb3JlBAkAbmFtZV9oYXNo1OjA+Iuy51oDCgBuZXR3b3JrX2lk+R/aKAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:copper_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2AgAACAQAbmFtZRQAbWluZWNyYWZ0OmNvcHBlcl9vcmUECQBuYW1lX2hhc2iSZduSntOzOwMKAG5ldHdvcmtfaWQtIuCnCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:emerald_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAAAACAQAbmFtZRUAbWluZWNyYWZ0OmVtZXJhbGRfb3JlBAkAbmFtZV9oYXNoJTovr+VgINsDCgBuZXR3b3JrX2lknbkqCgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:quartz_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAAAACAQAbmFtZRQAbWluZWNyYWZ0OnF1YXJ0el9vcmUECQBuYW1lX2hhc2g0yNHLMK9TaQMKAG5ldHdvcmtfaWSzN7nzCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:nether_gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfAgAACAQAbmFtZRkAbWluZWNyYWZ0Om5ldGhlcl9nb2xkX29yZQQJAG5hbWVfaGFzaEJZ7segIBgBAwoAbmV0d29ya19pZNI9pDgKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:ancient_debris", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAgAACAQAbmFtZRgAbWluZWNyYWZ0OmFuY2llbnRfZGVicmlzBAkAbmFtZV9oYXNoNrbxMc9AwKcDCgBuZXR3b3JrX2lkrSNjEAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:deepslate_iron_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9pcm9uX29yZQQJAG5hbWVfaGFzaB/fDL9pgvXXAwoAbmV0d29ya19pZFA0bz4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9nb2xkX29yZQQJAG5hbWVfaGFzaF9G7WYhKFinAwoAbmV0d29ya19pZHQTfBUKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_diamond_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV9kaWFtb25kX29yZQQJAG5hbWVfaGFzaEUH5USh+iD3AwoAbmV0d29ya19pZHP6VzAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_lapis_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV9sYXBpc19vcmUECQBuYW1lX2hhc2j+yFxU/KZs1gMKAG5ldHdvcmtfaWRKINzICgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate_redstone_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAgAACAQAbmFtZSAAbWluZWNyYWZ0OmRlZXBzbGF0ZV9yZWRzdG9uZV9vcmUECQBuYW1lX2hhc2iVgM3wWWD6ugMKAG5ldHdvcmtfaWReBdYRCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate_emerald_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSWAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV9lbWVyYWxkX29yZQQJAG5hbWVfaGFzaNlfo5HTwS6wAwoAbmV0d29ya19pZNeie6sKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_coal_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSVAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9jb2FsX29yZQQJAG5hbWVfaGFzaIjikmcbRrPPAwoAbmV0d29ya19pZD9TiygKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:deepslate_copper_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9jb3BwZXJfb3JlBAkAbmFtZV9oYXNottjV4Ev5LAQDCgBuZXR3b3JrX2lkP23rgQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gravel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAAAACAQAbmFtZRAAbWluZWNyYWZ0OmdyYXZlbAQJAG5hbWVfaGFzaOFxz8XJd2r/AwoAbmV0d29ya19pZBpfI1sKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:granite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAwAACAQAbmFtZREAbWluZWNyYWZ0OmdyYW5pdGUECQBuYW1lX2hhc2iq+Dur2pw4AwMKAG5ldHdvcmtfaWT2NMfJCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:diorite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAwAACAQAbmFtZREAbWluZWNyYWZ0OmRpb3JpdGUECQBuYW1lX2hhc2iaFsq2iinZBQMKAG5ldHdvcmtfaWQqGE6XCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:andesite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAwAACAQAbmFtZRIAbWluZWNyYWZ0OmFuZGVzaXRlBAkAbmFtZV9oYXNosaLIEnQQoSYDCgBuZXR3b3JrX2lkEApRZAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAgAACAQAbmFtZRQAbWluZWNyYWZ0OmJsYWNrc3RvbmUECQBuYW1lX2hhc2iMFYziD80D6QMKAG5ldHdvcmtfaWSrUryHCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AgAACAQAbmFtZRMAbWluZWNyYWZ0OmRlZXBzbGF0ZQQJAG5hbWVfaGFzaKX5pAblxz8TAwoAbmV0d29ya19pZOJoQjsKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_granite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGUECQBuYW1lX2hhc2iLiEfys8pFIAMKAG5ldHdvcmtfaWTCxxcHCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_diorite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGUECQBuYW1lX2hhc2hTxY4fKmNmlAMKAG5ldHdvcmtfaWTmtjdRCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_andesite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSAwAACAQAbmFtZRsAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlBAkAbmFtZV9oYXNovl28uFk4HuQDCgBuZXR3b3JrX2lklFjuCwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiAgAACAQAbmFtZR0AbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmUECQBuYW1lX2hhc2jT9fHCl6vWQQMKAG5ldHdvcmtfaWR/Ho6oCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AgAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaHC1edoaWF3uAwoAbmV0d29ya19pZCPeQsEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnNhbmQECQBuYW1lX2hhc2i6lthXXbAyWAMKAG5ldHdvcmtfaWRjeUMICgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:red_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS0BAAACAQAbmFtZRIAbWluZWNyYWZ0OnJlZF9zYW5kBAkAbmFtZV9oYXNoCiarI69JQCkDCgBuZXR3b3JrX2lkU8UD/AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cactus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAAAACAQAbmFtZRAAbWluZWNyYWZ0OmNhY3R1cwQJAG5hbWVfaGFzaCG9zL0N4wvGAwoAbmV0d29ya19pZDeCERAKBgBzdGF0ZXMDAwBhZ2UAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAAAACAQAbmFtZREAbWluZWNyYWZ0Om9ha19sb2cECQBuYW1lX2hhc2ho6TS+K7PZFQMKAG5ldHdvcmtfaWQjfjoxCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQJAQAACAQAbmFtZRoAbWluZWNyYWZ0OnN0cmlwcGVkX29ha19sb2cECQBuYW1lX2hhc2h8dqh+OOHU4wMKAG5ldHdvcmtfaWSYKjdrCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:spruce_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AwAACAQAbmFtZRQAbWluZWNyYWZ0OnNwcnVjZV9sb2cECQBuYW1lX2hhc2hZ03qaLoF3WgMKAG5ldHdvcmtfaWRlFD8eCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_spruce_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX3NwcnVjZV9sb2cECQBuYW1lX2hhc2iNrhKjS5IyrgMKAG5ldHdvcmtfaWRQcEC3CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:birch_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AwAACAQAbmFtZRMAbWluZWNyYWZ0OmJpcmNoX2xvZwQJAG5hbWVfaGFzaBUzT3NxsZAnAwoAbmV0d29ya19pZBKN3VQKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_birch_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAQAACAQAbmFtZRwAbWluZWNyYWZ0OnN0cmlwcGVkX2JpcmNoX2xvZwQJAG5hbWVfaGFzaCFKS4AeuSidAwoAbmV0d29ya19pZN0IONIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:jungle_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AwAACAQAbmFtZRQAbWluZWNyYWZ0Omp1bmdsZV9sb2cECQBuYW1lX2hhc2gkwW0KNulqDgMKAG5ldHdvcmtfaWQaziU/CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_jungle_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2p1bmdsZV9sb2cECQBuYW1lX2hhc2hAwMsgOk02JAMKAG5ldHdvcmtfaWQvls0eCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:acacia_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAAAACAQAbmFtZRQAbWluZWNyYWZ0OmFjYWNpYV9sb2cECQBuYW1lX2hhc2iV48VpYhjoYQMKAG5ldHdvcmtfaWRxEqe0CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_acacia_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2FjYWNpYV9sb2cECQBuYW1lX2hhc2hJb0lQqnEqlgMKAG5ldHdvcmtfaWRg3IdRCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dark_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7AwAACAQAbmFtZRYAbWluZWNyYWZ0OmRhcmtfb2FrX2xvZwQJAG5hbWVfaGFzaIWfVRd0XUo3AwoAbmV0d29ya19pZPMM7LYKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_dark_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIAQAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2Rhcmtfb2FrX2xvZwQJAG5hbWVfaGFzaPFTdxRdPwkOAwoAbmV0d29ya19pZDIzenIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mangrove_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAgAACAQAbmFtZRYAbWluZWNyYWZ0Om1hbmdyb3ZlX2xvZwQJAG5hbWVfaGFzaHZe6DzPZBobAwoAbmV0d29ya19pZG6DuYkKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_mangrove_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAgAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX21hbmdyb3ZlX2xvZwQJAG5hbWVfaGFzaLqIBo4hwA//AwoAbmV0d29ya19pZPtRn7UKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cherry_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAwAACAQAbmFtZRQAbWluZWNyYWZ0OmNoZXJyeV9sb2cECQBuYW1lX2hhc2hwFlaioppB1wMKAG5ldHdvcmtfaWS2sdXECgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_cherry_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAwAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2NoZXJyeV9sb2cECQBuYW1lX2hhc2i85H6G+WhXaAMKAG5ldHdvcmtfaWRjzoglCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAQAACAQAbmFtZRYAbWluZWNyYWZ0OmNyaW1zb25fc3RlbQQJAG5hbWVfaGFzaM0FzfL0UTKZAwoAbmV0d29ya19pZKvzID0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_crimson_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAQAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2NyaW1zb25fc3RlbQQJAG5hbWVfaGFzaDlA6nood57EAwoAbmV0d29ya19pZHrIqjIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAQAACAQAbmFtZRUAbWluZWNyYWZ0OndhcnBlZF9zdGVtBAkAbmFtZV9oYXNon7cKfPZxdrUDCgBuZXR3b3JrX2lkerWyMwoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_warped_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAQAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX3dhcnBlZF9zdGVtBAkAbmFtZV9oYXNoEw+y0dDPSd8DCgBuZXR3b3JrX2lkIQ9vBAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAQAACAQAbmFtZRIAbWluZWNyYWZ0Om9ha193b29kBAkAbmFtZV9oYXNoqQIkuVPyJX0DCgBuZXR3b3JrX2lku2G1YAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtBAAACAQAbmFtZRUAbWluZWNyYWZ0OnNwcnVjZV93b29kBAkAbmFtZV9oYXNoTrIJ5TAQ+OgDCgBuZXR3b3JrX2lkaXLxCwoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJpcmNoX3dvb2QECQBuYW1lX2hhc2iqVjG4xt0cKQMKAG5ldHdvcmtfaWS06c5VCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvBAAACAQAbmFtZRUAbWluZWNyYWZ0Omp1bmdsZV93b29kBAkAbmFtZV9oYXNo9bYW29ORWCoDCgBuZXR3b3JrX2lkyFyKLQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwBAAACAQAbmFtZRUAbWluZWNyYWZ0OmFjYWNpYV93b29kBAkAbmFtZV9oYXNoKkDfgzlJUcIDCgBuZXR3b3JrX2lkuTWlcgoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxBAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhcmtfb2FrX3dvb2QECQBuYW1lX2hhc2jaKv4ORLadAAMKAG5ldHdvcmtfaWSDrNQ8CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyBAAACAQAbmFtZRsAbWluZWNyYWZ0OnN0cmlwcGVkX29ha193b29kBAkAbmFtZV9oYXNovW6KCv+VZnsDCgBuZXR3b3JrX2lkkhWGegoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_spruce_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzBAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX3NwcnVjZV93b29kBAkAbmFtZV9oYXNoMnuUk4Xo6icDCgBuZXR3b3JrX2lkes2ydAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_birch_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0BAAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2JpcmNoX3dvb2QECQBuYW1lX2hhc2hm88R604TKbAMKAG5ldHdvcmtfaWRleEMJCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_jungle_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1BAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2p1bmdsZV93b29kBAkAbmFtZV9oYXNoUVs6KsZQRBoDCgBuZXR3b3JrX2lk92k8HQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_acacia_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2BAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2FjYWNpYV93b29kBAkAbmFtZV9oYXNo/kOPN2bCJhUDCgBuZXR3b3JrX2lktl6LwQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_dark_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3BAAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX2Rhcmtfb2FrX3dvb2QECQBuYW1lX2hhc2h2jFDfKVFgfAMKAG5ldHdvcmtfaWTgZQ5VCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAgAACAQAbmFtZRcAbWluZWNyYWZ0Om1hbmdyb3ZlX3dvb2QECQBuYW1lX2hhc2iXVxG0JG2fVAMKAG5ldHdvcmtfaWTok1JCCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkBDABzdHJpcHBlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_mangrove_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAgAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX21hbmdyb3ZlX3dvb2QECQBuYW1lX2hhc2h7CkbaBF7/WAMKAG5ldHdvcmtfaWQLAX88CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhAwAACAQAbmFtZRUAbWluZWNyYWZ0OmNoZXJyeV93b29kBAkAbmFtZV9oYXNoAW8srlmpBM8DCgBuZXR3b3JrX2lkEALMfAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AQwAc3RyaXBwZWRfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_cherry_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAwAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2NoZXJyeV93b29kBAkAbmFtZV9oYXNo/e7KXv+CB38DCgBuZXR3b3JrX2lkg5aVtQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:crimson_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAgAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25faHlwaGFlBAkAbmFtZV9oYXNouRmKmfSqEWADCgBuZXR3b3JrX2lk+Tm5rQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stripped_crimson_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQrAgAACAQAbmFtZSEAbWluZWNyYWZ0OnN0cmlwcGVkX2NyaW1zb25faHlwaGFlBAkAbmFtZV9oYXNoFffwmABq4LUDCgBuZXR3b3JrX2lkZAlUbgoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:warped_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAgAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9oeXBoYWUECQBuYW1lX2hhc2hn8plQUr6pmQMKAG5ldHdvcmtfaWRU2AIBCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:stripped_warped_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAgAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX3dhcnBlZF9oeXBoYWUECQBuYW1lX2hhc2irKq+HYPSgjQMKAG5ldHdvcmtfaWSbrOPDCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bamboo_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJhbWJvb19ibG9jawQJAG5hbWVfaGFzaAbDeur6stIBAwoAbmV0d29ya19pZCJAwn0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:stripped_bamboo_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAwAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2JhbWJvb19ibG9jawQJAG5hbWVfaGFzaJpwytpZOZM9AwoAbmV0d29ya19pZKuRbNEKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oak_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQSAAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19sZWF2ZXMECQBuYW1lX2hhc2h6O4xGqA2oKgMKAG5ldHdvcmtfaWT98c59CgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:spruce_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfBAAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9sZWF2ZXMECQBuYW1lX2hhc2i9x1CtNAuqZwMKAG5ldHdvcmtfaWSzF7pTCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:birch_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgBAAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX2xlYXZlcwQJAG5hbWVfaGFzaBlAGHaoaLZSAwoAbmV0d29ya19pZOjtvWcKBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:jungle_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhBAAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9sZWF2ZXMECQBuYW1lX2hhc2iW1uAH07zGhgMKAG5ldHdvcmtfaWSA5KX0CgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:acacia_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAAAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9sZWF2ZXMECQBuYW1lX2hhc2iZJf8dAgDRNQMKAG5ldHdvcmtfaWQ/G7VuCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dark_oak_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiBAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX2xlYXZlcwQJAG5hbWVfaGFzaCk7rDipWFSjAwoAbmV0d29ya19pZJ2AkbYKBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:mangrove_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX2xlYXZlcwQJAG5hbWVfaGFzaKyI/dWvhEG8AwoAbmV0d29ya19pZPQxCZ8KBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cherry_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9sZWF2ZXMECQBuYW1lX2hhc2giTs9ChhYBlQMKAG5ldHdvcmtfaWR8bPpwCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:azalea_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAgAACAQAbmFtZRcAbWluZWNyYWZ0OmF6YWxlYV9sZWF2ZXMECQBuYW1lX2hhc2iXFhD57wFS7AMKAG5ldHdvcmtfaWTNB/9ECgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:azalea_leaves_flowered", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREAgAACAQAbmFtZSAAbWluZWNyYWZ0OmF6YWxlYV9sZWF2ZXNfZmxvd2VyZWQECQBuYW1lX2hhc2gs8jxlS/pMrwMKAG5ldHdvcmtfaWQ7W4PyCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:oak_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAAAACAQAbmFtZRUAbWluZWNyYWZ0Om9ha19zYXBsaW5nBAkAbmFtZV9oYXNoogXcT9QfjiUDCgBuZXR3b3JrX2lkG22C+AoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4BAAACAQAbmFtZRgAbWluZWNyYWZ0OnNwcnVjZV9zYXBsaW5nBAkAbmFtZV9oYXNoe8hz4uYP0FcDCgBuZXR3b3JrX2lkUQmhaQoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5BAAACAQAbmFtZRcAbWluZWNyYWZ0OmJpcmNoX3NhcGxpbmcECQBuYW1lX2hhc2h348iJQ/tK4wMKAG5ldHdvcmtfaWQ2Uh53CgYAc3RhdGVzAQcAYWdlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6BAAACAQAbmFtZRgAbWluZWNyYWZ0Omp1bmdsZV9zYXBsaW5nBAkAbmFtZV9oYXNo7tyTOdSrxaADCgBuZXR3b3JrX2lkXmBAdAoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7BAAACAQAbmFtZRgAbWluZWNyYWZ0OmFjYWNpYV9zYXBsaW5nBAkAbmFtZV9oYXNo99sg15uoX7ADCgBuZXR3b3JrX2lkPXX1KgoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8BAAACAQAbmFtZRoAbWluZWNyYWZ0OmRhcmtfb2FrX3NhcGxpbmcECQBuYW1lX2hhc2jnVzFplW7cHgMKAG5ldHdvcmtfaWTD4giHCgYAc3RhdGVzAQcAYWdlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_propagule", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAgAACAQAbmFtZRwAbWluZWNyYWZ0Om1hbmdyb3ZlX3Byb3BhZ3VsZQQJAG5hbWVfaGFzaJGeox6hkfLFAwoAbmV0d29ya19pZAIpvpYKBgBzdGF0ZXMBBwBoYW5naW5nAAMPAHByb3BhZ3VsZV9zdGFnZQAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiAwAACAQAbmFtZRgAbWluZWNyYWZ0OmNoZXJyeV9zYXBsaW5nBAkAbmFtZV9oYXNoGrPpNMf1LtcDCgBuZXR3b3JrX2lkypakXQoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bee_nest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAQAACAQAbmFtZRIAbWluZWNyYWZ0OmJlZV9uZXN0BAkAbmFtZV9oYXNo2R2WBxUHEZIDCgBuZXR3b3JrX2lkiXWLEAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAADCwBob25leV9sZXZlbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:wheat_seeds" + }, + { + "id": "minecraft:pumpkin_seeds" + }, + { + "id": "minecraft:melon_seeds" + }, + { + "id": "minecraft:beetroot_seeds" + }, + { + "id": "minecraft:torchflower_seeds" + }, + { + "id": "minecraft:pitcher_pod" + }, + { + "id": "minecraft:wheat" + }, + { + "id": "minecraft:beetroot" + }, + { + "id": "minecraft:potato" + }, + { + "id": "minecraft:poisonous_potato" + }, + { + "id": "minecraft:carrot" + }, + { + "id": "minecraft:golden_carrot" + }, + { + "id": "minecraft:apple" + }, + { + "id": "minecraft:golden_apple" + }, + { + "id": "minecraft:enchanted_golden_apple" + }, + { + "id": "minecraft:melon_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAAAACAQAbmFtZRUAbWluZWNyYWZ0Om1lbG9uX2Jsb2NrBAkAbmFtZV9oYXNoXxSm0iYpAx8DCgBuZXR3b3JrX2lkC9rqygoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:melon_slice" + }, + { + "id": "minecraft:glistering_melon_slice" + }, + { + "id": "minecraft:sweet_berries" + }, + { + "id": "minecraft:glow_berries" + }, + { + "id": "minecraft:pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAAAACAQAbmFtZREAbWluZWNyYWZ0OnB1bXBraW4ECQBuYW1lX2hhc2gc8A3jaSzWbgMKAG5ldHdvcmtfaWRFGA+xCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:carved_pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSaAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNhcnZlZF9wdW1wa2luBAkAbmFtZV9oYXNoPu1T0MJuG90DCgBuZXR3b3JrX2lkXNNn5QoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lit_pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAAAACAQAbmFtZRUAbWluZWNyYWZ0OmxpdF9wdW1wa2luBAkAbmFtZV9oYXNo7gWtEm2uPL0DCgBuZXR3b3JrX2lki8sU4AoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:honeycomb" + }, + { + "id": "minecraft:fern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPBAAACAQAbmFtZQ4AbWluZWNyYWZ0OmZlcm4ECQBuYW1lX2hhc2iHbj3yXFn4owMKAG5ldHdvcmtfaWQKC6u7CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:large_fern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgBAAACAQAbmFtZRQAbWluZWNyYWZ0OmxhcmdlX2Zlcm4ECQBuYW1lX2hhc2gnE9sd0LzHtQMKAG5ldHdvcmtfaWTS9hG4CgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:short_grass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfAAAACAQAbmFtZRUAbWluZWNyYWZ0OnNob3J0X2dyYXNzBAkAbmFtZV9oYXNobWQghLH0bLcDCgBuZXR3b3JrX2lkJWOOqAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tall_grass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfBAAACAQAbmFtZRQAbWluZWNyYWZ0OnRhbGxfZ3Jhc3MECQBuYW1lX2hhc2ii5MyZJpv4sgMKAG5ldHdvcmtfaWRRfeH4CgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:nether_sprouts" + }, + { + "id": "minecraft:fire_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAwAACAQAbmFtZRQAbWluZWNyYWZ0OmZpcmVfY29yYWwECQBuYW1lX2hhc2hOHyyECVQVJwMKAG5ldHdvcmtfaWS9vF0UCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brain_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJyYWluX2NvcmFsBAkAbmFtZV9oYXNoRiWlLCwA2ycDCgBuZXR3b3JrX2lkrjAuhgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:bubble_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbAQJAG5hbWVfaGFzaJz6rWnl+v2qAwoAbmV0d29ya19pZImIWy0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:tube_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAQAACAQAbmFtZRQAbWluZWNyYWZ0OnR1YmVfY29yYWwECQBuYW1lX2hhc2iYa8oO/tgk7wMKAG5ldHdvcmtfaWRTfND5CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:horn_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHAwAACAQAbmFtZRQAbWluZWNyYWZ0Omhvcm5fY29yYWwECQBuYW1lX2hhc2iZnRHjZbnLPgMKAG5ldHdvcmtfaWR+GGp8CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_fire_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbAQJAG5hbWVfaGFzaEPU6tFy/latAwoAbmV0d29ya19pZNMa7V4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_brain_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAwAACAQAbmFtZRoAbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWwECQBuYW1lX2hhc2j5L6QJCISvzwMKAG5ldHdvcmtfaWQkKzeiCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKAwAACAQAbmFtZRsAbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsBAkAbmFtZV9oYXNoSTOZ/8wpeNYDCgBuZXR3b3JrX2lka6w9DAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dead_tube_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbAQJAG5hbWVfaGFzaJGjNWhlaIJeAwoAbmV0d29ya19pZO3Z0ygKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_horn_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbAQJAG5hbWVfaGFzaJBkz3qt+g2cAwoAbmV0d29ya19pZBAN+eYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:fire_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJBAAACAQAbmFtZRgAbWluZWNyYWZ0OmZpcmVfY29yYWxfZmFuBAkAbmFtZV9oYXNosOTxYYxsDLgDCgBuZXR3b3JrX2lkFKxbEgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brain_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHBAAACAQAbmFtZRkAbWluZWNyYWZ0OmJyYWluX2NvcmFsX2ZhbgQJAG5hbWVfaGFzaAi5uHizSNcqAwoAbmV0d29ya19pZFtLjNwKBgBzdGF0ZXMDEwBjb3JhbF9mYW5fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:bubble_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIBAAACAQAbmFtZRoAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hy/rX2on17DgMKAG5ldHdvcmtfaWQof60VCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:tube_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAQAACAQAbmFtZRgAbWluZWNyYWZ0OnR1YmVfY29yYWxfZmFuBAkAbmFtZV9oYXNo9pbJbo+PphIDCgBuZXR3b3JrX2lkenDTYgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:horn_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKBAAACAQAbmFtZRgAbWluZWNyYWZ0Omhvcm5fY29yYWxfZmFuBAkAbmFtZV9oYXNoA+ri6NPDkbUDCgBuZXR3b3JrX2lkezoHNwoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_fire_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNBAAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hpQO02NDxPvwMKAG5ldHdvcmtfaWTaOJgLCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_brain_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLBAAACAQAbmFtZR4AbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWxfZmFuBAkAbmFtZV9oYXNoI9/+Z4YqMhIDCgBuZXR3b3JrX2lkqYXxYgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsX2ZhbgQJAG5hbWVfaGFzaBNECtIM6VIOAwoAbmV0d29ya19pZLrNtBEKBgBzdGF0ZXMDEwBjb3JhbF9mYW5fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dead_tube_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAQAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hbBBM9jFKWvQMKAG5ldHdvcmtfaWSkJKUWCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_horn_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROBAAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbF9mYW4ECQBuYW1lX2hhc2hObElFrHfPygMKAG5ldHdvcmtfaWQ1ZxvmCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAQAACAQAbmFtZRcAbWluZWNyYWZ0OmNyaW1zb25fcm9vdHMECQBuYW1lX2hhc2j1fWgQLViv5QMKAG5ldHdvcmtfaWRLh5DXCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:warped_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAQAACAQAbmFtZRYAbWluZWNyYWZ0OndhcnBlZF9yb290cwQJAG5hbWVfaGFzaBc3WvbJOLlkAwoAbmV0d29ya19pZNLgDnAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dandelion", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQlAAAACAQAbmFtZRMAbWluZWNyYWZ0OmRhbmRlbGlvbgQJAG5hbWVfaGFzaBJ3bEUi+Nn/AwoAbmV0d29ya19pZBjjC44KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:poppy", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnBvcHB5BAkAbmFtZV9oYXNocMF8pITMbkcDCgBuZXR3b3JrX2lk8im6ywoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:blue_orchid", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9BAAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfb3JjaGlkBAkAbmFtZV9oYXNoBjz2MsgB21EDCgBuZXR3b3JrX2lk/iLsSwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:allium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+BAAACAQAbmFtZRAAbWluZWNyYWZ0OmFsbGl1bQQJAG5hbWVfaGFzaDCGQBHNDTkcAwoAbmV0d29ya19pZD9Dgr0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:azure_bluet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/BAAACAQAbmFtZRUAbWluZWNyYWZ0OmF6dXJlX2JsdWV0BAkAbmFtZV9oYXNo9N5egqMT2QcDCgBuZXR3b3JrX2lkwIgDnwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:red_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRABAAACAQAbmFtZRMAbWluZWNyYWZ0OnJlZF90dWxpcAQJAG5hbWVfaGFzaAjMi9Rd+6rhAwoAbmV0d29ya19pZAZCnt8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:orange_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBBAAACAQAbmFtZRYAbWluZWNyYWZ0Om9yYW5nZV90dWxpcAQJAG5hbWVfaGFzaP+NjxMBZ8vAAwoAbmV0d29ya19pZPYatsMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCBAAACAQAbmFtZRUAbWluZWNyYWZ0OndoaXRlX3R1bGlwBAkAbmFtZV9oYXNo5vbU4VRPh3ADCgBuZXR3b3JrX2lkok+4rQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDBAAACAQAbmFtZRQAbWluZWNyYWZ0OnBpbmtfdHVsaXAECQBuYW1lX2hhc2hxDHZa6OaNXAMKAG5ldHdvcmtfaWTiOT+VCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:oxeye_daisy", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREBAAACAQAbmFtZRUAbWluZWNyYWZ0Om94ZXllX2RhaXN5BAkAbmFtZV9oYXNoXwxsqNQTN9gDCgBuZXR3b3JrX2lkw7R7dwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cornflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFBAAACAQAbmFtZRQAbWluZWNyYWZ0OmNvcm5mbG93ZXIECQBuYW1lX2hhc2gnhyC3EeqHgAMKAG5ldHdvcmtfaWR4VrvACgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lily_of_the_valley", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGBAAACAQAbmFtZRwAbWluZWNyYWZ0OmxpbHlfb2ZfdGhlX3ZhbGxleQQJAG5hbWVfaGFzaI64TJSf9mgQAwoAbmV0d29ya19pZFE9+nwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sunflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvAAAACAQAbmFtZRMAbWluZWNyYWZ0OnN1bmZsb3dlcgQJAG5hbWVfaGFzaAMxYQLoqlZ0AwoAbmV0d29ya19pZA10iSoKBgBzdGF0ZXMBDwB1cHBlcl9ibG9ja19iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lilac", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReBAAACAQAbmFtZQ8AbWluZWNyYWZ0OmxpbGFjBAkAbmFtZV9oYXNoD3nrQJuo7NkDCgBuZXR3b3JrX2lk5W+uFAoGAHN0YXRlcwEPAHVwcGVyX2Jsb2NrX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:rose_bush", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhBAAACAQAbmFtZRMAbWluZWNyYWZ0OnJvc2VfYnVzaAQJAG5hbWVfaGFzaLoiFk8LVpGKAwoAbmV0d29ya19pZMZPv48KBgBzdGF0ZXMBDwB1cHBlcl9ibG9ja19iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:peony", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiBAAACAQAbmFtZQ8AbWluZWNyYWZ0OnBlb255BAkAbmFtZV9oYXNoR4dYc4QquPADCgBuZXR3b3JrX2lkrTe7RwoGAHN0YXRlcwEPAHVwcGVyX2Jsb2NrX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:pitcher_plant", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAwAACAQAbmFtZRcAbWluZWNyYWZ0OnBpdGNoZXJfcGxhbnQECQBuYW1lX2hhc2hRJHzsbDH+SQMKAG5ldHdvcmtfaWRnY76VCgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_petals", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkAwAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfcGV0YWxzBAkAbmFtZV9oYXNo6DQwN9SwV3QDCgBuZXR3b3JrX2lkNWneGgoGAHN0YXRlcwMGAGdyb3d0aAAAAAAIHABtaW5lY3JhZnQ6Y2FyZGluYWxfZGlyZWN0aW9uBQBzb3V0aAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:wither_rose", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAQAACAQAbmFtZRUAbWluZWNyYWZ0OndpdGhlcl9yb3NlBAkAbmFtZV9oYXNoaSKxl3I516gDCgBuZXR3b3JrX2lkATXLPwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:torchflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3AwAACAQAbmFtZRUAbWluZWNyYWZ0OnRvcmNoZmxvd2VyBAkAbmFtZV9oYXNoL+mHtElwbqQDCgBuZXR3b3JrX2lkI34O+AoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:white_dye" + }, + { + "id": "minecraft:light_gray_dye" + }, + { + "id": "minecraft:gray_dye" + }, + { + "id": "minecraft:black_dye" + }, + { + "id": "minecraft:brown_dye" + }, + { + "id": "minecraft:red_dye" + }, + { + "id": "minecraft:orange_dye" + }, + { + "id": "minecraft:yellow_dye" + }, + { + "id": "minecraft:lime_dye" + }, + { + "id": "minecraft:green_dye" + }, + { + "id": "minecraft:cyan_dye" + }, + { + "id": "minecraft:light_blue_dye" + }, + { + "id": "minecraft:blue_dye" + }, + { + "id": "minecraft:purple_dye" + }, + { + "id": "minecraft:magenta_dye" + }, + { + "id": "minecraft:pink_dye" + }, + { + "id": "minecraft:ink_sac" + }, + { + "id": "minecraft:glow_ink_sac" + }, + { + "id": "minecraft:cocoa_beans" + }, + { + "id": "minecraft:lapis_lazuli" + }, + { + "id": "minecraft:bone_meal" + }, + { + "id": "minecraft:vine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnZpbmUECQBuYW1lX2hhc2j0Sj8/XeXOLAMKAG5ldHdvcmtfaWSUkDtbCgYAc3RhdGVzAxMAdmluZV9kaXJlY3Rpb25fYml0cwAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:weeping_vines", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAQAACAQAbmFtZRcAbWluZWNyYWZ0OndlZXBpbmdfdmluZXMECQBuYW1lX2hhc2jrLgLHkQygiwMKAG5ldHdvcmtfaWQ8NHSJCgYAc3RhdGVzAxEAd2VlcGluZ192aW5lc19hZ2UAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:twisting_vines", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAgAACAQAbmFtZRgAbWluZWNyYWZ0OnR3aXN0aW5nX3ZpbmVzBAkAbmFtZV9oYXNoDYR5QgVUQJADCgBuZXR3b3JrX2lk5kYVIQoGAHN0YXRlcwMSAHR3aXN0aW5nX3ZpbmVzX2FnZQAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:waterlily", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRvAAAACAQAbmFtZRMAbWluZWNyYWZ0OndhdGVybGlseQQJAG5hbWVfaGFzaEHgC4c1SXg0AwoAbmV0d29ya19pZOOerp8KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:seagrass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAQAACAQAbmFtZRIAbWluZWNyYWZ0OnNlYWdyYXNzBAkAbmFtZV9oYXNoHSBFtoHdWxIDCgBuZXR3b3JrX2lkd3lhEAoGAHN0YXRlcwgOAHNlYV9ncmFzc190eXBlBwBkZWZhdWx0AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:kelp" + }, + { + "id": "minecraft:deadbush", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAAAACAQAbmFtZRIAbWluZWNyYWZ0OmRlYWRidXNoBAkAbmFtZV9oYXNoPFODe4IScnYDCgBuZXR3b3JrX2lkVfnl+goGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:bamboo", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhbWJvbwQJAG5hbWVfaGFzaBgpGmyzhedCAwoAbmV0d29ya19pZIZv1nYKBgBzdGF0ZXMBBwBhZ2VfYml0AAgQAGJhbWJvb19sZWFmX3NpemUJAG5vX2xlYXZlcwgWAGJhbWJvb19zdGFsa190aGlja25lc3MEAHRoaW4AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:snow", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnNub3cECQBuYW1lX2hhc2gVHr5XXdETWAMKAG5ldHdvcmtfaWQ0zCeHCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAAAACAQAbmFtZQ0AbWluZWNyYWZ0OmljZQQJAG5hbWVfaGFzaNF26f+uUT29AwoAbmV0d29ya19pZOUMaQYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:packed_ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAAAACAQAbmFtZRQAbWluZWNyYWZ0OnBhY2tlZF9pY2UECQBuYW1lX2hhc2hk4bu123ZrFgMKAG5ldHdvcmtfaWTr/ooaCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blue_ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKAQAACAQAbmFtZRIAbWluZWNyYWZ0OmJsdWVfaWNlBAkAbmFtZV9oYXNo+EKxYgFhKcgDCgBuZXR3b3JrX2lkxfsA8goGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:snow_layer", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAAAACAQAbmFtZRQAbWluZWNyYWZ0OnNub3dfbGF5ZXIECQBuYW1lX2hhc2hXka6atMYUCQMKAG5ldHdvcmtfaWRCrIPcCgYAc3RhdGVzAQsAY292ZXJlZF9iaXQAAwYAaGVpZ2h0AAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pointed_dripstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzAgAACAQAbmFtZRsAbWluZWNyYWZ0OnBvaW50ZWRfZHJpcHN0b25lBAkAbmFtZV9oYXNoJMISzmHQgt8DCgBuZXR3b3JrX2lkbWrtYgoGAHN0YXRlcwgTAGRyaXBzdG9uZV90aGlja25lc3MDAHRpcAEHAGhhbmdpbmcBAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dripstone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AgAACAQAbmFtZRkAbWluZWNyYWZ0OmRyaXBzdG9uZV9ibG9jawQJAG5hbWVfaGFzaIIXnEqY77YsAwoAbmV0d29ya19pZMZi2kwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:moss_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAgAACAQAbmFtZRUAbWluZWNyYWZ0Om1vc3NfY2FycGV0BAkAbmFtZV9oYXNo/NEDxRPTshYDCgBuZXR3b3JrX2lkaGG3QwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:moss_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/AgAACAQAbmFtZRQAbWluZWNyYWZ0Om1vc3NfYmxvY2sECQBuYW1lX2hhc2iovcsPUYX2tgMKAG5ldHdvcmtfaWT3JSbfCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dirt_with_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9AgAACAQAbmFtZRkAbWluZWNyYWZ0OmRpcnRfd2l0aF9yb290cwQJAG5hbWVfaGFzaLCNDYPviDCIAwoAbmV0d29ya19pZNCkwzoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:hanging_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+AgAACAQAbmFtZRcAbWluZWNyYWZ0Omhhbmdpbmdfcm9vdHMECQBuYW1lX2hhc2jaXn+Y5UZpDAMKAG5ldHdvcmtfaWRU4c2vCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:mangrove_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hbmdyb3ZlX3Jvb3RzBAkAbmFtZV9oYXNoa786PzQGZ6kDCgBuZXR3b3JrX2lklA0AHgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:muddy_mangrove_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAgAACAQAbmFtZR4AbWluZWNyYWZ0Om11ZGR5X21hbmdyb3ZlX3Jvb3RzBAkAbmFtZV9oYXNo9YApdHpo1RkDCgBuZXR3b3JrX2lkH0Oc4woGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:big_dripleaf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJpZ19kcmlwbGVhZgQJAG5hbWVfaGFzaGBEhXjo6qSdAwoAbmV0d29ya19pZMETsb8KBgBzdGF0ZXMBEQBiaWdfZHJpcGxlYWZfaGVhZAEIEQBiaWdfZHJpcGxlYWZfdGlsdAQAbm9uZQgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:small_dripleaf_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAgAACAQAbmFtZR4AbWluZWNyYWZ0OnNtYWxsX2RyaXBsZWFmX2Jsb2NrBAkAbmFtZV9oYXNojxRAgXP9uWADCgBuZXR3b3JrX2lkozbVPwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24EAGVhc3QBDwB1cHBlcl9ibG9ja19iaXQBAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spore_blossom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRAAgAACAQAbmFtZRcAbWluZWNyYWZ0OnNwb3JlX2Jsb3Nzb20ECQBuYW1lX2hhc2il3U72Gbco2gMKAG5ldHdvcmtfaWSbbbgcCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:azalea", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAgAACAQAbmFtZRAAbWluZWNyYWZ0OmF6YWxlYQQJAG5hbWVfaGFzaNyUl+BW9JrBAwoAbmV0d29ya19pZO/XZtQKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:flowering_azalea", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAgAACAQAbmFtZRoAbWluZWNyYWZ0OmZsb3dlcmluZ19hemFsZWEECQBuYW1lX2hhc2ie9r33wz8kiwMKAG5ldHdvcmtfaWQ3ij0VCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:glow_lichen", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSaAgAACAQAbmFtZRUAbWluZWNyYWZ0Omdsb3dfbGljaGVuBAkAbmFtZV9oYXNobyPUrIYlo44DCgBuZXR3b3JrX2lkCh8lSAoGAHN0YXRlcwMZAG11bHRpX2ZhY2VfZGlyZWN0aW9uX2JpdHM/AAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:amethyst_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAgAACAQAbmFtZRgAbWluZWNyYWZ0OmFtZXRoeXN0X2Jsb2NrBAkAbmFtZV9oYXNob+JK1iiAthcDCgBuZXR3b3JrX2lk8HtpzgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:budding_amethyst", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHAgAACAQAbmFtZRoAbWluZWNyYWZ0OmJ1ZGRpbmdfYW1ldGh5c3QECQBuYW1lX2hhc2gJvAwfI14fxgMKAG5ldHdvcmtfaWTQYqfACgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:amethyst_cluster", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAgAACAQAbmFtZRoAbWluZWNyYWZ0OmFtZXRoeXN0X2NsdXN0ZXIECQBuYW1lX2hhc2jK82S88Jgm8wMKAG5ldHdvcmtfaWSCPMPGCgYAc3RhdGVzCBQAbWluZWNyYWZ0OmJsb2NrX2ZhY2UCAHVwAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:large_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAgAACAQAbmFtZRwAbWluZWNyYWZ0OmxhcmdlX2FtZXRoeXN0X2J1ZAQJAG5hbWVfaGFzaAHhdpWD+sd5AwoAbmV0d29ya19pZKkQxOcKBgBzdGF0ZXMIFABtaW5lY3JhZnQ6YmxvY2tfZmFjZQIAdXAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:medium_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKAgAACAQAbmFtZR0AbWluZWNyYWZ0Om1lZGl1bV9hbWV0aHlzdF9idWQECQBuYW1lX2hhc2g5lBGtC0DzZQMKAG5ldHdvcmtfaWSYiP4gCgYAc3RhdGVzCBQAbWluZWNyYWZ0OmJsb2NrX2ZhY2UCAHVwAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:small_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLAgAACAQAbmFtZRwAbWluZWNyYWZ0OnNtYWxsX2FtZXRoeXN0X2J1ZAQJAG5hbWVfaGFzaEnb4+q9PO4YAwoAbmV0d29ya19pZGWzxrQKBgBzdGF0ZXMIFABtaW5lY3JhZnQ6YmxvY2tfZmFjZQIAdXAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAgAACAQAbmFtZQ4AbWluZWNyYWZ0OnR1ZmYECQBuYW1lX2hhc2h1Rwc1XYsBGwMKAG5ldHdvcmtfaWRwQGn0CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:tuff_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAwAACAQAbmFtZRUAbWluZWNyYWZ0OnR1ZmZfc3RhaXJzBAkAbmFtZV9oYXNoKjyNUBjcfZsDCgBuZXR3b3JrX2lk+LsycgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tuff_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAwAACAQAbmFtZRMAbWluZWNyYWZ0OnR1ZmZfc2xhYgQJAG5hbWVfaGFzaIhCGdlIsnMUAwoAbmV0d29ya19pZN1dUL4KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:tuff_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAwAACAQAbmFtZRMAbWluZWNyYWZ0OnR1ZmZfd2FsbAQJAG5hbWVfaGFzaMyeeu1IRf03AwoAbmV0d29ya19pZDkIrosKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chiseled_tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoaXNlbGVkX3R1ZmYECQBuYW1lX2hhc2iVliOT8OTQ9AMKAG5ldHdvcmtfaWTLNKOiCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAwAACAQAbmFtZRcAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmYECQBuYW1lX2hhc2hyaLe/KEVZ0gMKAG5ldHdvcmtfaWTcX3NrCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_tuff_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTuAwAACAQAbmFtZR4AbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfc3RhaXJzBAkAbmFtZV9oYXNo8yuah8QI1dcDCgBuZXR3b3JrX2lkjLoU4AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:polished_tuff_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTsAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfc2xhYgQJAG5hbWVfaGFzaLXdb48YvAsHAwoAbmV0d29ya19pZAnJ7W0KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_tuff_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfd2FsbAQJAG5hbWVfaGFzaJVZj6QYWXUrAwoAbmV0d29ya19pZLU7dooKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tuff_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAwAACAQAbmFtZRUAbWluZWNyYWZ0OnR1ZmZfYnJpY2tzBAkAbmFtZV9oYXNo/hbQ+mXSK7wDCgBuZXR3b3JrX2lk6gmIwQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tuff_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT0AwAACAQAbmFtZRsAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNoWJpkAurUfKwDCgBuZXR3b3JrX2lkUMcjiwoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tuff_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTyAwAACAQAbmFtZRkAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaLqPMjVCv5dIAwoAbmV0d29ya19pZOmeRhcKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:tuff_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT1AwAACAQAbmFtZRkAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfd2FsbAQJAG5hbWVfaGFzaIL0IyNCOsonAwoAbmV0d29ya19pZJW4T5UKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chiseled_tuff_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT2AwAACAQAbmFtZR4AbWluZWNyYWZ0OmNoaXNlbGVkX3R1ZmZfYnJpY2tzBAkAbmFtZV9oYXNo3oQw6gmxYuADCgBuZXR3b3JrX2lkm3D8AgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:calcite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAgAACAQAbmFtZREAbWluZWNyYWZ0OmNhbGNpdGUECQBuYW1lX2hhc2ixKLu8ZIdzDQMKAG5ldHdvcmtfaWQlSbJDCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:chicken" + }, + { + "id": "minecraft:porkchop" + }, + { + "id": "minecraft:beef" + }, + { + "id": "minecraft:mutton" + }, + { + "id": "minecraft:rabbit" + }, + { + "id": "minecraft:cod" + }, + { + "id": "minecraft:salmon" + }, + { + "id": "minecraft:tropical_fish" + }, + { + "id": "minecraft:pufferfish" + }, + { + "id": "minecraft:brown_mushroom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAAAACAQAbmFtZRgAbWluZWNyYWZ0OmJyb3duX211c2hyb29tBAkAbmFtZV9oYXNonYw/FO78WDoDCgBuZXR3b3JrX2lkLh1OXAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:red_mushroom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAAAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZF9tdXNocm9vbQQJAG5hbWVfaGFzaPpzJua7669xAwoAbmV0d29ya19pZCvWPYkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_fungus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fZnVuZ3VzBAkAbmFtZV9oYXNolIcCUuFM2u0DCgBuZXR3b3JrX2lkD2NN0QoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_fungus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9mdW5ndXMECQBuYW1lX2hhc2gq8bSnRVTAFgMKAG5ldHdvcmtfaWTkwS+rCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkdOMhDAoGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cw4AAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAAAACAQAbmFtZRwAbWluZWNyYWZ0OnJlZF9tdXNocm9vbV9ibG9jawQJAG5hbWVfaGFzaJTTyJbth9M9AwoAbmV0d29ya19pZM+AyboKBgBzdGF0ZXMDEgBodWdlX211c2hyb29tX2JpdHMOAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkbdt3CAoGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cw8AAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkSrMl9goGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cwAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:egg" + }, + { + "id": "minecraft:sugar_cane" + }, + { + "id": "minecraft:sugar" + }, + { + "id": "minecraft:rotten_flesh" + }, + { + "id": "minecraft:bone" + }, + { + "id": "minecraft:web", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAAAACAQAbmFtZQ0AbWluZWNyYWZ0OndlYgQJAG5hbWVfaGFzaA4GKQCvG4i9AwoAbmV0d29ya19pZApt+jgKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:spider_eye" + }, + { + "id": "minecraft:mob_spawner", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0AAAACAQAbmFtZRUAbWluZWNyYWZ0Om1vYl9zcGF3bmVyBAkAbmFtZV9oYXNoNwGrCV/Fkh8DCgBuZXR3b3JrX2lkM1wTmgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:trial_spawner", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AgAACAQAbmFtZRcAbWluZWNyYWZ0OnRyaWFsX3NwYXduZXIECQBuYW1lX2hhc2iNLRPB4ACz+QMKAG5ldHdvcmtfaWTWFYHGCgYAc3RhdGVzAQcAb21pbm91cwADEwB0cmlhbF9zcGF3bmVyX3N0YXRlAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:vault", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AgAACAQAbmFtZQ8AbWluZWNyYWZ0OnZhdWx0BAkAbmFtZV9oYXNoCAp9n3IAyqcDCgBuZXR3b3JrX2lk6/P+vwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAQcAb21pbm91cwAICwB2YXVsdF9zdGF0ZQgAaW5hY3RpdmUAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:end_portal_frame", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AAAACAQAbmFtZRoAbWluZWNyYWZ0OmVuZF9wb3J0YWxfZnJhbWUECQBuYW1lX2hhc2gqofyUIjGOpQMKAG5ldHdvcmtfaWRbGHf8CgYAc3RhdGVzARIAZW5kX3BvcnRhbF9leWVfYml0AAgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:infested_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAAAACAQAbmFtZRgAbWluZWNyYWZ0OmluZmVzdGVkX3N0b25lBAkAbmFtZV9oYXNoxnRcHDu4zqQDCgBuZXR3b3JrX2lkpfcnsgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:infested_cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZBAAACAQAbmFtZR4AbWluZWNyYWZ0OmluZmVzdGVkX2NvYmJsZXN0b25lBAkAbmFtZV9oYXNoy+LVCKG2kVMDCgBuZXR3b3JrX2lkpn+icAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:infested_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaBAAACAQAbmFtZR8AbWluZWNyYWZ0OmluZmVzdGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaBMnals7a32CAwoAbmV0d29ya19pZNHi2UYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:infested_mossy_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbBAAACAQAbmFtZSUAbWluZWNyYWZ0OmluZmVzdGVkX21vc3N5X3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaAmJk+HmVq0rAwoAbmV0d29ya19pZAVH8/sKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:infested_cracked_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcBAAACAQAbmFtZScAbWluZWNyYWZ0OmluZmVzdGVkX2NyYWNrZWRfc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNoMyc60XcfcyoDCgBuZXR3b3JrX2lkaW+kbQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:infested_chiseled_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdBAAACAQAbmFtZSgAbWluZWNyYWZ0OmluZmVzdGVkX2NoaXNlbGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaNUvNIIg9dZbAwoAbmV0d29ya19pZCajGicKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:infested_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAgAACAQAbmFtZRwAbWluZWNyYWZ0OmluZmVzdGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaICF2VYccxF1AwoAbmV0d29ya19pZDa/624KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dragon_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AAAACAQAbmFtZRQAbWluZWNyYWZ0OmRyYWdvbl9lZ2cECQBuYW1lX2hhc2inMzXrV+/e1wMKAG5ldHdvcmtfaWTgO1yRCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:turtle_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAQAACAQAbmFtZRQAbWluZWNyYWZ0OnR1cnRsZV9lZ2cECQBuYW1lX2hhc2iwSRcxOJIJ9gMKAG5ldHdvcmtfaWSIRNUhCgYAc3RhdGVzCA0AY3JhY2tlZF9zdGF0ZQkAbm9fY3JhY2tzCBAAdHVydGxlX2VnZ19jb3VudAcAb25lX2VnZwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sniffer_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTAwAACAQAbmFtZRUAbWluZWNyYWZ0OnNuaWZmZXJfZWdnBAkAbmFtZV9oYXNoY1lozc8lPcYDCgBuZXR3b3JrX2lk7yb/2QoGAHN0YXRlcwgNAGNyYWNrZWRfc3RhdGUJAG5vX2NyYWNrcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:frog_spawn", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAgAACAQAbmFtZRQAbWluZWNyYWZ0OmZyb2dfc3Bhd24ECQBuYW1lX2hhc2iWmd7idp3ZZwMKAG5ldHdvcmtfaWRFzJudCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:pearlescent_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAgAACAQAbmFtZR8AbWluZWNyYWZ0OnBlYXJsZXNjZW50X2Zyb2dsaWdodAQJAG5hbWVfaGFzaKkcFRyycYGyAwoAbmV0d29ya19pZJqYakAKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:verdant_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAgAACAQAbmFtZRsAbWluZWNyYWZ0OnZlcmRhbnRfZnJvZ2xpZ2h0BAkAbmFtZV9oYXNoA+eXuTBohrQDCgBuZXR3b3JrX2lkDIVnsQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:ochre_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAgAACAQAbmFtZRkAbWluZWNyYWZ0Om9jaHJlX2Zyb2dsaWdodAQJAG5hbWVfaGFzaMY59kjPe+c3AwoAbmV0d29ya19pZO2TD50KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chicken_spawn_egg" + }, + { + "id": "minecraft:bee_spawn_egg" + }, + { + "id": "minecraft:cow_spawn_egg" + }, + { + "id": "minecraft:pig_spawn_egg" + }, + { + "id": "minecraft:sheep_spawn_egg" + }, + { + "id": "minecraft:wolf_spawn_egg" + }, + { + "id": "minecraft:polar_bear_spawn_egg" + }, + { + "id": "minecraft:ocelot_spawn_egg" + }, + { + "id": "minecraft:cat_spawn_egg" + }, + { + "id": "minecraft:mooshroom_spawn_egg" + }, + { + "id": "minecraft:bat_spawn_egg" + }, + { + "id": "minecraft:parrot_spawn_egg" + }, + { + "id": "minecraft:rabbit_spawn_egg" + }, + { + "id": "minecraft:llama_spawn_egg" + }, + { + "id": "minecraft:horse_spawn_egg" + }, + { + "id": "minecraft:donkey_spawn_egg" + }, + { + "id": "minecraft:mule_spawn_egg" + }, + { + "id": "minecraft:skeleton_horse_spawn_egg" + }, + { + "id": "minecraft:zombie_horse_spawn_egg" + }, + { + "id": "minecraft:tropical_fish_spawn_egg" + }, + { + "id": "minecraft:cod_spawn_egg" + }, + { + "id": "minecraft:pufferfish_spawn_egg" + }, + { + "id": "minecraft:salmon_spawn_egg" + }, + { + "id": "minecraft:dolphin_spawn_egg" + }, + { + "id": "minecraft:turtle_spawn_egg" + }, + { + "id": "minecraft:panda_spawn_egg" + }, + { + "id": "minecraft:fox_spawn_egg" + }, + { + "id": "minecraft:creeper_spawn_egg" + }, + { + "id": "minecraft:enderman_spawn_egg" + }, + { + "id": "minecraft:silverfish_spawn_egg" + }, + { + "id": "minecraft:skeleton_spawn_egg" + }, + { + "id": "minecraft:wither_skeleton_spawn_egg" + }, + { + "id": "minecraft:stray_spawn_egg" + }, + { + "id": "minecraft:slime_spawn_egg" + }, + { + "id": "minecraft:spider_spawn_egg" + }, + { + "id": "minecraft:zombie_spawn_egg" + }, + { + "id": "minecraft:zombie_pigman_spawn_egg" + }, + { + "id": "minecraft:husk_spawn_egg" + }, + { + "id": "minecraft:drowned_spawn_egg" + }, + { + "id": "minecraft:squid_spawn_egg" + }, + { + "id": "minecraft:glow_squid_spawn_egg" + }, + { + "id": "minecraft:cave_spider_spawn_egg" + }, + { + "id": "minecraft:witch_spawn_egg" + }, + { + "id": "minecraft:guardian_spawn_egg" + }, + { + "id": "minecraft:elder_guardian_spawn_egg" + }, + { + "id": "minecraft:endermite_spawn_egg" + }, + { + "id": "minecraft:magma_cube_spawn_egg" + }, + { + "id": "minecraft:strider_spawn_egg" + }, + { + "id": "minecraft:hoglin_spawn_egg" + }, + { + "id": "minecraft:piglin_spawn_egg" + }, + { + "id": "minecraft:zoglin_spawn_egg" + }, + { + "id": "minecraft:piglin_brute_spawn_egg" + }, + { + "id": "minecraft:goat_spawn_egg" + }, + { + "id": "minecraft:axolotl_spawn_egg" + }, + { + "id": "minecraft:warden_spawn_egg" + }, + { + "id": "minecraft:allay_spawn_egg" + }, + { + "id": "minecraft:frog_spawn_egg" + }, + { + "id": "minecraft:tadpole_spawn_egg" + }, + { + "id": "minecraft:trader_llama_spawn_egg" + }, + { + "id": "minecraft:camel_spawn_egg" + }, + { + "id": "minecraft:ghast_spawn_egg" + }, + { + "id": "minecraft:blaze_spawn_egg" + }, + { + "id": "minecraft:shulker_spawn_egg" + }, + { + "id": "minecraft:vindicator_spawn_egg" + }, + { + "id": "minecraft:evoker_spawn_egg" + }, + { + "id": "minecraft:vex_spawn_egg" + }, + { + "id": "minecraft:villager_spawn_egg" + }, + { + "id": "minecraft:wandering_trader_spawn_egg" + }, + { + "id": "minecraft:zombie_villager_spawn_egg" + }, + { + "id": "minecraft:phantom_spawn_egg" + }, + { + "id": "minecraft:pillager_spawn_egg" + }, + { + "id": "minecraft:ravager_spawn_egg" + }, + { + "id": "minecraft:iron_golem_spawn_egg" + }, + { + "id": "minecraft:snow_golem_spawn_egg" + }, + { + "id": "minecraft:sniffer_spawn_egg" + }, + { + "id": "minecraft:breeze_spawn_egg" + }, + { + "id": "minecraft:armadillo_spawn_egg" + }, + { + "id": "minecraft:bogged_spawn_egg" + }, + { + "id": "minecraft:obsidian", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxAAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ic2lkaWFuBAkAbmFtZV9oYXNoiz4qrb8QjyEDCgBuZXR3b3JrX2lkuqnPpQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:crying_obsidian", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAgAACAQAbmFtZRkAbWluZWNyYWZ0OmNyeWluZ19vYnNpZGlhbgQJAG5hbWVfaGFzaKT0JlA7Z1K+AwoAbmV0d29ya19pZCjbPV4KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bedrock", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAAAACAQAbmFtZREAbWluZWNyYWZ0OmJlZHJvY2sECQBuYW1lX2hhc2hWfFrh4LVtxwMKAG5ldHdvcmtfaWT7fKz1CgYAc3RhdGVzAQ4AaW5maW5pYnVybl9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:soul_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAAAACAQAbmFtZRMAbWluZWNyYWZ0OnNvdWxfc2FuZAQJAG5hbWVfaGFzaMaf+bccu+KTAwoAbmV0d29ya19pZBQSHrMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:magma", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAAAACAQAbmFtZQ8AbWluZWNyYWZ0Om1hZ21hBAkAbmFtZV9oYXNoqyTjKaIsWfYDCgBuZXR3b3JrX2lkyfWAZgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:nether_wart" + }, + { + "id": "minecraft:end_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AAAACAQAbmFtZRMAbWluZWNyYWZ0OmVuZF9zdG9uZQQJAG5hbWVfaGFzaH1J9jA39GJNAwoAbmV0d29ya19pZFeFQ7UKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chorus_flower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAAAACAQAbmFtZRcAbWluZWNyYWZ0OmNob3J1c19mbG93ZXIECQBuYW1lX2hhc2iMpSodli5uawMKAG5ldHdvcmtfaWRnd1ZWCgYAc3RhdGVzAwMAYWdlAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:chorus_plant", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAAAACAQAbmFtZRYAbWluZWNyYWZ0OmNob3J1c19wbGFudAQJAG5hbWVfaGFzaJhSrmNGKwaMAwoAbmV0d29ya19pZA3uVqMKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chorus_fruit" + }, + { + "id": "minecraft:popped_chorus_fruit" + }, + { + "id": "minecraft:sponge", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAAAACAQAbmFtZRAAbWluZWNyYWZ0OnNwb25nZQQJAG5hbWVfaGFzaLrd2ScYRDMiAwoAbmV0d29ya19pZF01rO0KBgBzdGF0ZXMICwBzcG9uZ2VfdHlwZQMAZHJ5AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:sponge", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAAAACAQAbmFtZRAAbWluZWNyYWZ0OnNwb25nZQQJAG5hbWVfaGFzaLrd2ScYRDMiAwoAbmV0d29ya19pZPiOc4QKBgBzdGF0ZXMICwBzcG9uZ2VfdHlwZQMAd2V0AAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:tube_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAQAACAQAbmFtZRoAbWluZWNyYWZ0OnR1YmVfY29yYWxfYmxvY2sECQBuYW1lX2hhc2iGkaiR7Eot4wMKAG5ldHdvcmtfaWQPNJ6sCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:brain_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQBAAACAQAbmFtZRsAbWluZWNyYWZ0OmJyYWluX2NvcmFsX2Jsb2NrBAkAbmFtZV9oYXNoeDNAK18yUo4DCgBuZXR3b3JrX2lkloN1vgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:bubble_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRBAAACAQAbmFtZRwAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaAI2mwMlvcNbAwoAbmV0d29ya19pZBlkxKIKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:fire_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSBAAACAQAbmFtZRoAbWluZWNyYWZ0OmZpcmVfY29yYWxfYmxvY2sECQBuYW1lX2hhc2gg1gLeXLmKaAMKAG5ldHdvcmtfaWSp3W57CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:horn_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTBAAACAQAbmFtZRoAbWluZWNyYWZ0Omhvcm5fY29yYWxfYmxvY2sECQBuYW1lX2hhc2hnZSLRWUwGhAMKAG5ldHdvcmtfaWRSK6ccCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_tube_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaB9+lY3hAkNNAwoAbmV0d29ya19pZF0hKKYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_brain_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVBAAACAQAbmFtZSAAbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWxfYmxvY2sECQBuYW1lX2hhc2iHyDn52AO8uwMKAG5ldHdvcmtfaWQw7yCaCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWBAAACAQAbmFtZSEAbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsX2Jsb2NrBAkAbmFtZV9oYXNotwkk/ITrsjADCgBuZXR3b3JrX2lk56mXUgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dead_fire_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaG0qHxbIrBEyAwoAbmV0d29ya19pZFvnH88KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:dead_horn_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaL7D8bu4Fm+0AwoAbmV0d29ya19pZEALRLoKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sculk", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAgAACAQAbmFtZQ8AbWluZWNyYWZ0OnNjdWxrBAkAbmFtZV9oYXNo2Lq7T5yQF8kDCgBuZXR3b3JrX2lkyqUPPgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sculk_vein", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAgAACAQAbmFtZRQAbWluZWNyYWZ0OnNjdWxrX3ZlaW4ECQBuYW1lX2hhc2gJUdhVooV4zwMKAG5ldHdvcmtfaWSUfn1XCgYAc3RhdGVzAxkAbXVsdGlfZmFjZV9kaXJlY3Rpb25fYml0cwAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:sculk_catalyst", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAgAACAQAbmFtZRgAbWluZWNyYWZ0OnNjdWxrX2NhdGFseXN0BAkAbmFtZV9oYXNo+gCpbrCHST4DCgBuZXR3b3JrX2lkMJ2n/woGAHN0YXRlcwEFAGJsb29tAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sculk_shrieker", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTMAgAACAQAbmFtZRgAbWluZWNyYWZ0OnNjdWxrX3Nocmlla2VyBAkAbmFtZV9oYXNo5OXtyObniQ4DCgBuZXR3b3JrX2lkxapoNAoGAHN0YXRlcwEGAGFjdGl2ZQABCgBjYW5fc3VtbW9uAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sculk_sensor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAgAACAQAbmFtZRYAbWluZWNyYWZ0OnNjdWxrX3NlbnNvcgQJAG5hbWVfaGFzaCkmHreeTgNnAwoAbmV0d29ya19pZLj2WPcKBgBzdGF0ZXMDEgBzY3Vsa19zZW5zb3JfcGhhc2UAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:calibrated_sculk_sensor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAwAACAQAbmFtZSEAbWluZWNyYWZ0OmNhbGlicmF0ZWRfc2N1bGtfc2Vuc29yBAkAbmFtZV9oYXNoffAcXXN/iJUDCgBuZXR3b3JrX2lkwOx3QQoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAxIAc2N1bGtfc2Vuc29yX3BoYXNlAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:reinforced_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTRAgAACAQAbmFtZR4AbWluZWNyYWZ0OnJlaW5mb3JjZWRfZGVlcHNsYXRlBAkAbmFtZV9oYXNoldDmj91EapQDCgBuZXR3b3JrX2lkHIt+aQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:leather_helmet" + }, + { + "id": "minecraft:chainmail_helmet" + }, + { + "id": "minecraft:iron_helmet" + }, + { + "id": "minecraft:golden_helmet" + }, + { + "id": "minecraft:diamond_helmet" + }, + { + "id": "minecraft:netherite_helmet" + }, + { + "id": "minecraft:leather_chestplate" + }, + { + "id": "minecraft:chainmail_chestplate" + }, + { + "id": "minecraft:iron_chestplate" + }, + { + "id": "minecraft:golden_chestplate" + }, + { + "id": "minecraft:diamond_chestplate" + }, + { + "id": "minecraft:netherite_chestplate" + }, + { + "id": "minecraft:leather_leggings" + }, + { + "id": "minecraft:chainmail_leggings" + }, + { + "id": "minecraft:iron_leggings" + }, + { + "id": "minecraft:golden_leggings" + }, + { + "id": "minecraft:diamond_leggings" + }, + { + "id": "minecraft:netherite_leggings" + }, + { + "id": "minecraft:leather_boots" + }, + { + "id": "minecraft:chainmail_boots" + }, + { + "id": "minecraft:iron_boots" + }, + { + "id": "minecraft:golden_boots" + }, + { + "id": "minecraft:diamond_boots" + }, + { + "id": "minecraft:netherite_boots" + }, + { + "id": "minecraft:wooden_sword" + }, + { + "id": "minecraft:stone_sword" + }, + { + "id": "minecraft:iron_sword" + }, + { + "id": "minecraft:golden_sword" + }, + { + "id": "minecraft:diamond_sword" + }, + { + "id": "minecraft:netherite_sword" + }, + { + "id": "minecraft:wooden_axe" + }, + { + "id": "minecraft:stone_axe" + }, + { + "id": "minecraft:iron_axe" + }, + { + "id": "minecraft:golden_axe" + }, + { + "id": "minecraft:diamond_axe" + }, + { + "id": "minecraft:netherite_axe" + }, + { + "id": "minecraft:wooden_pickaxe" + }, + { + "id": "minecraft:stone_pickaxe" + }, + { + "id": "minecraft:iron_pickaxe" + }, + { + "id": "minecraft:golden_pickaxe" + }, + { + "id": "minecraft:diamond_pickaxe" + }, + { + "id": "minecraft:netherite_pickaxe" + }, + { + "id": "minecraft:wooden_shovel" + }, + { + "id": "minecraft:stone_shovel" + }, + { + "id": "minecraft:iron_shovel" + }, + { + "id": "minecraft:golden_shovel" + }, + { + "id": "minecraft:diamond_shovel" + }, + { + "id": "minecraft:netherite_shovel" + }, + { + "id": "minecraft:wooden_hoe" + }, + { + "id": "minecraft:stone_hoe" + }, + { + "id": "minecraft:iron_hoe" + }, + { + "id": "minecraft:golden_hoe" + }, + { + "id": "minecraft:diamond_hoe" + }, + { + "id": "minecraft:netherite_hoe" + }, + { + "id": "minecraft:bow" + }, + { + "id": "minecraft:crossbow" + }, + { + "id": "minecraft:mace" + }, + { + "id": "minecraft:arrow" + }, + { + "id": "minecraft:arrow", + "damage": 6 + }, + { + "id": "minecraft:arrow", + "damage": 7 + }, + { + "id": "minecraft:arrow", + "damage": 8 + }, + { + "id": "minecraft:arrow", + "damage": 9 + }, + { + "id": "minecraft:arrow", + "damage": 10 + }, + { + "id": "minecraft:arrow", + "damage": 11 + }, + { + "id": "minecraft:arrow", + "damage": 12 + }, + { + "id": "minecraft:arrow", + "damage": 13 + }, + { + "id": "minecraft:arrow", + "damage": 14 + }, + { + "id": "minecraft:arrow", + "damage": 15 + }, + { + "id": "minecraft:arrow", + "damage": 16 + }, + { + "id": "minecraft:arrow", + "damage": 17 + }, + { + "id": "minecraft:arrow", + "damage": 18 + }, + { + "id": "minecraft:arrow", + "damage": 19 + }, + { + "id": "minecraft:arrow", + "damage": 20 + }, + { + "id": "minecraft:arrow", + "damage": 21 + }, + { + "id": "minecraft:arrow", + "damage": 22 + }, + { + "id": "minecraft:arrow", + "damage": 23 + }, + { + "id": "minecraft:arrow", + "damage": 24 + }, + { + "id": "minecraft:arrow", + "damage": 25 + }, + { + "id": "minecraft:arrow", + "damage": 26 + }, + { + "id": "minecraft:arrow", + "damage": 27 + }, + { + "id": "minecraft:arrow", + "damage": 28 + }, + { + "id": "minecraft:arrow", + "damage": 29 + }, + { + "id": "minecraft:arrow", + "damage": 30 + }, + { + "id": "minecraft:arrow", + "damage": 31 + }, + { + "id": "minecraft:arrow", + "damage": 32 + }, + { + "id": "minecraft:arrow", + "damage": 33 + }, + { + "id": "minecraft:arrow", + "damage": 34 + }, + { + "id": "minecraft:arrow", + "damage": 35 + }, + { + "id": "minecraft:arrow", + "damage": 36 + }, + { + "id": "minecraft:arrow", + "damage": 37 + }, + { + "id": "minecraft:arrow", + "damage": 38 + }, + { + "id": "minecraft:arrow", + "damage": 39 + }, + { + "id": "minecraft:arrow", + "damage": 40 + }, + { + "id": "minecraft:arrow", + "damage": 41 + }, + { + "id": "minecraft:arrow", + "damage": 42 + }, + { + "id": "minecraft:arrow", + "damage": 43 + }, + { + "id": "minecraft:arrow", + "damage": 44 + }, + { + "id": "minecraft:arrow", + "damage": 45 + }, + { + "id": "minecraft:arrow", + "damage": 46 + }, + { + "id": "minecraft:arrow", + "damage": 47 + }, + { + "id": "minecraft:ominous_bottle" + }, + { + "id": "minecraft:ominous_bottle", + "damage": 1 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 2 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 3 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 4 + }, + { + "id": "minecraft:shield" + }, + { + "id": "minecraft:cooked_chicken" + }, + { + "id": "minecraft:cooked_porkchop" + }, + { + "id": "minecraft:cooked_beef" + }, + { + "id": "minecraft:cooked_mutton" + }, + { + "id": "minecraft:cooked_rabbit" + }, + { + "id": "minecraft:cooked_cod" + }, + { + "id": "minecraft:cooked_salmon" + }, + { + "id": "minecraft:bread" + }, + { + "id": "minecraft:mushroom_stew" + }, + { + "id": "minecraft:beetroot_soup" + }, + { + "id": "minecraft:rabbit_stew" + }, + { + "id": "minecraft:baked_potato" + }, + { + "id": "minecraft:cookie" + }, + { + "id": "minecraft:pumpkin_pie" + }, + { + "id": "minecraft:cake" + }, + { + "id": "minecraft:dried_kelp" + }, + { + "id": "minecraft:fishing_rod" + }, + { + "id": "minecraft:carrot_on_a_stick" + }, + { + "id": "minecraft:warped_fungus_on_a_stick" + }, + { + "id": "minecraft:snowball" + }, + { + "id": "minecraft:wind_charge" + }, + { + "id": "minecraft:shears" + }, + { + "id": "minecraft:flint_and_steel" + }, + { + "id": "minecraft:lead" + }, + { + "id": "minecraft:clock" + }, + { + "id": "minecraft:compass" + }, + { + "id": "minecraft:recovery_compass" + }, + { + "id": "minecraft:goat_horn" + }, + { + "id": "minecraft:goat_horn", + "damage": 1 + }, + { + "id": "minecraft:goat_horn", + "damage": 2 + }, + { + "id": "minecraft:goat_horn", + "damage": 3 + }, + { + "id": "minecraft:goat_horn", + "damage": 4 + }, + { + "id": "minecraft:goat_horn", + "damage": 5 + }, + { + "id": "minecraft:goat_horn", + "damage": 6 + }, + { + "id": "minecraft:goat_horn", + "damage": 7 + }, + { + "id": "minecraft:empty_map" + }, + { + "id": "minecraft:empty_map", + "damage": 2 + }, + { + "id": "minecraft:saddle" + }, + { + "id": "minecraft:leather_horse_armor" + }, + { + "id": "minecraft:iron_horse_armor" + }, + { + "id": "minecraft:golden_horse_armor" + }, + { + "id": "minecraft:diamond_horse_armor" + }, + { + "id": "minecraft:wolf_armor" + }, + { + "id": "minecraft:trident" + }, + { + "id": "minecraft:turtle_helmet" + }, + { + "id": "minecraft:elytra" + }, + { + "id": "minecraft:totem_of_undying" + }, + { + "id": "minecraft:glass_bottle" + }, + { + "id": "minecraft:experience_bottle" + }, + { + "id": "minecraft:potion" + }, + { + "id": "minecraft:potion", + "damage": 1 + }, + { + "id": "minecraft:potion", + "damage": 2 + }, + { + "id": "minecraft:potion", + "damage": 3 + }, + { + "id": "minecraft:potion", + "damage": 4 + }, + { + "id": "minecraft:potion", + "damage": 5 + }, + { + "id": "minecraft:potion", + "damage": 6 + }, + { + "id": "minecraft:potion", + "damage": 7 + }, + { + "id": "minecraft:potion", + "damage": 8 + }, + { + "id": "minecraft:potion", + "damage": 9 + }, + { + "id": "minecraft:potion", + "damage": 10 + }, + { + "id": "minecraft:potion", + "damage": 11 + }, + { + "id": "minecraft:potion", + "damage": 12 + }, + { + "id": "minecraft:potion", + "damage": 13 + }, + { + "id": "minecraft:potion", + "damage": 14 + }, + { + "id": "minecraft:potion", + "damage": 15 + }, + { + "id": "minecraft:potion", + "damage": 16 + }, + { + "id": "minecraft:potion", + "damage": 17 + }, + { + "id": "minecraft:potion", + "damage": 18 + }, + { + "id": "minecraft:potion", + "damage": 19 + }, + { + "id": "minecraft:potion", + "damage": 20 + }, + { + "id": "minecraft:potion", + "damage": 21 + }, + { + "id": "minecraft:potion", + "damage": 22 + }, + { + "id": "minecraft:potion", + "damage": 23 + }, + { + "id": "minecraft:potion", + "damage": 24 + }, + { + "id": "minecraft:potion", + "damage": 25 + }, + { + "id": "minecraft:potion", + "damage": 26 + }, + { + "id": "minecraft:potion", + "damage": 27 + }, + { + "id": "minecraft:potion", + "damage": 28 + }, + { + "id": "minecraft:potion", + "damage": 29 + }, + { + "id": "minecraft:potion", + "damage": 30 + }, + { + "id": "minecraft:potion", + "damage": 31 + }, + { + "id": "minecraft:potion", + "damage": 32 + }, + { + "id": "minecraft:potion", + "damage": 33 + }, + { + "id": "minecraft:potion", + "damage": 34 + }, + { + "id": "minecraft:potion", + "damage": 35 + }, + { + "id": "minecraft:potion", + "damage": 36 + }, + { + "id": "minecraft:potion", + "damage": 37 + }, + { + "id": "minecraft:potion", + "damage": 38 + }, + { + "id": "minecraft:potion", + "damage": 39 + }, + { + "id": "minecraft:potion", + "damage": 40 + }, + { + "id": "minecraft:potion", + "damage": 41 + }, + { + "id": "minecraft:potion", + "damage": 42 + }, + { + "id": "minecraft:potion", + "damage": 43 + }, + { + "id": "minecraft:potion", + "damage": 44 + }, + { + "id": "minecraft:potion", + "damage": 45 + }, + { + "id": "minecraft:potion", + "damage": 46 + }, + { + "id": "minecraft:splash_potion" + }, + { + "id": "minecraft:splash_potion", + "damage": 1 + }, + { + "id": "minecraft:splash_potion", + "damage": 2 + }, + { + "id": "minecraft:splash_potion", + "damage": 3 + }, + { + "id": "minecraft:splash_potion", + "damage": 4 + }, + { + "id": "minecraft:splash_potion", + "damage": 5 + }, + { + "id": "minecraft:splash_potion", + "damage": 6 + }, + { + "id": "minecraft:splash_potion", + "damage": 7 + }, + { + "id": "minecraft:splash_potion", + "damage": 8 + }, + { + "id": "minecraft:splash_potion", + "damage": 9 + }, + { + "id": "minecraft:splash_potion", + "damage": 10 + }, + { + "id": "minecraft:splash_potion", + "damage": 11 + }, + { + "id": "minecraft:splash_potion", + "damage": 12 + }, + { + "id": "minecraft:splash_potion", + "damage": 13 + }, + { + "id": "minecraft:splash_potion", + "damage": 14 + }, + { + "id": "minecraft:splash_potion", + "damage": 15 + }, + { + "id": "minecraft:splash_potion", + "damage": 16 + }, + { + "id": "minecraft:splash_potion", + "damage": 17 + }, + { + "id": "minecraft:splash_potion", + "damage": 18 + }, + { + "id": "minecraft:splash_potion", + "damage": 19 + }, + { + "id": "minecraft:splash_potion", + "damage": 20 + }, + { + "id": "minecraft:splash_potion", + "damage": 21 + }, + { + "id": "minecraft:splash_potion", + "damage": 22 + }, + { + "id": "minecraft:splash_potion", + "damage": 23 + }, + { + "id": "minecraft:splash_potion", + "damage": 24 + }, + { + "id": "minecraft:splash_potion", + "damage": 25 + }, + { + "id": "minecraft:splash_potion", + "damage": 26 + }, + { + "id": "minecraft:splash_potion", + "damage": 27 + }, + { + "id": "minecraft:splash_potion", + "damage": 28 + }, + { + "id": "minecraft:splash_potion", + "damage": 29 + }, + { + "id": "minecraft:splash_potion", + "damage": 30 + }, + { + "id": "minecraft:splash_potion", + "damage": 31 + }, + { + "id": "minecraft:splash_potion", + "damage": 32 + }, + { + "id": "minecraft:splash_potion", + "damage": 33 + }, + { + "id": "minecraft:splash_potion", + "damage": 34 + }, + { + "id": "minecraft:splash_potion", + "damage": 35 + }, + { + "id": "minecraft:splash_potion", + "damage": 36 + }, + { + "id": "minecraft:splash_potion", + "damage": 37 + }, + { + "id": "minecraft:splash_potion", + "damage": 38 + }, + { + "id": "minecraft:splash_potion", + "damage": 39 + }, + { + "id": "minecraft:splash_potion", + "damage": 40 + }, + { + "id": "minecraft:splash_potion", + "damage": 41 + }, + { + "id": "minecraft:splash_potion", + "damage": 42 + }, + { + "id": "minecraft:splash_potion", + "damage": 43 + }, + { + "id": "minecraft:splash_potion", + "damage": 44 + }, + { + "id": "minecraft:splash_potion", + "damage": 45 + }, + { + "id": "minecraft:splash_potion", + "damage": 46 + }, + { + "id": "minecraft:lingering_potion" + }, + { + "id": "minecraft:lingering_potion", + "damage": 1 + }, + { + "id": "minecraft:lingering_potion", + "damage": 2 + }, + { + "id": "minecraft:lingering_potion", + "damage": 3 + }, + { + "id": "minecraft:lingering_potion", + "damage": 4 + }, + { + "id": "minecraft:lingering_potion", + "damage": 5 + }, + { + "id": "minecraft:lingering_potion", + "damage": 6 + }, + { + "id": "minecraft:lingering_potion", + "damage": 7 + }, + { + "id": "minecraft:lingering_potion", + "damage": 8 + }, + { + "id": "minecraft:lingering_potion", + "damage": 9 + }, + { + "id": "minecraft:lingering_potion", + "damage": 10 + }, + { + "id": "minecraft:lingering_potion", + "damage": 11 + }, + { + "id": "minecraft:lingering_potion", + "damage": 12 + }, + { + "id": "minecraft:lingering_potion", + "damage": 13 + }, + { + "id": "minecraft:lingering_potion", + "damage": 14 + }, + { + "id": "minecraft:lingering_potion", + "damage": 15 + }, + { + "id": "minecraft:lingering_potion", + "damage": 16 + }, + { + "id": "minecraft:lingering_potion", + "damage": 17 + }, + { + "id": "minecraft:lingering_potion", + "damage": 18 + }, + { + "id": "minecraft:lingering_potion", + "damage": 19 + }, + { + "id": "minecraft:lingering_potion", + "damage": 20 + }, + { + "id": "minecraft:lingering_potion", + "damage": 21 + }, + { + "id": "minecraft:lingering_potion", + "damage": 22 + }, + { + "id": "minecraft:lingering_potion", + "damage": 23 + }, + { + "id": "minecraft:lingering_potion", + "damage": 24 + }, + { + "id": "minecraft:lingering_potion", + "damage": 25 + }, + { + "id": "minecraft:lingering_potion", + "damage": 26 + }, + { + "id": "minecraft:lingering_potion", + "damage": 27 + }, + { + "id": "minecraft:lingering_potion", + "damage": 28 + }, + { + "id": "minecraft:lingering_potion", + "damage": 29 + }, + { + "id": "minecraft:lingering_potion", + "damage": 30 + }, + { + "id": "minecraft:lingering_potion", + "damage": 31 + }, + { + "id": "minecraft:lingering_potion", + "damage": 32 + }, + { + "id": "minecraft:lingering_potion", + "damage": 33 + }, + { + "id": "minecraft:lingering_potion", + "damage": 34 + }, + { + "id": "minecraft:lingering_potion", + "damage": 35 + }, + { + "id": "minecraft:lingering_potion", + "damage": 36 + }, + { + "id": "minecraft:lingering_potion", + "damage": 37 + }, + { + "id": "minecraft:lingering_potion", + "damage": 38 + }, + { + "id": "minecraft:lingering_potion", + "damage": 39 + }, + { + "id": "minecraft:lingering_potion", + "damage": 40 + }, + { + "id": "minecraft:lingering_potion", + "damage": 41 + }, + { + "id": "minecraft:lingering_potion", + "damage": 42 + }, + { + "id": "minecraft:lingering_potion", + "damage": 43 + }, + { + "id": "minecraft:lingering_potion", + "damage": 44 + }, + { + "id": "minecraft:lingering_potion", + "damage": 45 + }, + { + "id": "minecraft:lingering_potion", + "damage": 46 + }, + { + "id": "minecraft:spyglass" + }, + { + "id": "minecraft:brush" + }, + { + "id": "minecraft:stick" + }, + { + "id": "minecraft:bed" + }, + { + "id": "minecraft:bed", + "damage": 8 + }, + { + "id": "minecraft:bed", + "damage": 7 + }, + { + "id": "minecraft:bed", + "damage": 15 + }, + { + "id": "minecraft:bed", + "damage": 12 + }, + { + "id": "minecraft:bed", + "damage": 14 + }, + { + "id": "minecraft:bed", + "damage": 1 + }, + { + "id": "minecraft:bed", + "damage": 4 + }, + { + "id": "minecraft:bed", + "damage": 5 + }, + { + "id": "minecraft:bed", + "damage": 13 + }, + { + "id": "minecraft:bed", + "damage": 9 + }, + { + "id": "minecraft:bed", + "damage": 3 + }, + { + "id": "minecraft:bed", + "damage": 11 + }, + { + "id": "minecraft:bed", + "damage": 10 + }, + { + "id": "minecraft:bed", + "damage": 2 + }, + { + "id": "minecraft:bed", + "damage": 6 + }, + { + "id": "minecraft:torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnRvcmNoBAkAbmFtZV9oYXNoagn7rmDBzisDCgBuZXR3b3JrX2lk+BwwuQoGAHN0YXRlcwgWAHRvcmNoX2ZhY2luZ19kaXJlY3Rpb24HAHVua25vd24AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:soul_torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLAgAACAQAbmFtZRQAbWluZWNyYWZ0OnNvdWxfdG9yY2gECQBuYW1lX2hhc2huixOT04BRdQMKAG5ldHdvcmtfaWShbFILCgYAc3RhdGVzCBYAdG9yY2hfZmFjaW5nX2RpcmVjdGlvbgcAdW5rbm93bgADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sea_pickle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAQAACAQAbmFtZRQAbWluZWNyYWZ0OnNlYV9waWNrbGUECQBuYW1lX2hhc2iONEfZJB+glgMKAG5ldHdvcmtfaWSINWQyCgYAc3RhdGVzAw0AY2x1c3Rlcl9jb3VudAAAAAABCABkZWFkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTPAQAACAQAbmFtZREAbWluZWNyYWZ0OmxhbnRlcm4ECQBuYW1lX2hhc2hMw44VI2HWygMKAG5ldHdvcmtfaWRkjQvzCgYAc3RhdGVzAQcAaGFuZ2luZwAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:soul_lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMAgAACAQAbmFtZRYAbWluZWNyYWZ0OnNvdWxfbGFudGVybgQJAG5hbWVfaGFzaGjIpjxk9z+RAwoAbmV0d29ya19pZGfoP8cKBgBzdGF0ZXMBBwBoYW5naW5nAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAgAACAQAbmFtZRAAbWluZWNyYWZ0OmNhbmRsZQQJAG5hbWVfaGFzaHPd+MsNdWTfAwoAbmV0d29ya19pZHsBMA0KBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAgAACAQAbmFtZRYAbWluZWNyYWZ0OndoaXRlX2NhbmRsZQQJAG5hbWVfaGFzaN1EG5Q1mHiEAwoAbmV0d29ya19pZKN1mmgKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:orange_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSdAgAACAQAbmFtZRcAbWluZWNyYWZ0Om9yYW5nZV9jYW5kbGUECQBuYW1lX2hhc2jySEVWHgUIHQMKAG5ldHdvcmtfaWSfVz82CgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:magenta_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hZ2VudGFfY2FuZGxlBAkAbmFtZV9oYXNoG0u6YIOoBSEDCgBuZXR3b3JrX2lk9xGNkQoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_blue_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSfAgAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY2FuZGxlBAkAbmFtZV9oYXNocXGeK0zgrG0DCgBuZXR3b3JrX2lk2m1y8goGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:yellow_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAgAACAQAbmFtZRcAbWluZWNyYWZ0OnllbGxvd19jYW5kbGUECQBuYW1lX2hhc2i00dtusU3CqQMKAG5ldHdvcmtfaWR9LTmpCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:lime_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAgAACAQAbmFtZRUAbWluZWNyYWZ0OmxpbWVfY2FuZGxlBAkAbmFtZV9oYXNokcmrw5xvz7ADCgBuZXR3b3JrX2lkIAUu6QoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:pink_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAgAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfY2FuZGxlBAkAbmFtZV9oYXNoQJdEY4sZ0dwDCgBuZXR3b3JrX2lk23Rn5AoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:gray_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAgAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXlfY2FuZGxlBAkAbmFtZV9oYXNoS5poSo9wBDEDCgBuZXR3b3JrX2lk3trRCAoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_gray_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAgAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY2FuZGxlBAkAbmFtZV9oYXNo9ruTZLBNMasDCgBuZXR3b3JrX2lkb6DOegoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cyan_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAgAACAQAbmFtZRUAbWluZWNyYWZ0OmN5YW5fY2FuZGxlBAkAbmFtZV9oYXNoc/M8PNVcjOwDCgBuZXR3b3JrX2lkZoIQOQoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:purple_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSmAgAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnBsZV9jYW5kbGUECQBuYW1lX2hhc2jaI3xUW0/myQMKAG5ldHdvcmtfaWSnLI2BCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blue_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAgAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfY2FuZGxlBAkAbmFtZV9oYXNoAASSPW6TgQADCgBuZXR3b3JrX2lkrxrjQAoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:brown_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJyb3duX2NhbmRsZQQJAG5hbWVfaGFzaDia0l6s1+WYAwoAbmV0d29ya19pZKSkBXYKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:green_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAgAACAQAbmFtZRYAbWluZWNyYWZ0OmdyZWVuX2NhbmRsZQQJAG5hbWVfaGFzaLeFPO1l+fIoAwoAbmV0d29ya19pZBkznDsKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:red_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAgAACAQAbmFtZRQAbWluZWNyYWZ0OnJlZF9jYW5kbGUECQBuYW1lX2hhc2jjAQpGf59ZdwMKAG5ldHdvcmtfaWRbb88GCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:black_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJsYWNrX2NhbmRsZQQJAG5hbWVfaGFzaB+wRDpOqREKAwoAbmV0d29ya19pZNnOnuEKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crafting_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AAAACAQAbmFtZRgAbWluZWNyYWZ0OmNyYWZ0aW5nX3RhYmxlBAkAbmFtZV9oYXNoe76VAmjvbpYDCgBuZXR3b3JrX2lkwCxwaAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cartography_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTHAQAACAQAbmFtZRsAbWluZWNyYWZ0OmNhcnRvZ3JhcGh5X3RhYmxlBAkAbmFtZV9oYXNomaWiiD/znP8DCgBuZXR3b3JrX2lkI6FzMwoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:fletching_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAQAACAQAbmFtZRkAbWluZWNyYWZ0OmZsZXRjaGluZ190YWJsZQQJAG5hbWVfaGFzaPFibh8unKyUAwoAbmV0d29ya19pZJ2mW0oKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:smithing_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAQAACAQAbmFtZRgAbWluZWNyYWZ0OnNtaXRoaW5nX3RhYmxlBAkAbmFtZV9oYXNo4tFES2xOXEYDCgBuZXR3b3JrX2lkXWMBzQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:beehive", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAQAACAQAbmFtZREAbWluZWNyYWZ0OmJlZWhpdmUECQBuYW1lX2hhc2hCcqn12UbNpwMKAG5ldHdvcmtfaWR/idcaCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAMLAGhvbmV5X2xldmVsAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:suspicious_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAwAACAQAbmFtZRkAbWluZWNyYWZ0OnN1c3BpY2lvdXNfc2FuZAQJAG5hbWVfaGFzaL67QsuvLP00AwoAbmV0d29ya19pZKnkaIAKBgBzdGF0ZXMDEABicnVzaGVkX3Byb2dyZXNzAAAAAAEHAGhhbmdpbmcBAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:suspicious_gravel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AwAACAQAbmFtZRsAbWluZWNyYWZ0OnN1c3BpY2lvdXNfZ3JhdmVsBAkAbmFtZV9oYXNoJSVbGNk7C3oDCgBuZXR3b3JrX2lkvIEJAAoGAHN0YXRlcwMQAGJydXNoZWRfcHJvZ3Jlc3MAAAAAAQcAaGFuZ2luZwEAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:campfire" + }, + { + "id": "minecraft:soul_campfire" + }, + { + "id": "minecraft:furnace", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9AAAACAQAbmFtZREAbWluZWNyYWZ0OmZ1cm5hY2UECQBuYW1lX2hhc2ioOQrludYY8wMKAG5ldHdvcmtfaWRZxnDOCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:blast_furnace", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTDAQAACAQAbmFtZRcAbWluZWNyYWZ0OmJsYXN0X2Z1cm5hY2UECQBuYW1lX2hhc2ivDbnjkpGm5QMKAG5ldHdvcmtfaWTcEbV/CgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:smoker", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAQAACAQAbmFtZRAAbWluZWNyYWZ0OnNtb2tlcgQJAG5hbWVfaGFzaJd1rDMkRWomAwoAbmV0d29ya19pZGWswMwKBgBzdGF0ZXMIHABtaW5lY3JhZnQ6Y2FyZGluYWxfZGlyZWN0aW9uBQBzb3V0aAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:respawn_anchor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJlc3Bhd25fYW5jaG9yBAkAbmFtZV9oYXNoZOdcjW05qigDCgBuZXR3b3JrX2lkmhMcaQoGAHN0YXRlcwMVAHJlc3Bhd25fYW5jaG9yX2NoYXJnZQAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:brewing_stand" + }, + { + "id": "minecraft:anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmFudmlsBAkAbmFtZV9oYXNoNqB3fgcUCbwDCgBuZXR3b3JrX2lkqXzNjwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:chipped_anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS+BAAACAQAbmFtZRcAbWluZWNyYWZ0OmNoaXBwZWRfYW52aWwECQBuYW1lX2hhc2ge+QY3vlS/eQMKAG5ldHdvcmtfaWRJ15iUCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:damaged_anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS/BAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhbWFnZWRfYW52aWwECQBuYW1lX2hhc2imJ12Be2V8+AMKAG5ldHdvcmtfaWRh5SHkCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:grindstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTCAQAACAQAbmFtZRQAbWluZWNyYWZ0OmdyaW5kc3RvbmUECQBuYW1lX2hhc2id56zc0nk99wMKAG5ldHdvcmtfaWS4Es07CgYAc3RhdGVzCAoAYXR0YWNobWVudAgAc3RhbmRpbmcDCQBkaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:enchanting_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0AAAACAQAbmFtZRoAbWluZWNyYWZ0OmVuY2hhbnRpbmdfdGFibGUECQBuYW1lX2hhc2jgIx24VLvMvwMKAG5ldHdvcmtfaWRliFFJCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bookshelf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAAAACAQAbmFtZRMAbWluZWNyYWZ0OmJvb2tzaGVsZgQJAG5hbWVfaGFzaDU04DrgJCS9AwoAbmV0d29ya19pZBcWwIwKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chiseled_bookshelf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAwAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX2Jvb2tzaGVsZgQJAG5hbWVfaGFzaNXDBnsIsywYAwoAbmV0d29ya19pZIprt5IKBgBzdGF0ZXMDDABib29rc19zdG9yZWQAAAAAAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lectern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTBAQAACAQAbmFtZREAbWluZWNyYWZ0OmxlY3Rlcm4ECQBuYW1lX2hhc2j5Z4Mmi/1QxAMKAG5ldHdvcmtfaWR4JfDHCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgBCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cauldron" + }, + { + "id": "minecraft:composter", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAQAACAQAbmFtZRMAbWluZWNyYWZ0OmNvbXBvc3RlcgQJAG5hbWVfaGFzaPAADHptzeWJAwoAbmV0d29ya19pZHIL6i4KBgBzdGF0ZXMDFABjb21wb3N0ZXJfZmlsbF9sZXZlbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2AAAACAQAbmFtZQ8AbWluZWNyYWZ0OmNoZXN0BAkAbmFtZV9oYXNog9ozMxlcA88DCgBuZXR3b3JrX2lkDkOFvAoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:trapped_chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAAAACAQAbmFtZRcAbWluZWNyYWZ0OnRyYXBwZWRfY2hlc3QECQBuYW1lX2hhc2g2qpF9stsEjgMKAG5ldHdvcmtfaWTjJWYxCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAbm9ydGgAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:ender_chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAAAACAQAbmFtZRUAbWluZWNyYWZ0OmVuZGVyX2NoZXN0BAkAbmFtZV9oYXNohEZzOFdg0WUDCgBuZXR3b3JrX2lkx4jiSQoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:barrel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhcnJlbAQJAG5hbWVfaGFzaHDkRPGymiRqAwoAbmV0d29ya19pZPnxzgsKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:undyed_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAAAACAQAbmFtZRwAbWluZWNyYWZ0OnVuZHllZF9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaOC9mypm/MlBAwoAbmV0d29ya19pZJ8rxp0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:white_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAAAACAQAbmFtZRsAbWluZWNyYWZ0OndoaXRlX3NodWxrZXJfYm94BAkAbmFtZV9oYXNosK79m1rPUBwDCgBuZXR3b3JrX2lkjrET6goGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:light_gray_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAwAACAQAbmFtZSAAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2iBe5zq7PxHmgMKAG5ldHdvcmtfaWSCVJv0CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:gray_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAwAACAQAbmFtZRoAbWluZWNyYWZ0OmdyYXlfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2ga2s8ctjHUhgMKAG5ldHdvcmtfaWS3WMsWCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:black_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRyAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJsYWNrX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoPm03OZphrp8DCgBuZXR3b3JrX2lkXHztNAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:brown_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRvAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJyb3duX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoT3DD6qAL9cADCgBuZXR3b3JrX2lkaXxpYQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:red_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRxAwAACAQAbmFtZRkAbWluZWNyYWZ0OnJlZF9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaMIlKSCzqSZoAwoAbmV0d29ya19pZNrf+icKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:orange_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAwAACAQAbmFtZRwAbWluZWNyYWZ0Om9yYW5nZV9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaG2MAXU67wGrAwoAbmV0d29ya19pZGoO05gKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:yellow_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAwAACAQAbmFtZRwAbWluZWNyYWZ0OnllbGxvd19zaHVsa2VyX2JveAQJAG5hbWVfaGFzaIsLwQHYjcIEAwoAbmV0d29ya19pZBCBSiYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lime_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoAwAACAQAbmFtZRoAbWluZWNyYWZ0OmxpbWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2hUwBkg+faUGAMKAG5ldHdvcmtfaWRJeKqqCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:green_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRwAwAACAQAbmFtZRsAbWluZWNyYWZ0OmdyZWVuX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoZgUeT3LupLUDCgBuZXR3b3JrX2lkzJiohQoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:cyan_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAwAACAQAbmFtZRoAbWluZWNyYWZ0OmN5YW5fc2h1bGtlcl9ib3gECQBuYW1lX2hhc2gSfbjteXg5yAMKAG5ldHdvcmtfaWTHeliECgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:light_blue_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAwAACAQAbmFtZSAAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2h0VFCX0qsRxQMKAG5ldHdvcmtfaWQXD8U0CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blue_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJsdWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2hn9gS0XIe6rAMKAG5ldHdvcmtfaWTO4PJaCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:purple_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAwAACAQAbmFtZRwAbWluZWNyYWZ0OnB1cnBsZV9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaEV/lkNPxRDdAwoAbmV0d29ya19pZFK25GAKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:magenta_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAwAACAQAbmFtZR0AbWluZWNyYWZ0Om1hZ2VudGFfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2iqWM7IJHxcFgMKAG5ldHdvcmtfaWTyyudTCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:pink_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBpbmtfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2in1tkJ1GNcZgMKAG5ldHdvcmtfaWQOEGXjCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:armor_stand" + }, + { + "id": "minecraft:noteblock", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAAAACAQAbmFtZRMAbWluZWNyYWZ0Om5vdGVibG9jawQJAG5hbWVfaGFzaHPA8dBBH0UaAwoAbmV0d29ya19pZH1U5QkKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jukebox", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAAAACAQAbmFtZREAbWluZWNyYWZ0Omp1a2Vib3gECQBuYW1lX2hhc2ieAIPExf/ZfgMKAG5ldHdvcmtfaWSmR7JfCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:music_disc_13" + }, + { + "id": "minecraft:music_disc_cat" + }, + { + "id": "minecraft:music_disc_blocks" + }, + { + "id": "minecraft:music_disc_chirp" + }, + { + "id": "minecraft:music_disc_far" + }, + { + "id": "minecraft:music_disc_mall" + }, + { + "id": "minecraft:music_disc_mellohi" + }, + { + "id": "minecraft:music_disc_stal" + }, + { + "id": "minecraft:music_disc_strad" + }, + { + "id": "minecraft:music_disc_ward" + }, + { + "id": "minecraft:music_disc_11" + }, + { + "id": "minecraft:music_disc_wait" + }, + { + "id": "minecraft:music_disc_otherside" + }, + { + "id": "minecraft:music_disc_5" + }, + { + "id": "minecraft:music_disc_pigstep" + }, + { + "id": "minecraft:music_disc_relic" + }, + { + "id": "minecraft:music_disc_creator" + }, + { + "id": "minecraft:music_disc_creator_music_box" + }, + { + "id": "minecraft:music_disc_precipice" + }, + { + "id": "minecraft:disc_fragment_5" + }, + { + "id": "minecraft:glowstone_dust" + }, + { + "id": "minecraft:glowstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAAAACAQAbmFtZRMAbWluZWNyYWZ0Omdsb3dzdG9uZQQJAG5hbWVfaGFzaFYqXNkefIlPAwoAbmV0d29ya19pZGT7WYYKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:redstone_lamp", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AAAACAQAbmFtZRcAbWluZWNyYWZ0OnJlZHN0b25lX2xhbXAECQBuYW1lX2hhc2hJ9V80caPvEgMKAG5ldHdvcmtfaWRvNPwnCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:sea_lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAAAACAQAbmFtZRUAbWluZWNyYWZ0OnNlYV9sYW50ZXJuBAkAbmFtZV9oYXNoLPsv1TX9M+QDCgBuZXR3b3JrX2lk1PPVyAoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:oak_sign" + }, + { + "id": "minecraft:spruce_sign" + }, + { + "id": "minecraft:birch_sign" + }, + { + "id": "minecraft:jungle_sign" + }, + { + "id": "minecraft:acacia_sign" + }, + { + "id": "minecraft:dark_oak_sign" + }, + { + "id": "minecraft:mangrove_sign" + }, + { + "id": "minecraft:cherry_sign" + }, + { + "id": "minecraft:bamboo_sign" + }, + { + "id": "minecraft:crimson_sign" + }, + { + "id": "minecraft:warped_sign" + }, + { + "id": "minecraft:oak_hanging_sign" + }, + { + "id": "minecraft:spruce_hanging_sign" + }, + { + "id": "minecraft:birch_hanging_sign" + }, + { + "id": "minecraft:jungle_hanging_sign" + }, + { + "id": "minecraft:acacia_hanging_sign" + }, + { + "id": "minecraft:dark_oak_hanging_sign" + }, + { + "id": "minecraft:mangrove_hanging_sign" + }, + { + "id": "minecraft:cherry_hanging_sign" + }, + { + "id": "minecraft:bamboo_hanging_sign" + }, + { + "id": "minecraft:crimson_hanging_sign" + }, + { + "id": "minecraft:warped_hanging_sign" + }, + { + "id": "minecraft:painting" + }, + { + "id": "minecraft:frame" + }, + { + "id": "minecraft:glow_frame" + }, + { + "id": "minecraft:honey_bottle" + }, + { + "id": "minecraft:flower_pot" + }, + { + "id": "minecraft:bowl" + }, + { + "id": "minecraft:bucket" + }, + { + "id": "minecraft:milk_bucket" + }, + { + "id": "minecraft:water_bucket" + }, + { + "id": "minecraft:lava_bucket" + }, + { + "id": "minecraft:cod_bucket" + }, + { + "id": "minecraft:salmon_bucket" + }, + { + "id": "minecraft:tropical_fish_bucket" + }, + { + "id": "minecraft:pufferfish_bucket" + }, + { + "id": "minecraft:powder_snow_bucket" + }, + { + "id": "minecraft:axolotl_bucket" + }, + { + "id": "minecraft:tadpole_bucket" + }, + { + "id": "minecraft:skull", + "damage": 3 + }, + { + "id": "minecraft:skull", + "damage": 2 + }, + { + "id": "minecraft:skull", + "damage": 4 + }, + { + "id": "minecraft:skull", + "damage": 5 + }, + { + "id": "minecraft:skull" + }, + { + "id": "minecraft:skull", + "damage": 1 + }, + { + "id": "minecraft:skull", + "damage": 6 + }, + { + "id": "minecraft:beacon", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAAAACAQAbmFtZRAAbWluZWNyYWZ0OmJlYWNvbgQJAG5hbWVfaGFzaACwhhfSkdkHAwoAbmV0d29ya19pZF8jfiEKBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:bell", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAQAACAQAbmFtZQ4AbWluZWNyYWZ0OmJlbGwECQBuYW1lX2hhc2iPqsgDXRcsxAMKAG5ldHdvcmtfaWT7zhOoCgYAc3RhdGVzCAoAYXR0YWNobWVudAgAc3RhbmRpbmcDCQBkaXJlY3Rpb24AAAAAAQoAdG9nZ2xlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:conduit", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAQAACAQAbmFtZREAbWluZWNyYWZ0OmNvbmR1aXQECQBuYW1lX2hhc2jqxKAxq2EaWQMKAG5ldHdvcmtfaWTWcBVnCgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stonecutter_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAQAACAQAbmFtZRsAbWluZWNyYWZ0OnN0b25lY3V0dGVyX2Jsb2NrBAkAbmFtZV9oYXNoQAXTbAM3MeYDCgBuZXR3b3JrX2lkWS4RjAoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:coal" + }, + { + "id": "minecraft:charcoal" + }, + { + "id": "minecraft:diamond" + }, + { + "id": "minecraft:iron_nugget" + }, + { + "id": "minecraft:raw_iron" + }, + { + "id": "minecraft:raw_gold" + }, + { + "id": "minecraft:raw_copper" + }, + { + "id": "minecraft:copper_ingot" + }, + { + "id": "minecraft:iron_ingot" + }, + { + "id": "minecraft:netherite_scrap" + }, + { + "id": "minecraft:netherite_ingot" + }, + { + "id": "minecraft:gold_nugget" + }, + { + "id": "minecraft:gold_ingot" + }, + { + "id": "minecraft:emerald" + }, + { + "id": "minecraft:quartz" + }, + { + "id": "minecraft:clay_ball" + }, + { + "id": "minecraft:brick" + }, + { + "id": "minecraft:netherbrick" + }, + { + "id": "minecraft:prismarine_shard" + }, + { + "id": "minecraft:amethyst_shard" + }, + { + "id": "minecraft:prismarine_crystals" + }, + { + "id": "minecraft:nautilus_shell" + }, + { + "id": "minecraft:heart_of_the_sea" + }, + { + "id": "minecraft:turtle_scute" + }, + { + "id": "minecraft:armadillo_scute" + }, + { + "id": "minecraft:phantom_membrane" + }, + { + "id": "minecraft:string" + }, + { + "id": "minecraft:feather" + }, + { + "id": "minecraft:flint" + }, + { + "id": "minecraft:gunpowder" + }, + { + "id": "minecraft:leather" + }, + { + "id": "minecraft:rabbit_hide" + }, + { + "id": "minecraft:rabbit_foot" + }, + { + "id": "minecraft:fire_charge" + }, + { + "id": "minecraft:blaze_rod" + }, + { + "id": "minecraft:breeze_rod" + }, + { + "id": "minecraft:heavy_core", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7AgAACAQAbmFtZRQAbWluZWNyYWZ0OmhlYXZ5X2NvcmUECQBuYW1lX2hhc2hhz/uNCtrC2QMKAG5ldHdvcmtfaWRaFu+8CgYAc3RhdGVzAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:blaze_powder" + }, + { + "id": "minecraft:magma_cream" + }, + { + "id": "minecraft:fermented_spider_eye" + }, + { + "id": "minecraft:echo_shard" + }, + { + "id": "minecraft:dragon_breath" + }, + { + "id": "minecraft:shulker_shell" + }, + { + "id": "minecraft:ghast_tear" + }, + { + "id": "minecraft:slime_ball" + }, + { + "id": "minecraft:ender_pearl" + }, + { + "id": "minecraft:ender_eye" + }, + { + "id": "minecraft:nether_star" + }, + { + "id": "minecraft:end_rod", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTQAAAACAQAbmFtZREAbWluZWNyYWZ0OmVuZF9yb2QECQBuYW1lX2hhc2jx/q5cEA0hmQMKAG5ldHdvcmtfaWQ2eM8kCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lightning_rod", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3AgAACAQAbmFtZRcAbWluZWNyYWZ0OmxpZ2h0bmluZ19yb2QECQBuYW1lX2hhc2ioXQF1xvfHNQMKAG5ldHdvcmtfaWRLuHyACgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:end_crystal" + }, + { + "id": "minecraft:paper" + }, + { + "id": "minecraft:book" + }, + { + "id": "minecraft:writable_book" + }, + { + "id": "minecraft:trial_key" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:oak_boat" + }, + { + "id": "minecraft:spruce_boat" + }, + { + "id": "minecraft:birch_boat" + }, + { + "id": "minecraft:jungle_boat" + }, + { + "id": "minecraft:acacia_boat" + }, + { + "id": "minecraft:dark_oak_boat" + }, + { + "id": "minecraft:mangrove_boat" + }, + { + "id": "minecraft:cherry_boat" + }, + { + "id": "minecraft:bamboo_raft" + }, + { + "id": "minecraft:oak_chest_boat" + }, + { + "id": "minecraft:spruce_chest_boat" + }, + { + "id": "minecraft:birch_chest_boat" + }, + { + "id": "minecraft:jungle_chest_boat" + }, + { + "id": "minecraft:acacia_chest_boat" + }, + { + "id": "minecraft:dark_oak_chest_boat" + }, + { + "id": "minecraft:mangrove_chest_boat" + }, + { + "id": "minecraft:cherry_chest_boat" + }, + { + "id": "minecraft:bamboo_chest_raft" + }, + { + "id": "minecraft:rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnJhaWwECQBuYW1lX2hhc2hUzmhUXYJDUQMKAG5ldHdvcmtfaWR+Sp6YCgYAc3RhdGVzAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:golden_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbAAAACAQAbmFtZRUAbWluZWNyYWZ0OmdvbGRlbl9yYWlsBAkAbmFtZV9oYXNoOoV5MaKipoUDCgBuZXR3b3JrX2lkfAcxLwoGAHN0YXRlcwENAHJhaWxfZGF0YV9iaXQAAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:detector_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcAAAACAQAbmFtZRcAbWluZWNyYWZ0OmRldGVjdG9yX3JhaWwECQBuYW1lX2hhc2gVUk31qOysUQMKAG5ldHdvcmtfaWRVW/aICgYAc3RhdGVzAQ0AcmFpbF9kYXRhX2JpdAADDgByYWlsX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:activator_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AAAACAQAbmFtZRgAbWluZWNyYWZ0OmFjdGl2YXRvcl9yYWlsBAkAbmFtZV9oYXNosIL91qriCRkDCgBuZXR3b3JrX2lkZfckmwoGAHN0YXRlcwENAHJhaWxfZGF0YV9iaXQAAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:minecart" + }, + { + "id": "minecraft:chest_minecart" + }, + { + "id": "minecraft:hopper_minecart" + }, + { + "id": "minecraft:tnt_minecart" + }, + { + "id": "minecraft:redstone" + }, + { + "id": "minecraft:redstone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAAAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZHN0b25lX2Jsb2NrBAkAbmFtZV9oYXNoRhULL0r8o0sDCgBuZXR3b3JrX2lklayOHgoGAHN0YXRlcwADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:redstone_torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAAAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZHN0b25lX3RvcmNoBAkAbmFtZV9oYXNoizFRjpYMIDgDCgBuZXR3b3JrX2lkuHz7yAoGAHN0YXRlcwgWAHRvcmNoX2ZhY2luZ19kaXJlY3Rpb24HAHVua25vd24AAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:lever", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmxldmVyBAkAbmFtZV9oYXNoGMJeLJsUMLYDCgBuZXR3b3JrX2lkEF/GuAoGAHN0YXRlcwgPAGxldmVyX2RpcmVjdGlvbg4AZG93bl9lYXN0X3dlc3QBCABvcGVuX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:wooden_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAAAACAQAbmFtZRcAbWluZWNyYWZ0Ondvb2Rlbl9idXR0b24ECQBuYW1lX2hhc2hR7PgSTQt0sQMKAG5ldHdvcmtfaWSU07kYCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAQAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9idXR0b24ECQBuYW1lX2hhc2jBW9Z8aYE7YQMKAG5ldHdvcmtfaWTkUIGuCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSMAQAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX2J1dHRvbgQJAG5hbWVfaGFzaJXYgGuSHbTwAwoAbmV0d29ya19pZGWp3yoKBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSOAQAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9idXR0b24ECQBuYW1lX2hhc2iCgNANcJs+BQMKAG5ldHdvcmtfaWT9fImWCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAQAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9idXR0b24ECQBuYW1lX2hhc2gVvmcT7LTO0wMKAG5ldHdvcmtfaWRQnxIJCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSNAQAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX2J1dHRvbgQJAG5hbWVfaGFzaIV10ZGGrCIEAwoAbmV0d29ya19pZN5vAmIKBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX2J1dHRvbgQJAG5hbWVfaGFzaNzeYYKLgOzJAwoAbmV0d29ya19pZAFEGQ0KBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9idXR0b24ECQBuYW1lX2hhc2j2/IHjeAbUcwMKAG5ldHdvcmtfaWRJ1irQCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19idXR0b24ECQBuYW1lX2hhc2j7AddMi+6nsgMKAG5ldHdvcmtfaWSa9w4/CgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stone_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX2J1dHRvbgQJAG5hbWVfaGFzaM4ejMctmvohAwoAbmV0d29ya19pZMw+aC0KBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:crimson_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAgAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fYnV0dG9uBAkAbmFtZV9oYXNofnjYHaYIeWgDCgBuZXR3b3JrX2lk+n1vyQoGAHN0YXRlcwESAGJ1dHRvbl9wcmVzc2VkX2JpdAADEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAgAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9idXR0b24ECQBuYW1lX2hhc2jwkV2EU6Cn1QMKAG5ldHdvcmtfaWTnHnk1CgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:polished_blackstone_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnV0dG9uBAkAbmFtZV9oYXNojmxzQKS0S/EDCgBuZXR3b3JrX2lkDtQ95woGAHN0YXRlcwESAGJ1dHRvbl9wcmVzc2VkX2JpdAADEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:tripwire_hook", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAAAACAQAbmFtZRcAbWluZWNyYWZ0OnRyaXB3aXJlX2hvb2sECQBuYW1lX2hhc2gQdp+oGZLNnAMKAG5ldHdvcmtfaWSy+1KJCgYAc3RhdGVzAQwAYXR0YWNoZWRfYml0AAMJAGRpcmVjdGlvbgAAAAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:wooden_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAAAACAQAbmFtZR8AbWluZWNyYWZ0Ondvb2Rlbl9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaGkGs5kCuA74AwoAbmV0d29ya19pZDRzPNwKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:spruce_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAQAACAQAbmFtZR8AbWluZWNyYWZ0OnNwcnVjZV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaNmwuq549fJKAwoAbmV0d29ya19pZLQMCw0KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:birch_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSWAQAACAQAbmFtZR4AbWluZWNyYWZ0OmJpcmNoX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNorQkT9kDdlTwDCgBuZXR3b3JrX2lkH0G97AoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:jungle_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAQAACAQAbmFtZR8AbWluZWNyYWZ0Omp1bmdsZV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaJ7DcteCkb8/AwoAbmV0d29ya19pZLdPBSAKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:acacia_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSVAQAACAQAbmFtZR8AbWluZWNyYWZ0OmFjYWNpYV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaC2frZtfoYqCAwoAbmV0d29ya19pZIDdI18KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:dark_oak_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAQAACAQAbmFtZSEAbWluZWNyYWZ0OmRhcmtfb2FrX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoHUCJsTy52pwDCgBuZXR3b3JrX2lkKpi8rAoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:mangrove_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAgAACAQAbmFtZSEAbWluZWNyYWZ0Om1hbmdyb3ZlX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoiDsTfJaX100DCgBuZXR3b3JrX2lkuwWDyQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:cherry_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAwAACAQAbmFtZR8AbWluZWNyYWZ0OmNoZXJyeV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaALMqYEZDUQHAwoAbmV0d29ya19pZPNT+r0KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:bamboo_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJhbWJvb19wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaNvxJ7NIAaqlAwoAbmV0d29ya19pZIZ8XnYKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:crimson_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAgAACAQAbmFtZSAAbWluZWNyYWZ0OmNyaW1zb25fcHJlc3N1cmVfcGxhdGUECQBuYW1lX2hhc2hqBDVDAd31/gMKAG5ldHdvcmtfaWRmV18LCgYAc3RhdGVzAw8AcmVkc3RvbmVfc2lnbmFsAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:warped_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAgAACAQAbmFtZR8AbWluZWNyYWZ0OndhcnBlZF9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaBxFoQksWtYUAwoAbmV0d29ya19pZJVRoIcKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:stone_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0b25lX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNounJuTBUTrU8DCgBuZXR3b3JrX2lkjDydwQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:light_weighted_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSTAAAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X3dlaWdodGVkX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoOyOJkNxLtkEDCgBuZXR3b3JrX2lkrr2AjgoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:heavy_weighted_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAAAACAQAbmFtZScAbWluZWNyYWZ0OmhlYXZ5X3dlaWdodGVkX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoltgDmDvTajUDCgBuZXR3b3JrX2lkFxVKuQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:polished_blackstone_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAgAACAQAbmFtZSwAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfcHJlc3N1cmVfcGxhdGUECQBuYW1lX2hhc2h65Ci6/CeGqwMKAG5ldHdvcmtfaWTaSW5xCgYAc3RhdGVzAw8AcmVkc3RvbmVfc2lnbmFsAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:observer", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT7AAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ic2VydmVyBAkAbmFtZV9oYXNoYhlh1lpmHTgDCgBuZXR3b3JrX2lkQEh55goGAHN0YXRlcwgaAG1pbmVjcmFmdDpmYWNpbmdfZGlyZWN0aW9uBABkb3duAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:daylight_detector", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAAAACAQAbmFtZRsAbWluZWNyYWZ0OmRheWxpZ2h0X2RldGVjdG9yBAkAbmFtZV9oYXNoV0F0s7B7PVgDCgBuZXR3b3JrX2lkri5afQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:repeater" + }, + { + "id": "minecraft:comparator" + }, + { + "id": "minecraft:hopper" + }, + { + "id": "minecraft:dropper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AAAACAQAbmFtZREAbWluZWNyYWZ0OmRyb3BwZXIECQBuYW1lX2hhc2joXP7XqU0l3QMKAG5ldHdvcmtfaWQfQN6zCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgMAAAABDQB0cmlnZ2VyZWRfYml0AAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:dispenser", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAAAACAQAbmFtZRMAbWluZWNyYWZ0OmRpc3BlbnNlcgQJAG5hbWVfaGFzaP1RR+zAbYP2AwoAbmV0d29ya19pZGAayD0KBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAwAAAAENAHRyaWdnZXJlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:crafter", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AgAACAQAbmFtZREAbWluZWNyYWZ0OmNyYWZ0ZXIECQBuYW1lX2hhc2iLCT/rJmRN8QMKAG5ldHdvcmtfaWTPTbvrCgYAc3RhdGVzAQgAY3JhZnRpbmcACAsAb3JpZW50YXRpb24JAGRvd25fZWFzdAENAHRyaWdnZXJlZF9iaXQAAAMHAHZlcnNpb24GFBUBAA==" + }, + { + "id": "minecraft:piston", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhAAAACAQAbmFtZRAAbWluZWNyYWZ0OnBpc3RvbgQJAG5hbWVfaGFzaDs3AFh1fL0uAwoAbmV0d29ya19pZLD/5XQKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAQAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:sticky_piston", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQdAAAACAQAbmFtZRcAbWluZWNyYWZ0OnN0aWNreV9waXN0b24ECQBuYW1lX2hhc2hPFJFJSiJ0ZQMKAG5ldHdvcmtfaWT/MzCJCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgEAAAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:tnt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAAAACAQAbmFtZQ0AbWluZWNyYWZ0OnRudAQJAG5hbWVfaGFzaEYOHwCvJH29AwoAbmV0d29ya19pZCGfjU4KBgBzdGF0ZXMBFABhbGxvd191bmRlcndhdGVyX2JpdAABCwBleHBsb2RlX2JpdAAAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:name_tag" + }, + { + "id": "minecraft:loom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAQAACAQAbmFtZQ4AbWluZWNyYWZ0Omxvb20ECQBuYW1lX2hhc2i7DKjAXNq8TAMKAG5ldHdvcmtfaWR/49HXCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:banner", + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 8, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 7, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 15, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 12, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 14, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 1, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 4, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 5, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 13, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 9, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 3, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 11, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 10, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 2, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 6, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 15, + "nbt_b64": "CgAAAwQAVHlwZQEAAAAA" + }, + { + "id": "minecraft:creeper_banner_pattern" + }, + { + "id": "minecraft:skull_banner_pattern" + }, + { + "id": "minecraft:flower_banner_pattern" + }, + { + "id": "minecraft:mojang_banner_pattern" + }, + { + "id": "minecraft:field_masoned_banner_pattern" + }, + { + "id": "minecraft:bordure_indented_banner_pattern" + }, + { + "id": "minecraft:piglin_banner_pattern" + }, + { + "id": "minecraft:globe_banner_pattern" + }, + { + "id": "minecraft:flow_banner_pattern" + }, + { + "id": "minecraft:guster_banner_pattern" + }, + { + "id": "minecraft:angler_pottery_sherd" + }, + { + "id": "minecraft:archer_pottery_sherd" + }, + { + "id": "minecraft:arms_up_pottery_sherd" + }, + { + "id": "minecraft:blade_pottery_sherd" + }, + { + "id": "minecraft:brewer_pottery_sherd" + }, + { + "id": "minecraft:burn_pottery_sherd" + }, + { + "id": "minecraft:danger_pottery_sherd" + }, + { + "id": "minecraft:explorer_pottery_sherd" + }, + { + "id": "minecraft:flow_pottery_sherd" + }, + { + "id": "minecraft:friend_pottery_sherd" + }, + { + "id": "minecraft:guster_pottery_sherd" + }, + { + "id": "minecraft:heart_pottery_sherd" + }, + { + "id": "minecraft:heartbreak_pottery_sherd" + }, + { + "id": "minecraft:howl_pottery_sherd" + }, + { + "id": "minecraft:miner_pottery_sherd" + }, + { + "id": "minecraft:mourner_pottery_sherd" + }, + { + "id": "minecraft:plenty_pottery_sherd" + }, + { + "id": "minecraft:prize_pottery_sherd" + }, + { + "id": "minecraft:scrape_pottery_sherd" + }, + { + "id": "minecraft:sheaf_pottery_sherd" + }, + { + "id": "minecraft:shelter_pottery_sherd" + }, + { + "id": "minecraft:skull_pottery_sherd" + }, + { + "id": "minecraft:snort_pottery_sherd" + }, + { + "id": "minecraft:netherite_upgrade_smithing_template" + }, + { + "id": "minecraft:sentry_armor_trim_smithing_template" + }, + { + "id": "minecraft:vex_armor_trim_smithing_template" + }, + { + "id": "minecraft:wild_armor_trim_smithing_template" + }, + { + "id": "minecraft:coast_armor_trim_smithing_template" + }, + { + "id": "minecraft:dune_armor_trim_smithing_template" + }, + { + "id": "minecraft:wayfinder_armor_trim_smithing_template" + }, + { + "id": "minecraft:shaper_armor_trim_smithing_template" + }, + { + "id": "minecraft:raiser_armor_trim_smithing_template" + }, + { + "id": "minecraft:host_armor_trim_smithing_template" + }, + { + "id": "minecraft:ward_armor_trim_smithing_template" + }, + { + "id": "minecraft:silence_armor_trim_smithing_template" + }, + { + "id": "minecraft:tide_armor_trim_smithing_template" + }, + { + "id": "minecraft:snout_armor_trim_smithing_template" + }, + { + "id": "minecraft:rib_armor_trim_smithing_template" + }, + { + "id": "minecraft:eye_armor_trim_smithing_template" + }, + { + "id": "minecraft:spire_armor_trim_smithing_template" + }, + { + "id": "minecraft:flow_armor_trim_smithing_template" + }, + { + "id": "minecraft:bolt_armor_trim_smithing_template" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_star", + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 8, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 7, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 15, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 12, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 14, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 1, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 4, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 5, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 13, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 9, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 3, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 11, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 10, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 2, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 6, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id": "minecraft:chain" + }, + { + "id": "minecraft:target", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTuAQAACAQAbmFtZRAAbWluZWNyYWZ0OnRhcmdldAQJAG5hbWVfaGFzaJc66SVbYlaxAwoAbmV0d29ya19pZPBozs0KBgBzdGF0ZXMAAwcAdmVyc2lvbgYUFQEA" + }, + { + "id": "minecraft:decorated_pot", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAwAACAQAbmFtZRcAbWluZWNyYWZ0OmRlY29yYXRlZF9wb3QECQBuYW1lX2hhc2jjQgckn8VTvwMKAG5ldHdvcmtfaWRwvkUUCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBhQVAQA=" + }, + { + "id": "minecraft:lodestone_compass" + }, + { + "id": "minecraft:wither_spawn_egg" + }, + { + "id": "minecraft:ender_dragon_spawn_egg" + }, + { + "id": "minecraft:ominous_trial_key" + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/creative_items.1_21_30.json b/core/src/main/resources/bedrock/creative_items.1_21_30.json new file mode 100644 index 000000000..8fab4638e --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_21_30.json @@ -0,0 +1,6214 @@ +{ + "items": [ + { + "id": "minecraft:oak_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19wbGFua3MECQBuYW1lX2hhc2ilMDLR92rQ4wMKAG5ldHdvcmtfaWS2GotyCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAwAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9wbGFua3MECQBuYW1lX2hhc2iumBkmFGFE8gMKAG5ldHdvcmtfaWSo8TFgCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX3BsYW5rcwQJAG5hbWVfaGFzaLrrAKJqV2WFAwoAbmV0d29ya19pZL+e3ZAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAwAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9wbGFua3MECQBuYW1lX2hhc2iBM3k4T3FAugMKAG5ldHdvcmtfaWSXUmBCCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAwAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9wbGFua3MECQBuYW1lX2hhc2g60edJxO5/aAMKAG5ldHdvcmtfaWTUXozECgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX3BsYW5rcwQJAG5hbWVfaGFzaAr64wkQ9cA7AwoAbmV0d29ya19pZFbMeR0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX3BsYW5rcwQJAG5hbWVfaGFzaPvLtcEA0F8xAwoAbmV0d29ya19pZEvnlCYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9wbGFua3MECQBuYW1lX2hhc2hNIvVh/lVW7gMKAG5ldHdvcmtfaWQTXpRoCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19wbGFua3MECQBuYW1lX2hhc2gYnjNz7SCCjgMKAG5ldHdvcmtfaWTi8ySSCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_mosaic", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT8AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19tb3NhaWMECQBuYW1lX2hhc2izSEgiMKOp/AMKAG5ldHdvcmtfaWQZ/p8xCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:crimson_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fcGxhbmtzBAkAbmFtZV9oYXNoJc5IKqNXJnwDCgBuZXR3b3JrX2lkwtJDdQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_planks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTyAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9wbGFua3MECQBuYW1lX2hhc2g3yGXEWhe6LgMKAG5ldHdvcmtfaWStTABvCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2hZu/xE7lYtNgMKAG5ldHdvcmtfaWSLY2XwCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mossy_cobblestone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKBAAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lX3dhbGwECQBuYW1lX2hhc2gHUQMwwFQeNQMKAG5ldHdvcmtfaWRzPhG8CgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:granite_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLBAAACAQAbmFtZRYAbWluZWNyYWZ0OmdyYW5pdGVfd2FsbAQJAG5hbWVfaGFzaE1GmM5AU0qUAwoAbmV0d29ya19pZE/UoPUKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:diorite_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTMBAAACAQAbmFtZRYAbWluZWNyYWZ0OmRpb3JpdGVfd2FsbAQJAG5hbWVfaGFzaF27l0QvdM8xAwoAbmV0d29ya19pZJe7jOwKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:andesite_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNBAAACAQAbmFtZRcAbWluZWNyYWZ0OmFuZGVzaXRlX3dhbGwECQBuYW1lX2hhc2gAL1Vay0kZjQMKAG5ldHdvcmtfaWRJGxdvCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sandstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTOBAAACAQAbmFtZRgAbWluZWNyYWZ0OnNhbmRzdG9uZV93YWxsBAkAbmFtZV9oYXNoYL2gu8a6HfgDCgBuZXR3b3JrX2lkHrhRjgoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_sandstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVBAAACAQAbmFtZRwAbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmVfd2FsbAQJAG5hbWVfaGFzaLAUUdOlo24MAwoAbmV0d29ya19pZI66BqAKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stone_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTQBAAACAQAbmFtZRoAbWluZWNyYWZ0OnN0b25lX2JyaWNrX3dhbGwECQBuYW1lX2hhc2hQegufuP6vjAMKAG5ldHdvcmtfaWS4AsOKCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mossy_stone_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTRBAAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrX3dhbGwECQBuYW1lX2hhc2i680zzUekp+wMKAG5ldHdvcmtfaWTQTaHPCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTPBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJyaWNrX3dhbGwECQBuYW1lX2hhc2gGJFLNjfgSCAMKAG5ldHdvcmtfaWSc5iUZCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:nether_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTSBAAACAQAbmFtZRsAbWluZWNyYWZ0Om5ldGhlcl9icmlja193YWxsBAkAbmFtZV9oYXNoAxb5f2yQ5MYDCgBuZXR3b3JrX2lkAECPDAoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_nether_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWBAAACAQAbmFtZR8AbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2tfd2FsbAQJAG5hbWVfaGFzaBOtwkokUt3cAwoAbmV0d29ya19pZJykmZUKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:end_stone_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTBAAACAQAbmFtZR4AbWluZWNyYWZ0OmVuZF9zdG9uZV9icmlja193YWxsBAkAbmFtZV9oYXNoOsr1L9kJIAMDCgBuZXR3b3JrX2lkjuYlYgoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:prismarine_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUBAAACAQAbmFtZRkAbWluZWNyYWZ0OnByaXNtYXJpbmVfd2FsbAQJAG5hbWVfaGFzaDO5IGrYZu1/AwoAbmV0d29ya19pZB4nLYYKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blackstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAgAACAQAbmFtZRkAbWluZWNyYWZ0OmJsYWNrc3RvbmVfd2FsbAQJAG5hbWVfaGFzaMP8XppUSU1RAwoAbmV0d29ya19pZMbeBBsKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAgAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfd2FsbAQJAG5hbWVfaGFzaP6SwV08YwzAAwoAbmV0d29ya19pZAJLsz8KBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQVAgAACAQAbmFtZSgAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfd2FsbAQJAG5hbWVfaGFzaBBIDZbHxiEzAwoAbmV0d29ya19pZEbLV8cKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cobbled_deepslate_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AgAACAQAbmFtZSAAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3dhbGwECQBuYW1lX2hhc2iECY5oKxeT+gMKAG5ldHdvcmtfaWRCnPrFCgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_tile_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3dhbGwECQBuYW1lX2hhc2jz7N+PeuEXgQMKAG5ldHdvcmtfaWTqw4s4CgYAc3RhdGVzCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfZWFzdAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX25vcnRoBABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfc291dGgEAG5vbmUIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV93ZXN0BABub25lAQ0Ad2FsbF9wb3N0X2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:polished_deepslate_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAgAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV93YWxsBAkAbmFtZV9oYXNoHxjTdj9pevMDCgBuZXR3b3JrX2lkIvBYYwoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSJAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja193YWxsBAkAbmFtZV9oYXNoEs3EQrjroyEDCgBuZXR3b3JrX2lkwlrCGwoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:mud_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAgAACAQAbmFtZRgAbWluZWNyYWZ0Om11ZF9icmlja193YWxsBAkAbmFtZV9oYXNov9b98ATpUSwDCgBuZXR3b3JrX2lkH/1WZQoGAHN0YXRlcwgZAHdhbGxfY29ubmVjdGlvbl90eXBlX2Vhc3QEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9ub3J0aAQAbm9uZQgaAHdhbGxfY29ubmVjdGlvbl90eXBlX3NvdXRoBABub25lCBkAd2FsbF9jb25uZWN0aW9uX3R5cGVfd2VzdAQAbm9uZQENAHdhbGxfcG9zdF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oak_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAAAACAQAbmFtZRMAbWluZWNyYWZ0Om9ha19mZW5jZQQJAG5hbWVfaGFzaGEmid7AaCWRAwoAbmV0d29ya19pZDvPEXcKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:spruce_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAwAACAQAbmFtZRYAbWluZWNyYWZ0OnNwcnVjZV9mZW5jZQQJAG5hbWVfaGFzaPQCm+aX1ZQeAwoAbmV0d29ya19pZD1QUEoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:birch_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/AwAACAQAbmFtZRUAbWluZWNyYWZ0OmJpcmNoX2ZlbmNlBAkAbmFtZV9oYXNo6CJ2ATpANfgDCgBuZXR3b3JrX2lkmCUV2QoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:jungle_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBAwAACAQAbmFtZRYAbWluZWNyYWZ0Omp1bmdsZV9mZW5jZQQJAG5hbWVfaGFzaOX4cD9uAmsdAwoAbmV0d29ya19pZHz1VxkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:acacia_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+AwAACAQAbmFtZRYAbWluZWNyYWZ0OmFjYWNpYV9mZW5jZQQJAG5hbWVfaGFzaGjn+RlKVDH6AwoAbmV0d29ya19pZNVGubwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dark_oak_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRAAwAACAQAbmFtZRgAbWluZWNyYWZ0OmRhcmtfb2FrX2ZlbmNlBAkAbmFtZV9oYXNoGPj0gCgM0c0DCgBuZXR3b3JrX2lk2w+gEwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mangrove_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hbmdyb3ZlX2ZlbmNlBAkAbmFtZV9oYXNowwAd7tPu9bsDCgBuZXR3b3JrX2lkKEcd0goGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cherry_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAwAACAQAbmFtZRYAbWluZWNyYWZ0OmNoZXJyeV9mZW5jZQQJAG5hbWVfaGFzaFmtUfHfTxcxAwoAbmV0d29ya19pZPCBxAIKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bamboo_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJhbWJvb19mZW5jZQQJAG5hbWVfaGFzaCKRbxfXsfkiAwoAbmV0d29ya19pZJNXKFcKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:nether_brick_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRxAAAACAQAbmFtZRwAbWluZWNyYWZ0Om5ldGhlcl9icmlja19mZW5jZQQJAG5hbWVfaGFzaA6030ngawxcAwoAbmV0d29ya19pZLnjLF4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AQAACAQAbmFtZRcAbWluZWNyYWZ0OmNyaW1zb25fZmVuY2UECQBuYW1lX2hhc2jhUhKv1HGj9AMKAG5ldHdvcmtfaWR3OH3OCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:warped_fence", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQAAgAACAQAbmFtZRYAbWluZWNyYWZ0OndhcnBlZF9mZW5jZQQJAG5hbWVfaGFzaJfb3/YuKmOWAwoAbmV0d29ya19pZCpaGC8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAAAACAQAbmFtZRQAbWluZWNyYWZ0OmZlbmNlX2dhdGUECQBuYW1lX2hhc2hTxpjEDmRzAwMKAG5ldHdvcmtfaWR+T9kTCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:spruce_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS3AAAACAQAbmFtZRsAbWluZWNyYWZ0OnNwcnVjZV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoanTVB84HRbkDCgBuZXR3b3JrX2lkEnw5egoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:birch_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4AAAACAQAbmFtZRoAbWluZWNyYWZ0OmJpcmNoX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2jmfPklI8azSwMKAG5ldHdvcmtfaWQL77/BCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:jungle_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS5AAAACAQAbmFtZRsAbWluZWNyYWZ0Omp1bmdsZV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNobYVQkfBomIcDCgBuZXR3b3JrX2lkA1zgtgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:acacia_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS7AAAACAQAbmFtZRsAbWluZWNyYWZ0OmFjYWNpYV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoZnrLUx/XSekDCgBuZXR3b3JrX2lkHg/kTgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dark_oak_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS6AAAACAQAbmFtZR0AbWluZWNyYWZ0OmRhcmtfb2FrX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2j2PTvdJJHcVQMKAG5ldHdvcmtfaWTwjOCeCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mangrove_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAgAACAQAbmFtZR0AbWluZWNyYWZ0Om1hbmdyb3ZlX2ZlbmNlX2dhdGUECQBuYW1lX2hhc2i/kOhBKiI/dAMKAG5ldHdvcmtfaWSfweCSCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAELAGluX3dhbGxfYml0AAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cherry_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAwAACAQAbmFtZRsAbWluZWNyYWZ0OmNoZXJyeV9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoKWLgCk0z+PsDCgBuZXR3b3JrX2lk/9bTZQoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bamboo_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJhbWJvb19mZW5jZV9nYXRlBAkAbmFtZV9oYXNopH1JrUgwdIADCgBuZXR3b3JrX2lkzIpPywoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAgAACAQAbmFtZRwAbWluZWNyYWZ0OmNyaW1zb25fZmVuY2VfZ2F0ZQQJAG5hbWVfaGFzaHE3Gfd0Z2d2AwoAbmV0d29ya19pZDQzVbEKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQsAaW5fd2FsbF9iaXQAAQgAb3Blbl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:warped_fence_gate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAgAACAQAbmFtZRsAbWluZWNyYWZ0OndhcnBlZF9mZW5jZV9nYXRlBAkAbmFtZV9oYXNoy0oIBjDIG4kDCgBuZXR3b3JrX2lkkf+/3QoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCwBpbl93YWxsX2JpdAABCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:normal_stone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzAQAACAQAbmFtZR0AbWluZWNyYWZ0Om5vcm1hbF9zdG9uZV9zdGFpcnMECQBuYW1lX2hhc2hAEktZZOkGIwMKAG5ldHdvcmtfaWQeH1ALCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX3N0YWlycwQJAG5hbWVfaGFzaNRjqVC5GRVDAwoAbmV0d29ya19pZDcCv+MKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mossy_cobblestone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSyAQAACAQAbmFtZSIAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lX3N0YWlycwQJAG5hbWVfaGFzaMVSTq5z9n1RAwoAbmV0d29ya19pZFIfrhkKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:oak_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1AAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19zdGFpcnMECQBuYW1lX2hhc2jk/HFzdXy0FQMKAG5ldHdvcmtfaWQJjyzBCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAAAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9zdGFpcnMECQBuYW1lX2hhc2iznygw7uBPBQMKAG5ldHdvcmtfaWTv+is3CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAAAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX3N0YWlycwQJAG5hbWVfaGFzaPfhbL619a3GAwoAbmV0d29ya19pZFyPlHAKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAAAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9zdGFpcnMECQBuYW1lX2hhc2jodJsHUbOVxQMKAG5ldHdvcmtfaWR0z5d4CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAAAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9zdGFpcnMECQBuYW1lX2hhc2h3x1NmD43IqQMKAG5ldHdvcmtfaWS7Jwz6CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX3N0YWlycwQJAG5hbWVfaGFzaMfwkbYPbNmAAwoAbmV0d29ya19pZCmBYKAKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX3N0YWlycwQJAG5hbWVfaGFzaNpUDY+uGMpyAwoAbmV0d29ya19pZChzUAsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9zdGFpcnMECQBuYW1lX2hhc2jMtr0v9JY4zwMKAG5ldHdvcmtfaWRQwq31CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19zdGFpcnMECQBuYW1lX2hhc2jFOzWL8PalKwMKAG5ldHdvcmtfaWTVPh42CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_mosaic_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKAwAACAQAbmFtZR4AbWluZWNyYWZ0OmJhbWJvb19tb3NhaWNfc3RhaXJzBAkAbmFtZV9oYXNoNLPiveSHPaoDCgBuZXR3b3JrX2lk44PHjgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAAAACAQAbmFtZRwAbWluZWNyYWZ0OnN0b25lX2JyaWNrX3N0YWlycwQJAG5hbWVfaGFzaN6tQViRo5cwAwoAbmV0d29ya19pZDMyMgIKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mossy_stone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAQAACAQAbmFtZSIAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrX3N0YWlycwQJAG5hbWVfaGFzaIB/Zv5YBPuYAwoAbmV0d29ya19pZANTOsMKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAAAACAQAbmFtZRoAbWluZWNyYWZ0OnNhbmRzdG9uZV9zdGFpcnMECQBuYW1lX2hhc2hOyA0BoYUOPQMKAG5ldHdvcmtfaWSV/834CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:smooth_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSwAQAACAQAbmFtZSEAbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNoB+CuCd8Ruz8DCgBuZXR3b3JrX2lksR+m8QoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:red_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS0AAAACAQAbmFtZR4AbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNoPs0LpHPL24YDCgBuZXR3b3JrX2lkLYVt3woGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvAQAACAQAbmFtZSUAbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lX3N0YWlycwQJAG5hbWVfaGFzaBvjtQv5pf+MAwoAbmV0d29ya19pZMHNND8KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:granite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAQAACAQAbmFtZRgAbWluZWNyYWZ0OmdyYW5pdGVfc3RhaXJzBAkAbmFtZV9oYXNoGzpvtoqKQjgDCgBuZXR3b3JrX2lkPkcB1goGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_granite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAQAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGVfc3RhaXJzBAkAbmFtZV9oYXNo3PvbSfEQklIDCgBuZXR3b3JrX2lkMmEm3AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:diorite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAQAACAQAbmFtZRgAbWluZWNyYWZ0OmRpb3JpdGVfc3RhaXJzBAkAbmFtZV9oYXNoi73T8VQuZmcDCgBuZXR3b3JrX2lk6i6nBQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_diorite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAQAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGVfc3RhaXJzBAkAbmFtZV9oYXNoFKRJd5Wk5L0DCgBuZXR3b3JrX2lkbt2ioAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:andesite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAQAACAQAbmFtZRkAbWluZWNyYWZ0OmFuZGVzaXRlX3N0YWlycwQJAG5hbWVfaGFzaO5w2FKBw76EAwoAbmV0d29ya19pZKhXEgUKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:polished_andesite_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAQAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlX3N0YWlycwQJAG5hbWVfaGFzaNcZZ/zmLInIAwoAbmV0d29ya19pZJTHrlEKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAAAACAQAbmFtZRYAbWluZWNyYWZ0OmJyaWNrX3N0YWlycwQJAG5hbWVfaGFzaMyt+cRDk5O2AwoAbmV0d29ya19pZNeMh58KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:nether_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRyAAAACAQAbmFtZR0AbWluZWNyYWZ0Om5ldGhlcl9icmlja19zdGFpcnMECQBuYW1lX2hhc2jRqIoOXgifBAMKAG5ldHdvcmtfaWQDiw5yCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_nether_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS3AQAACAQAbmFtZSEAbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNogQvosSbcj7kDCgBuZXR3b3JrX2lkx2IMtAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:end_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSxAQAACAQAbmFtZRoAbWluZWNyYWZ0OmVuZF9icmlja19zdGFpcnMECQBuYW1lX2hhc2hmlAk+QhsUsQMKAG5ldHdvcmtfaWTN7KFaCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:quartz_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAAAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9zdGFpcnMECQBuYW1lX2hhc2hmvpvOqGi6egMKAG5ldHdvcmtfaWRmUTh7CgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:smooth_quartz_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4AQAACAQAbmFtZR4AbWluZWNyYWZ0OnNtb290aF9xdWFydHpfc3RhaXJzBAkAbmFtZV9oYXNoNZZ9rX0qZOsDCgBuZXR3b3JrX2lkzsgQyQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purpur_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAAAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnB1cl9zdGFpcnMECQBuYW1lX2hhc2ifwDxeezXD7gMKAG5ldHdvcmtfaWTT+rxiCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:prismarine_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAQAACAQAbmFtZRsAbWluZWNyYWZ0OnByaXNtYXJpbmVfc3RhaXJzBAkAbmFtZV9oYXNooTHSZ+IrYtcDCgBuZXR3b3JrX2lkxTJfeAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dark_prismarine_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAQAACAQAbmFtZSAAbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZV9zdGFpcnMECQBuYW1lX2hhc2hIciLmam4o4AMKAG5ldHdvcmtfaWTVu7TCCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:prismarine_bricks_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAQAACAQAbmFtZSIAbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tzX3N0YWlycwQJAG5hbWVfaGFzaNIjq1oBlZMMAwoAbmV0d29ya19pZGEFwLYKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fc3RhaXJzBAkAbmFtZV9oYXNoZJqIzCBpCq4DCgBuZXR3b3JrX2lktXE00AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9zdGFpcnMECQBuYW1lX2hhc2hOkY27jLD4RQMKAG5ldHdvcmtfaWQ+E5VrCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blackstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAgAACAQAbmFtZRsAbWluZWNyYWZ0OmJsYWNrc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNokdoUb76p9McDCgBuZXR3b3JrX2lk5fWI5goGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfc3RhaXJzBAkAbmFtZV9oYXNolCFtFIE8MmADCgBuZXR3b3JrX2lkGTf7sgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQSAgAACAQAbmFtZSoAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNonks6UlfpOmkDCgBuZXR3b3JrX2lkgYeOdAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAgAACAQAbmFtZRsAbWluZWNyYWZ0OmN1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoHfoAXYq5G3MDCgBuZXR3b3JrX2lkeetf7woGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:exposed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAgAACAQAbmFtZSMAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcl9zdGFpcnMECQBuYW1lX2hhc2howneQGtZ9cgMKAG5ldHdvcmtfaWSg73zdCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:weathered_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAgAACAQAbmFtZSUAbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyX3N0YWlycwQJAG5hbWVfaGFzaP+R5loXxrVgAwoAbmV0d29ya19pZOnbRf4KBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:oxidized_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAgAACAQAbmFtZSQAbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNo6Jeoq5rsPxsDCgBuZXR3b3JrX2lkmRjDnQoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAgAACAQAbmFtZSEAbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoh07CQj0/SR8DCgBuZXR3b3JrX2lkmYqoqAoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_exposed_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAgAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcl9zdGFpcnMECQBuYW1lX2hhc2guVct1ilmxTwMKAG5ldHdvcmtfaWQgCPROCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_weathered_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAgAACAQAbmFtZSsAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyX3N0YWlycwQJAG5hbWVfaGFzaPXC8Sz/phCpAwoAbmV0d29ya19pZHlwHVsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS/AgAACAQAbmFtZSoAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXJfc3RhaXJzBAkAbmFtZV9oYXNoaqGdkuhxVZUDCgBuZXR3b3JrX2lkYQXzzgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cobbled_deepslate_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8AgAACAQAbmFtZSIAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3N0YWlycwQJAG5hbWVfaGFzaPIfa+TpyJcIAwoAbmV0d29ya19pZJUvOYIKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_tile_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3N0YWlycwQJAG5hbWVfaGFzaGFRFzB72mN2AwoAbmV0d29ya19pZJEOgIsKBgBzdGF0ZXMBDwB1cHNpZGVfZG93bl9iaXQAAxAAd2VpcmRvX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:polished_deepslate_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAgAACAQAbmFtZSMAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV9zdGFpcnMECQBuYW1lX2hhc2iNCYxVik9sGAMKAG5ldHdvcmtfaWSRVPnYCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAgAACAQAbmFtZSAAbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja19zdGFpcnMECQBuYW1lX2hhc2hIasOahEf83wMKAG5ldHdvcmtfaWQ1qEDCCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:mud_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAgAACAQAbmFtZRoAbWluZWNyYWZ0Om11ZF9icmlja19zdGFpcnMECQBuYW1lX2hhc2gt3qxK1NWajAMKAG5ldHdvcmtfaWSm9N3MCgYAc3RhdGVzAQ8AdXBzaWRlX2Rvd25fYml0AAMQAHdlaXJkb19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:wooden_door" + }, + { + "id": "minecraft:spruce_door" + }, + { + "id": "minecraft:birch_door" + }, + { + "id": "minecraft:jungle_door" + }, + { + "id": "minecraft:acacia_door" + }, + { + "id": "minecraft:dark_oak_door" + }, + { + "id": "minecraft:mangrove_door" + }, + { + "id": "minecraft:cherry_door" + }, + { + "id": "minecraft:bamboo_door" + }, + { + "id": "minecraft:iron_door" + }, + { + "id": "minecraft:crimson_door" + }, + { + "id": "minecraft:warped_door" + }, + { + "id": "minecraft:trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAAAACAQAbmFtZRIAbWluZWNyYWZ0OnRyYXBkb29yBAkAbmFtZV9oYXNotYiAJGtN0xADCgBuZXR3b3JrX2lkyTAWkAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAQAACAQAbmFtZRkAbWluZWNyYWZ0OnNwcnVjZV90cmFwZG9vcgQJAG5hbWVfaGFzaOwlfbgBkUW4AwoAbmV0d29ya19pZPHy1K0KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:birch_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAQAACAQAbmFtZRgAbWluZWNyYWZ0OmJpcmNoX3RyYXBkb29yBAkAbmFtZV9oYXNoSLtLweOLJ7wDCgBuZXR3b3JrX2lkeJWDfgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:jungle_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSTAQAACAQAbmFtZRkAbWluZWNyYWZ0Omp1bmdsZV90cmFwZG9vcgQJAG5hbWVfaGFzaDP/TnM9wyCIAwoAbmV0d29ya19pZEy2fJoKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:acacia_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAQAACAQAbmFtZRkAbWluZWNyYWZ0OmFjYWNpYV90cmFwZG9vcgQJAG5hbWVfaGFzaMj8xi3vmEKOAwoAbmV0d29ya19pZOHj8E8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dark_oak_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAQAACAQAbmFtZRsAbWluZWNyYWZ0OmRhcmtfb2FrX3RyYXBkb29yBAkAbmFtZV9oYXNomB2GGJQ2aOMDCgBuZXR3b3JrX2lko5ZHTwoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:mangrove_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAgAACAQAbmFtZRsAbWluZWNyYWZ0Om1hbmdyb3ZlX3RyYXBkb29yBAkAbmFtZV9oYXNooV3kQsQUUmkDCgBuZXR3b3JrX2lkkF/mxAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cherry_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAwAACAQAbmFtZRkAbWluZWNyYWZ0OmNoZXJyeV90cmFwZG9vcgQJAG5hbWVfaGFzaH/PefpfdHgtAwoAbmV0d29ya19pZOA7eNgKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:bamboo_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAwAACAQAbmFtZRkAbWluZWNyYWZ0OmJhbWJvb190cmFwZG9vcgQJAG5hbWVfaGFzaJrEOpsTwtKCAwoAbmV0d29ya19pZLvbPz8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:iron_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAAAACAQAbmFtZRcAbWluZWNyYWZ0Omlyb25fdHJhcGRvb3IECQBuYW1lX2hhc2gwA+IumsEiGQMKAG5ldHdvcmtfaWTvSVl/CgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT1AQAACAQAbmFtZRoAbWluZWNyYWZ0OmNyaW1zb25fdHJhcGRvb3IECQBuYW1lX2hhc2jHXufTnwUkYgMKAG5ldHdvcmtfaWQLjMYVCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:warped_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT2AQAACAQAbmFtZRkAbWluZWNyYWZ0OndhcnBlZF90cmFwZG9vcgQJAG5hbWVfaGFzaA20wG/+vkd6AwoAbmV0d29ya19pZHKR/hYKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:iron_bars", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAAAACAQAbmFtZRMAbWluZWNyYWZ0Omlyb25fYmFycwQJAG5hbWVfaGFzaPuefWSNAe56AwoAbmV0d29ya19pZN2LB5IKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQUAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmdsYXNzBAkAbmFtZV9oYXNowGJByfWff6gDCgBuZXR3b3JrX2lk0hdLNwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:white_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAAAACAQAbmFtZR0AbWluZWNyYWZ0OndoaXRlX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2iHubqoMbu9fAMKAG5ldHdvcmtfaWRndBrUCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:light_gray_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAwAACAQAbmFtZSIAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaKKa+LrRsHQhAwoAbmV0d29ya19pZEv2giYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:gray_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSmAwAACAQAbmFtZRwAbWluZWNyYWZ0OmdyYXlfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaIETy7Y/HZREAwoAbmV0d29ya19pZDomVrUKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:black_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAwAACAQAbmFtZR0AbWluZWNyYWZ0OmJsYWNrX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2iV6BCwpfDMmwMKAG5ldHdvcmtfaWSV7doJCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAwAACAQAbmFtZR0AbWluZWNyYWZ0OmJyb3duX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2igsEiq5np8JgMKAG5ldHdvcmtfaWRMzE/lCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAwAACAQAbmFtZRsAbWluZWNyYWZ0OnJlZF9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNoCa2J12/lQoIDCgBuZXR3b3JrX2lk283lWAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:orange_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAwAACAQAbmFtZR4AbWluZWNyYWZ0Om9yYW5nZV9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNozgjAuvzhxGsDCgBuZXR3b3JrX2lkW5CkhQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:yellow_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAwAACAQAbmFtZR4AbWluZWNyYWZ0OnllbGxvd19zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNo7EbHMd5WVugDCgBuZXR3b3JrX2lkkdDyXQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lime_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAwAACAQAbmFtZRwAbWluZWNyYWZ0OmxpbWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaBtZA1nZtwcFAwoAbmV0d29ya19pZDxX85UKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:green_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAwAACAQAbmFtZR0AbWluZWNyYWZ0OmdyZWVuX3N0YWluZWRfZ2xhc3MECQBuYW1lX2hhc2h91ptDgbehWwMKAG5ldHdvcmtfaWTlDhnECgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cyan_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAwAACAQAbmFtZRwAbWluZWNyYWZ0OmN5YW5fc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaBkIYQ8nQLqbAwoAbmV0d29ya19pZOL1lHsKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_blue_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAwAACAQAbmFtZSIAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaLt05n1G0fiSAwoAbmV0d29ya19pZNbwulIKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blue_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAwAACAQAbmFtZRwAbWluZWNyYWZ0OmJsdWVfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaPhLocSfzduRAwoAbmV0d29ya19pZENsjFwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:purple_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAwAACAQAbmFtZR4AbWluZWNyYWZ0OnB1cnBsZV9zdGFpbmVkX2dsYXNzBAkAbmFtZV9oYXNoJk0DhRO0szUDCgBuZXR3b3JrX2lkD98ZxgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:magenta_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAwAACAQAbmFtZR8AbWluZWNyYWZ0Om1hZ2VudGFfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaFEDeFiJj3zSAwoAbmV0d29ya19pZG+iFRoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:pink_stained_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBpbmtfc3RhaW5lZF9nbGFzcwQJAG5hbWVfaGFzaDijTX87ywxhAwoAbmV0d29ya19pZKdEricKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:tinted_glass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAgAACAQAbmFtZRYAbWluZWNyYWZ0OnRpbnRlZF9nbGFzcwQJAG5hbWVfaGFzaAFZWSamk6KdAwoAbmV0d29ya19pZGSvWX8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdsYXNzX3BhbmUECQBuYW1lX2hhc2gRSBHwNMQ4gQMKAG5ldHdvcmtfaWRGwixuCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:white_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAAAACAQAbmFtZSIAbWluZWNyYWZ0OndoaXRlX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaHgxQmgJVtRrAwoAbmV0d29ya19pZBEr/DYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_gray_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSJAwAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNon0aQw9lNkSEDCgBuZXR3b3JrX2lk9dp5VgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gray_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSIAwAACAQAbmFtZSEAbWluZWNyYWZ0OmdyYXlfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNors74IIw+2MMDCgBuZXR3b3JrX2lkmrGO5woGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:black_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAwAACAQAbmFtZSIAbWluZWNyYWZ0OmJsYWNrX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaOK/5ZRRd+M1AwoAbmV0d29ya19pZDv++oQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brown_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSNAwAACAQAbmFtZSIAbWluZWNyYWZ0OmJyb3duX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaLHeGJyRFTIWAwoAbmV0d29ya19pZMz9L0wKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAwAACAQAbmFtZSAAbWluZWNyYWZ0OnJlZF9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2gGr4x6JheAywMKAG5ldHdvcmtfaWQBjCTmCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:orange_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAwAACAQAbmFtZSMAbWluZWNyYWZ0Om9yYW5nZV9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2hbHxPD2gEbEAMKAG5ldHdvcmtfaWSt/7a5CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:yellow_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAwAACAQAbmFtZSMAbWluZWNyYWZ0OnllbGxvd19zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2g9tl4aOCyZBwMKAG5ldHdvcmtfaWTXRAS7CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lime_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAwAACAQAbmFtZSEAbWluZWNyYWZ0OmxpbWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNo3CtUyLwoGegDCgBuZXR3b3JrX2lkYJDnggoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:green_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSOAwAACAQAbmFtZSIAbWluZWNyYWZ0OmdyZWVuX3N0YWluZWRfZ2xhc3NfcGFuZQQJAG5hbWVfaGFzaJo6YP7IMy9SAwoAbmV0d29ya19pZHOnixoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cyan_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAwAACAQAbmFtZSEAbWluZWNyYWZ0OmN5YW5fc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoti97c6QrbLQDCgBuZXR3b3JrX2lkUqFUeQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_blue_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAwAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNovDg/gQle104DCgBuZXR3b3JrX2lkFuy4MQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blue_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSMAwAACAQAbmFtZSEAbWluZWNyYWZ0OmJsdWVfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoGc57tiexbQMDCgBuZXR3b3JrX2lk1eBLUAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purple_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAwAACAQAbmFtZSMAbWluZWNyYWZ0OnB1cnBsZV9zdGFpbmVkX2dsYXNzX3BhbmUECQBuYW1lX2hhc2hDJHYdd0FdfQMKAG5ldHdvcmtfaWSNsdK5CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:magenta_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAwAACAQAbmFtZSQAbWluZWNyYWZ0Om1hZ2VudGFfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNo3pcOw5bs5XoDCgBuZXR3b3JrX2lkVbOR7AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_stained_glass_pane", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAwAACAQAbmFtZSEAbWluZWNyYWZ0OnBpbmtfc3RhaW5lZF9nbGFzc19wYW5lBAkAbmFtZV9oYXNoWRhSACMWgswDCgBuZXR3b3JrX2lkIR92xwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:ladder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBAAAACAQAbmFtZRAAbWluZWNyYWZ0OmxhZGRlcgQJAG5hbWVfaGFzaKBhqheJVOz+AwoAbmV0d29ya19pZCgvzlsKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:scaffolding", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAQAACAQAbmFtZRUAbWluZWNyYWZ0OnNjYWZmb2xkaW5nBAkAbmFtZV9oYXNoYrkevrqcljwDCgBuZXR3b3JrX2lkD13mlAoGAHN0YXRlcwMJAHN0YWJpbGl0eQAAAAABDwBzdGFiaWxpdHlfY2hlY2sAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:smooth_stone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAAAACAQAbmFtZRsAbWluZWNyYWZ0OnNtb290aF9zdG9uZV9zbGFiBAkAbmFtZV9oYXNon5I1yVw74uMDCgBuZXR3b3JrX2lkqvjcBQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:normal_stone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCBAAACAQAbmFtZRsAbWluZWNyYWZ0Om5vcm1hbF9zdG9uZV9zbGFiBAkAbmFtZV9oYXNoIvsjJLQdolcDCgBuZXR3b3JrX2lkC1zqRQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cobblestone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoBAAACAQAbmFtZRoAbWluZWNyYWZ0OmNvYmJsZXN0b25lX3NsYWIECQBuYW1lX2hhc2h5CXtW7vlQVgMKAG5ldHdvcmtfaWRDGyj2CgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mossy_cobblestone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR3BAAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lX3NsYWIECQBuYW1lX2hhc2ijm1BCwB82VgMKAG5ldHdvcmtfaWR7ByMGCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oak_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ha19zbGFiBAkAbmFtZV9oYXNoJp1Cp1M4jlwDCgBuZXR3b3JrX2lkZH6+owoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:spruce_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjBAAACAQAbmFtZRUAbWluZWNyYWZ0OnNwcnVjZV9zbGFiBAkAbmFtZV9oYXNodQi70jB238cDCgBuZXR3b3JrX2lkrriOYQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:birch_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJpcmNoX3NsYWIECQBuYW1lX2hhc2gZPpfMxoOsTAMKAG5ldHdvcmtfaWThR9jyCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:jungle_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQlBAAACAQAbmFtZRUAbWluZWNyYWZ0Omp1bmdsZV9zbGFiBAkAbmFtZV9oYXNo6gLs79NXak4DCgBuZXR3b3JrX2lk5ZiKgwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:acacia_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmBAAACAQAbmFtZRUAbWluZWNyYWZ0OmFjYWNpYV9zbGFiBAkAbmFtZV9oYXNomSdFmDnv4OUDCgBuZXR3b3JrX2lkHttaXAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dark_oak_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnBAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhcmtfb2FrX3NsYWIECQBuYW1lX2hhc2hJjTohRFyhIQMKAG5ldHdvcmtfaWRMzDTyCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mangrove_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAgAACAQAbmFtZRcAbWluZWNyYWZ0Om1hbmdyb3ZlX3NsYWIECQBuYW1lX2hhc2jYCcmhJPeNMwMKAG5ldHdvcmtfaWQx6U1yCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cherry_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQaAwAACAQAbmFtZRUAbWluZWNyYWZ0OmNoZXJyeV9zbGFiBAkAbmFtZV9oYXNoTt0MmVn/mqoDCgBuZXR3b3JrX2lk2VVsZQoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bamboo_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQAAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJhbWJvb19zbGFiBAkAbmFtZV9oYXNoo1xuFqINeLYDCgBuZXR3b3JrX2lkVC+0twoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bamboo_mosaic_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLAwAACAQAbmFtZRwAbWluZWNyYWZ0OmJhbWJvb19tb3NhaWNfc2xhYgQJAG5hbWVfaGFzaNbVRBZ/ChI3AwoAbmV0d29ya19pZOLZHFMKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqBAAACAQAbmFtZRoAbWluZWNyYWZ0OnN0b25lX2JyaWNrX3NsYWIECQBuYW1lX2hhc2js6EexuKuzrQMKAG5ldHdvcmtfaWRSsMxaCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mossy_stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAQAACAQAbmFtZSAAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrX3NsYWIECQBuYW1lX2hhc2hiA4kFUl4tHAMKAG5ldHdvcmtfaWS6joSOCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnBAAACAQAbmFtZRgAbWluZWNyYWZ0OnNhbmRzdG9uZV9zbGFiBAkAbmFtZV9oYXNo/GMI0MZnrhsDCgBuZXR3b3JrX2lkFP8WmwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cut_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDBAAACAQAbmFtZRwAbWluZWNyYWZ0OmN1dF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaE+zxVQweViJAwoAbmV0d29ya19pZHsu74YKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:smooth_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4BAAACAQAbmFtZR8AbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaIkmsO1gw3gnAwoAbmV0d29ya19pZFSiwP0KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS2AAAACAQAbmFtZRwAbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmVfc2xhYgQJAG5hbWVfaGFzaEyDjeWlUHItAwoAbmV0d29ya19pZIT4rmwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cut_red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEBAAACAQAbmFtZSAAbWluZWNyYWZ0OmN1dF9yZWRfc2FuZHN0b25lX3NsYWIECQBuYW1lX2hhc2hTVRS++snU3wMKAG5ldHdvcmtfaWSvIAviCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6BAAACAQAbmFtZSMAbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lX3NsYWIECQBuYW1lX2hhc2i9iN2UK272tgMKAG5ldHdvcmtfaWRUZrwJCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:granite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/BAAACAQAbmFtZRYAbWluZWNyYWZ0OmdyYW5pdGVfc2xhYgQJAG5hbWVfaGFzaL0HprlAhhZwAwoAbmV0d29ya19pZIcIdc8KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_granite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSABAAACAQAbmFtZR8AbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGVfc2xhYgQJAG5hbWVfaGFzaP6bXk5w2dGrAwoAbmV0d29ya19pZCsRy1cKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:diorite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9BAAACAQAbmFtZRYAbWluZWNyYWZ0OmRpb3JpdGVfc2xhYgQJAG5hbWVfaGFzaM3ppS8v55sNAwoAbmV0d29ya19pZB+Pv9oKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_diorite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+BAAACAQAbmFtZR8AbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGVfc2xhYgQJAG5hbWVfaGFzaLZlyJLkMPhyAwoAbmV0d29ya19pZFM0HYwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:andesite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8BAAACAQAbmFtZRcAbWluZWNyYWZ0OmFuZGVzaXRlX3NsYWIECQBuYW1lX2hhc2icIrtuy/aosAMKAG5ldHdvcmtfaWTtXTtYCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_andesite_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7BAAACAQAbmFtZSAAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlX3NsYWIECQBuYW1lX2hhc2j56zJOfCF+3wMKAG5ldHdvcmtfaWRBs69FCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJyaWNrX3NsYWIECQBuYW1lX2hhc2hO/Da4jU2v4wMKAG5ldHdvcmtfaWRG/qphCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:nether_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsBAAACAQAbmFtZRsAbWluZWNyYWZ0Om5ldGhlcl9icmlja19zbGFiBAkAbmFtZV9oYXNonymoa2zbbqMDCgBuZXR3b3JrX2lkquvR1QoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_nether_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5BAAACAQAbmFtZR8AbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaG89ujUk3Y64AwoAbmV0d29ya19pZEZIunAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:end_stone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAQAACAQAbmFtZR4AbWluZWNyYWZ0OmVuZF9zdG9uZV9icmlja19zbGFiBAkAbmFtZV9oYXNo4tkxQtl+IyQDCgBuZXR3b3JrX2lkhByH/woGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:quartz_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrBAAACAQAbmFtZRUAbWluZWNyYWZ0OnF1YXJ0el9zbGFiBAkAbmFtZV9oYXNo9JMj3upfsbwDCgBuZXR3b3JrX2lkn2g2VAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:smooth_quartz_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBBAAACAQAbmFtZRwAbWluZWNyYWZ0OnNtb290aF9xdWFydHpfc2xhYgQJAG5hbWVfaGFzaHOSJv8ve0nmAwoAbmV0d29ya19pZFMk/JsKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:purpur_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRzBAAACAQAbmFtZRUAbWluZWNyYWZ0OnB1cnB1cl9zbGFiBAkAbmFtZV9oYXNo4XeWbKpx2ScDCgBuZXR3b3JrX2lkRkga5goGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:prismarine_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0BAAACAQAbmFtZRkAbWluZWNyYWZ0OnByaXNtYXJpbmVfc2xhYgQJAG5hbWVfaGFzaI9x+1fY8QRfAwoAbmV0d29ya19pZBTUZhwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_prismarine_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR1BAAACAQAbmFtZR4AbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZV9zbGFiBAkAbmFtZV9oYXNoSsZGDkEL5ZUDCgBuZXR3b3JrX2lkNLQ8VwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:prismarine_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR2BAAACAQAbmFtZR8AbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaB1FSbVi97xJAwoAbmV0d29ya19pZEBwwFMKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:crimson_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAgAACAQAbmFtZRYAbWluZWNyYWZ0OmNyaW1zb25fc2xhYgQJAG5hbWVfaGFzaKZ+EfP0ZYOZAwoAbmV0d29ya19pZAxRUWAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:warped_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIAgAACAQAbmFtZRUAbWluZWNyYWZ0OndhcnBlZF9zbGFiBAkAbmFtZV9oYXNo/AT0e/Z9W7UDCgBuZXR3b3JrX2lk1yq11AoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blackstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAgAACAQAbmFtZRkAbWluZWNyYWZ0OmJsYWNrc3RvbmVfc2xhYgQJAG5hbWVfaGFzaF/DD4ZUlNgtAwoAbmV0d29ya19pZGy1DjwKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_blackstone_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkAgAACAQAbmFtZSIAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfc2xhYgQJAG5hbWVfaGFzaDYnuUs86EWfAwoAbmV0d29ya19pZJj2bXIKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_blackstone_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbAgAACAQAbmFtZSgAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaKySLqvHc4xXAwoAbmV0d29ya19pZOyWX94KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoAgAACAQAbmFtZRkAbWluZWNyYWZ0OmN1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaDsNpb2qs4iBAwoAbmV0d29ya19pZOTm2nsKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:exposed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpAgAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcl9zbGFiBAkAbmFtZV9oYXNoahQ5OwIQb7kDCgBuZXR3b3JrX2lkrUlZLwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:weathered_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAgAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyX3NsYWIECQBuYW1lX2hhc2hBIuGIOVVXogMKAG5ldHdvcmtfaWQgnaDiCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oxidized_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAgAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaOptj9ycfpaDAwoAbmV0d29ya19pZMzFSRgKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAgAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaAlx6DZOCTHzAwoAbmV0d29ya19pZFRBvDAKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_exposed_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAgAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcl9zbGFiBAkAbmFtZV9oYXNo3KqS5OnhtRIDCgBuZXR3b3JrX2lkHTGcTgoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_weathered_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAgAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyX3NsYWIECQBuYW1lX2hhc2gzZ1oX0HCFtwMKAG5ldHdvcmtfaWSgJR+XCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTAAgAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXJfc2xhYgQJAG5hbWVfaGFzaMjjTnLu1KcqAwoAbmV0d29ya19pZIxsnFYKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cobbled_deepslate_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AgAACAQAbmFtZSAAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlX3NsYWIECQBuYW1lX2hhc2gwJIVWK1TM2QMKAG5ldHdvcmtfaWTYAoX5CgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_deepslate_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/AgAACAQAbmFtZSEAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZV9zbGFiBAkAbmFtZV9oYXNoC/Adiz8k6RYDCgBuZXR3b3JrX2lkuFYMAAoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_tile_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlX3NsYWIECQBuYW1lX2hhc2hPydV6emzIXAMKAG5ldHdvcmtfaWQwlbFCCgYAc3RhdGVzCBcAbWluZWNyYWZ0OnZlcnRpY2FsX2hhbGYGAGJvdHRvbQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:deepslate_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSHAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja19zbGFiBAkAbmFtZV9oYXNoSv62V7iw10UDCgBuZXR3b3JrX2lkWMoragoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mud_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAgAACAQAbmFtZRgAbWluZWNyYWZ0Om11ZF9icmlja19zbGFiBAkAbmFtZV9oYXNoq/tGBQWkv08DCgBuZXR3b3JrX2lkl4nnMwoGAHN0YXRlcwgXAG1pbmVjcmFmdDp2ZXJ0aWNhbF9oYWxmBgBib3R0b20AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brick_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAAAACAQAbmFtZRUAbWluZWNyYWZ0OmJyaWNrX2Jsb2NrBAkAbmFtZV9oYXNo5Qc2E005S3oDCgBuZXR3b3JrX2lkqeGWRgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chiseled_nether_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAgAACAQAbmFtZSAAbWluZWNyYWZ0OmNoaXNlbGVkX25ldGhlcl9icmlja3MECQBuYW1lX2hhc2g31SBPTcUK1QMKAG5ldHdvcmtfaWS8TJ+TCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cracked_nether_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAgAACAQAbmFtZR8AbWluZWNyYWZ0OmNyYWNrZWRfbmV0aGVyX2JyaWNrcwQJAG5hbWVfaGFzaAdC6eKzXT5tAwoAbmV0d29ya19pZIUSejwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:quartz_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAgAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9icmlja3MECQBuYW1lX2hhc2jSZO590dd8sAMKAG5ldHdvcmtfaWSc5xCLCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX2JyaWNrcwQJAG5hbWVfaGFzaGAiQu8VWVJRAwoAbmV0d29ya19pZH2DjXUKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mossy_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjBAAACAQAbmFtZRwAbWluZWNyYWZ0Om1vc3N5X3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaIZBO00MONRIAwoAbmV0d29ya19pZL2WDrAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cracked_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkBAAACAQAbmFtZR4AbWluZWNyYWZ0OmNyYWNrZWRfc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNocIkAp6riMz4DCgBuZXR3b3JrX2lkTWGeCwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chiseled_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlBAAACAQAbmFtZR8AbWluZWNyYWZ0OmNoaXNlbGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaMB2FPLLADkEAwoAbmV0d29ya19pZOIPn0IKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:end_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTOAAAACAQAbmFtZRQAbWluZWNyYWZ0OmVuZF9icmlja3MECQBuYW1lX2hhc2hIUFfxNLZaFgMKAG5ldHdvcmtfaWQ/vDihCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:prismarine_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzBAAACAQAbmFtZRsAbWluZWNyYWZ0OnByaXNtYXJpbmVfYnJpY2tzBAkAbmFtZV9oYXNozeGe3/7s5fcDCgBuZXR3b3JrX2lkj/iBnAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNoIHgsgIdzKXcDCgBuZXR3b3JrX2lkUw9b3woGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cracked_polished_blackstone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAgAACAQAbmFtZSwAbWluZWNyYWZ0OmNyYWNrZWRfcG9saXNoZWRfYmxhY2tzdG9uZV9icmlja3MECQBuYW1lX2hhc2jQIO1GQDk80AMKAG5ldHdvcmtfaWQ3UlRYCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:gilded_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAgAACAQAbmFtZRsAbWluZWNyYWZ0OmdpbGRlZF9ibGFja3N0b25lBAkAbmFtZV9oYXNoNoWt1ocG0HEDCgBuZXR3b3JrX2lktL8gUwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chiseled_polished_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAgAACAQAbmFtZSYAbWluZWNyYWZ0OmNoaXNlbGVkX3BvbGlzaGVkX2JsYWNrc3RvbmUECQBuYW1lX2hhc2gzFa+kEjCJgAMKAG5ldHdvcmtfaWR2NJX2CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate_tiles", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAgAACAQAbmFtZRkAbWluZWNyYWZ0OmRlZXBzbGF0ZV90aWxlcwQJAG5hbWVfaGFzaGcLLx3NXAFvAwoAbmV0d29ya19pZI/G/xYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cracked_deepslate_tiles", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAgAACAQAbmFtZSEAbWluZWNyYWZ0OmNyYWNrZWRfZGVlcHNsYXRlX3RpbGVzBAkAbmFtZV9oYXNo9zWgkFuMM1QDCgBuZXR3b3JrX2lkGwY6OgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:deepslate_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSGAgAACAQAbmFtZRoAbWluZWNyYWZ0OmRlZXBzbGF0ZV9icmlja3MECQBuYW1lX2hhc2gucvFmPdZxigMKAG5ldHdvcmtfaWSH4HDPCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cracked_deepslate_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAgAACAQAbmFtZSIAbWluZWNyYWZ0OmNyYWNrZWRfZGVlcHNsYXRlX2JyaWNrcwQJAG5hbWVfaGFzaN40aqhh9WqHAwoAbmV0d29ya19pZO9GPBQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chiseled_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAgAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaEU7/uRG8HSBAwoAbmV0d29ya19pZEqmI0EKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvYmJsZXN0b25lBAkAbmFtZV9oYXNoPoK7mGlSUz4DCgBuZXR3b3JrX2lkLm7RZwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mossy_cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwAAAACAQAbmFtZRsAbWluZWNyYWZ0Om1vc3N5X2NvYmJsZXN0b25lBAkAbmFtZV9oYXNoGJ67FCbkChMDCgBuZXR3b3JrX2lk/pYs1AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cobbled_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AgAACAQAbmFtZRsAbWluZWNyYWZ0OmNvYmJsZWRfZGVlcHNsYXRlBAkAbmFtZV9oYXNoLUz9Y/ywmLwDCgBuZXR3b3JrX2lkNwzZ+AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS2AQAACAQAbmFtZRYAbWluZWNyYWZ0OnNtb290aF9zdG9uZQQJAG5hbWVfaGFzaMwf87/JaTNvAwoAbmV0d29ya19pZLkZICEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYAAAACAQAbmFtZRMAbWluZWNyYWZ0OnNhbmRzdG9uZQQJAG5hbWVfaGFzaFEmWsEHFI1AAwoAbmV0d29ya19pZPsXMaQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chiseled_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvBAAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX3NhbmRzdG9uZQQJAG5hbWVfaGFzaPEkxMvZmemgAwoAbmV0d29ya19pZGI5NB4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cut_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSwBAAACAQAbmFtZRcAbWluZWNyYWZ0OmN1dF9zYW5kc3RvbmUECQBuYW1lX2hhc2ichLQc71njnQMKAG5ldHdvcmtfaWSmBLkRCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:smooth_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSxBAAACAQAbmFtZRoAbWluZWNyYWZ0OnNtb290aF9zYW5kc3RvbmUECQBuYW1lX2hhc2huR7XTwISyCAMKAG5ldHdvcmtfaWSzWj3UCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSzAAAACAQAbmFtZRcAbWluZWNyYWZ0OnJlZF9zYW5kc3RvbmUECQBuYW1lX2hhc2jBO4Gv2v59uAMKAG5ldHdvcmtfaWRXRYxZCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:chiseled_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS7BAAACAQAbmFtZSAAbWluZWNyYWZ0OmNoaXNlbGVkX3JlZF9zYW5kc3RvbmUECQBuYW1lX2hhc2gh5sX+ON054wMKAG5ldHdvcmtfaWT6Pw1PCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cut_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS8BAAACAQAbmFtZRsAbWluZWNyYWZ0OmN1dF9yZWRfc2FuZHN0b25lBAkAbmFtZV9oYXNoaOtka4NrQ1EDCgBuZXR3b3JrX2lk3r/JPAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_red_sandstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS9BAAACAQAbmFtZR4AbWluZWNyYWZ0OnNtb290aF9yZWRfc2FuZHN0b25lBAkAbmFtZV9oYXNoqsNl8x36ju4DCgBuZXR3b3JrX2lk7x5DTwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:coal_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWStAAAACAQAbmFtZRQAbWluZWNyYWZ0OmNvYWxfYmxvY2sECQBuYW1lX2hhc2jH8QQP3t5PiAMKAG5ldHdvcmtfaWRo+sR+CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dried_kelp_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAQAACAQAbmFtZRoAbWluZWNyYWZ0OmRyaWVkX2tlbHBfYmxvY2sECQBuYW1lX2hhc2iRoucexkrl8wMKAG5ldHdvcmtfaWQQCCrvCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:gold_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdvbGRfYmxvY2sECQBuYW1lX2hhc2iYLshvjtXzFwMKAG5ldHdvcmtfaWTDJGBcCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:iron_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAAAACAQAbmFtZRQAbWluZWNyYWZ0Omlyb25fYmxvY2sECQBuYW1lX2hhc2jYINmJQbvV/gMKAG5ldHdvcmtfaWRf7AbICgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:copper_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTAgAACAQAbmFtZRYAbWluZWNyYWZ0OmNvcHBlcl9ibG9jawQJAG5hbWVfaGFzaDVxnehsGaZ1AwoAbmV0d29ya19pZIiUodwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:copper_door" + }, + { + "id": "minecraft:copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXBAAACAQAbmFtZRkAbWluZWNyYWZ0OmNvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaO9fXio+svKVAwoAbmV0d29ya19pZMCoRjEKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT/AwAACAQAbmFtZRYAbWluZWNyYWZ0OmNvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaC/JEFOWnmEcAwoAbmV0d29ya19pZC6YiiMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:exposed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAgAACAQAbmFtZRgAbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyBAkAbmFtZV9oYXNoQH3Fukmu3CEDCgBuZXR3b3JrX2lk72jFIwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:exposed_copper_door" + }, + { + "id": "minecraft:exposed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQYBAAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX3RyYXBkb29yBAkAbmFtZV9oYXNoYhDFUysN7qUDCgBuZXR3b3JrX2lkMzwGJgoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:exposed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQABAAACAQAbmFtZR4AbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX2dyYXRlBAkAbmFtZV9oYXNolFIBYLYU0IcDCgBuZXR3b3JrX2lk4UqptAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:weathered_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAgAACAQAbmFtZRoAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXIECQBuYW1lX2hhc2hJCQXbvobv+gMKAG5ldHdvcmtfaWQwM0lJCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:weathered_copper_door" + }, + { + "id": "minecraft:weathered_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZBAAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfdHJhcGRvb3IECQBuYW1lX2hhc2hFnEC282a1tgMKAG5ldHdvcmtfaWTk70oiCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:weathered_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBBAAACAQAbmFtZSAAbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfZ3JhdGUECQBuYW1lX2hhc2jB3o8enlv1RgMKAG5ldHdvcmtfaWRih2pOCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oxidized_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAgAACAQAbmFtZRkAbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcgQJAG5hbWVfaGFzaMDtJqR0G5Y7AwoAbmV0d29ya19pZGjN8bUKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:oxidized_copper_door" + }, + { + "id": "minecraft:oxidized_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQaBAAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaOJpG/XFexVwAwoAbmV0d29ya19pZPhi0J4KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oxidized_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCBAAACAQAbmFtZR8AbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaBRfNhyndve7AwoAbmV0d29ya19pZKY2cnEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAgAACAQAbmFtZRYAbWluZWNyYWZ0OndheGVkX2NvcHBlcgQJAG5hbWVfaGFzaPF+FG6Eh5fsAwoAbmV0d29ya19pZIjtz/0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_copper_door" + }, + { + "id": "minecraft:waxed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbBAAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaO0JUKUHqNU6AwoAbmV0d29ya19pZJC3ZuMKBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDBAAACAQAbmFtZRwAbWluZWNyYWZ0OndheGVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaDmC92M2RO+HAwoAbmV0d29ya19pZH4og2AKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_exposed_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAgAACAQAbmFtZR4AbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyBAkAbmFtZV9oYXNoig8IOc+SCikDCgBuZXR3b3JrX2lklz8yWQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_exposed_copper_door" + }, + { + "id": "minecraft:waxed_exposed_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcBAAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX3RyYXBkb29yBAkAbmFtZV9oYXNoBHHxCpkUzpgDCgBuZXR3b3JrX2lkw2XBGQoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAABCABvcGVuX2JpdAABDwB1cHNpZGVfZG93bl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_exposed_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEBAAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX2dyYXRlBAkAbmFtZV9oYXNoWmd6B+hWwiEDCgBuZXR3b3JrX2lk8d4ZQwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_weathered_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAgAACAQAbmFtZSAAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXIECQBuYW1lX2hhc2gjtPq8MOdvKgMKAG5ldHdvcmtfaWSQ9Ln9CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_weathered_copper_door" + }, + { + "id": "minecraft:waxed_weathered_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQdBAAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfdHJhcGRvb3IECQBuYW1lX2hhc2gH9Fi3JCF4egMKAG5ldHdvcmtfaWRkGU6TCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAEPAHVwc2lkZV9kb3duX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_weathered_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFBAAACAQAbmFtZSYAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfZ3JhdGUECQBuYW1lX2hhc2hXfilVFDAiYQMKAG5ldHdvcmtfaWQqTGC1CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_oxidized_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS9AgAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcgQJAG5hbWVfaGFzaMaORhsO+LzjAwoAbmV0d29ya19pZJhGfLEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_oxidized_copper_door" + }, + { + "id": "minecraft:waxed_oxidized_copper_trapdoor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeBAAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl90cmFwZG9vcgQJAG5hbWVfaGFzaNA/q9qAy6Z9AwoAbmV0d29ya19pZDgExS8KBgBzdGF0ZXMDCQBkaXJlY3Rpb24AAAAAAQgAb3Blbl9iaXQAAQ8AdXBzaWRlX2Rvd25fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_oxidized_copper_grate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGBAAACAQAbmFtZSUAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl9ncmF0ZQQJAG5hbWVfaGFzaEbeMT605GP4AwoAbmV0d29ya19pZOZjpkkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaAgAACAQAbmFtZRQAbWluZWNyYWZ0OmN1dF9jb3BwZXIECQBuYW1lX2hhc2hAfN3NGax3eAMKAG5ldHdvcmtfaWTnFBtYCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:exposed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAgAACAQAbmFtZRwAbWluZWNyYWZ0OmV4cG9zZWRfY3V0X2NvcHBlcgQJAG5hbWVfaGFzaA85G3yv/w6pAwoAbmV0d29ya19pZMQhr0QKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:weathered_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcAgAACAQAbmFtZR4AbWluZWNyYWZ0OndlYXRoZXJlZF9jdXRfY29wcGVyBAkAbmFtZV9oYXNoVgRV0fBaz88DCgBuZXR3b3JrX2lk/0cYugoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oxidized_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdAgAACAQAbmFtZR0AbWluZWNyYWZ0Om94aWRpemVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2iP8WmFWOkriwMKAG5ldHdvcmtfaWQPdce7CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReAgAACAQAbmFtZRoAbWluZWNyYWZ0OndheGVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2jumiwOZIqv2AMKAG5ldHdvcmtfaWQvuxx9CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_exposed_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfAgAACAQAbmFtZSIAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY3V0X2NvcHBlcgQJAG5hbWVfaGFzaPE/OfK6IoVMAwoAbmV0d29ya19pZHy5HkcKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_weathered_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAgAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jdXRfY29wcGVyBAkAbmFtZV9oYXNoCA1xDp11bnwDCgBuZXR3b3JrX2lkDyEDVQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_oxidized_cut_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS+AgAACAQAbmFtZSMAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2N1dF9jb3BwZXIECQBuYW1lX2hhc2i1pZAsZYHLDAMKAG5ldHdvcmtfaWQ/wSkCCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT3AwAACAQAbmFtZRkAbWluZWNyYWZ0OmNoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaIsW5pmpJEuQAwoAbmV0d29ya19pZHetwrkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:exposed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT4AwAACAQAbmFtZSEAbWluZWNyYWZ0OmV4cG9zZWRfY2hpc2VsZWRfY29wcGVyBAkAbmFtZV9oYXNoOvrLJ0UowbgDCgBuZXR3b3JrX2lkZj7cPwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:weathered_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT5AwAACAQAbmFtZSMAbWluZWNyYWZ0OndlYXRoZXJlZF9jaGlzZWxlZF9jb3BwZXIECQBuYW1lX2hhc2hh+42XlsWvGAMKAG5ldHdvcmtfaWS7Cy59CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oxidized_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT6AwAACAQAbmFtZSIAbWluZWNyYWZ0Om94aWRpemVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaLpTIsnfluiCAwoAbmV0d29ya19pZB9/jS8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT7AwAACAQAbmFtZR8AbWluZWNyYWZ0OndheGVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaFnXvXY5OinzAwoAbmV0d29ya19pZAcKtHsKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_exposed_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT8AwAACAQAbmFtZScAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY2hpc2VsZWRfY29wcGVyBAkAbmFtZV9oYXNoHJdq+Pph6hMDCgBuZXR3b3JrX2lkdge7IAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_oxidized_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT9AwAACAQAbmFtZSgAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NoaXNlbGVkX2NvcHBlcgQJAG5hbWVfaGFzaMj49OvlTpgCAwoAbmV0d29ya19pZN/r+roKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_weathered_chiseled_copper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AwAACAQAbmFtZSkAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jaGlzZWxlZF9jb3BwZXIECQBuYW1lX2hhc2hzuO+Sg9LYQwMKAG5ldHdvcmtfaWQ7AN7iCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHBAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvcHBlcl9idWxiBAkAbmFtZV9oYXNo41TimHOsMWcDCgBuZXR3b3JrX2lkJnZvAgoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:exposed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIBAAACAQAbmFtZR0AbWluZWNyYWZ0OmV4cG9zZWRfY29wcGVyX2J1bGIECQBuYW1lX2hhc2g++f1wYLLCrAMKAG5ldHdvcmtfaWRLdMmGCgYAc3RhdGVzAQMAbGl0AAELAHBvd2VyZWRfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:weathered_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQJBAAACAQAbmFtZR8AbWluZWNyYWZ0OndlYXRoZXJlZF9jb3BwZXJfYnVsYgQJAG5hbWVfaGFzaMEtsYfwRTXlAwoAbmV0d29ya19pZAp51LQKBgBzdGF0ZXMBAwBsaXQAAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oxidized_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKBAAACAQAbmFtZR4AbWluZWNyYWZ0Om94aWRpemVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNovnrBQZs8nDIDCgBuZXR3b3JrX2lkPsj0AAoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLBAAACAQAbmFtZRsAbWluZWNyYWZ0OndheGVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNoGTg6TYllMiIDCgBuZXR3b3JrX2lk9m0WhgoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waxed_exposed_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMBAAACAQAbmFtZSMAbWluZWNyYWZ0OndheGVkX2V4cG9zZWRfY29wcGVyX2J1bGIECQBuYW1lX2hhc2gI6xkPcvBDVwMKAG5ldHdvcmtfaWR7BRcACgYAc3RhdGVzAQMAbGl0AAELAHBvd2VyZWRfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:waxed_weathered_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNBAAACAQAbmFtZSUAbWluZWNyYWZ0OndheGVkX3dlYXRoZXJlZF9jb3BwZXJfYnVsYgQJAG5hbWVfaGFzaMsUnmp3/VqVAwoAbmV0d29ya19pZEoworoKBgBzdGF0ZXMBAwBsaXQAAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:waxed_oxidized_copper_bulb", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOBAAACAQAbmFtZSQAbWluZWNyYWZ0OndheGVkX294aWRpemVkX2NvcHBlcl9idWxiBAkAbmFtZV9oYXNoBFKxY3fjVq4DCgBuZXR3b3JrX2lkzrJ6aAoGAHN0YXRlcwEDAGxpdAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:emerald_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAAAACAQAbmFtZRcAbWluZWNyYWZ0OmVtZXJhbGRfYmxvY2sECQBuYW1lX2hhc2hK6QunqJznNAMKAG5ldHdvcmtfaWRk5+otCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:diamond_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AAAACAQAbmFtZRcAbWluZWNyYWZ0OmRpYW1vbmRfYmxvY2sECQBuYW1lX2hhc2iGKrxuvkytFQMKAG5ldHdvcmtfaWQQeQZXCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lapis_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAAAACAQAbmFtZRUAbWluZWNyYWZ0OmxhcGlzX2Jsb2NrBAkAbmFtZV9oYXNoDZ44xdb2zVoDCgBuZXR3b3JrX2lktVy0BAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:raw_iron_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTCAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJhd19pcm9uX2Jsb2NrBAkAbmFtZV9oYXNo9XyzNIQXxvwDCgBuZXR3b3JrX2lknms1QAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:raw_copper_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTDAgAACAQAbmFtZRoAbWluZWNyYWZ0OnJhd19jb3BwZXJfYmxvY2sECQBuYW1lX2hhc2hw1KG0TNUGgwMKAG5ldHdvcmtfaWS1vGo/CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:raw_gold_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJhd19nb2xkX2Jsb2NrBAkAbmFtZV9oYXNo6YuwuLwfOBwDCgBuZXR3b3JrX2lkLiQ5gQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:quartz_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAAAACAQAbmFtZRYAbWluZWNyYWZ0OnF1YXJ0el9ibG9jawQJAG5hbWVfaGFzaCfpbqyIIvZCAwoAbmV0d29ya19pZE2axGsKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:quartz_pillar", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS5BAAACAQAbmFtZRcAbWluZWNyYWZ0OnF1YXJ0el9waWxsYXIECQBuYW1lX2hhc2igp62HI+PuSwMKAG5ldHdvcmtfaWS9SGXLCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chiseled_quartz_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS4BAAACAQAbmFtZR8AbWluZWNyYWZ0OmNoaXNlbGVkX3F1YXJ0el9ibG9jawQJAG5hbWVfaGFzaAftJM9mCAvaAwoAbmV0d29ya19pZFwy0s0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_quartz", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS6BAAACAQAbmFtZRcAbWluZWNyYWZ0OnNtb290aF9xdWFydHoECQBuYW1lX2hhc2hIVzzgiItGagMKAG5ldHdvcmtfaWTVWgU2CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:prismarine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAAAACAQAbmFtZRQAbWluZWNyYWZ0OnByaXNtYXJpbmUECQBuYW1lX2hhc2jcnQCHi9CspQMKAG5ldHdvcmtfaWQnuuW1CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_prismarine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSyBAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfcHJpc21hcmluZQQJAG5hbWVfaGFzaK+rhxsgkzplAwoAbmV0d29ya19pZIdA0I0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:slime", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnNsaW1lBAkAbmFtZV9oYXNoHJiEEJx+JlkDCgBuZXR3b3JrX2lkfgfVzAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:honey_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAQAACAQAbmFtZRUAbWluZWNyYWZ0OmhvbmV5X2Jsb2NrBAkAbmFtZV9oYXNo9zLYSUlelywDCgBuZXR3b3JrX2lko+dyWgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:honeycomb_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAQAACAQAbmFtZRkAbWluZWNyYWZ0OmhvbmV5Y29tYl9ibG9jawQJAG5hbWVfaGFzaASIPuOCYd1oAwoAbmV0d29ya19pZKys4n4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:hay_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAAAACAQAbmFtZRMAbWluZWNyYWZ0OmhheV9ibG9jawQJAG5hbWVfaGFzaIB2VxKxX8EpAwoAbmV0d29ya19pZKuQSloKBgBzdGF0ZXMDCgBkZXByZWNhdGVkAAAAAAgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAAAACAQAbmFtZRQAbWluZWNyYWZ0OmJvbmVfYmxvY2sECQBuYW1lX2hhc2i4ZX576W9AWgMKAG5ldHdvcmtfaWTWGacQCgYAc3RhdGVzAwoAZGVwcmVjYXRlZAAAAAAICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:nether_brick", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRwAAAACAQAbmFtZRYAbWluZWNyYWZ0Om5ldGhlcl9icmljawQJAG5hbWVfaGFzaMxcRiheU+nXAwoAbmV0d29ya19pZMkmzloKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_nether_brick", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAAAACAQAbmFtZRoAbWluZWNyYWZ0OnJlZF9uZXRoZXJfYnJpY2sECQBuYW1lX2hhc2j8pRO4LfoECAMKAG5ldHdvcmtfaWRpdF0YCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:netherite_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAgAACAQAbmFtZRkAbWluZWNyYWZ0Om5ldGhlcml0ZV9ibG9jawQJAG5hbWVfaGFzaMghh6Zib/ZKAwoAbmV0d29ya19pZIz0mq0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lodestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAQAACAQAbmFtZRMAbWluZWNyYWZ0OmxvZGVzdG9uZQQJAG5hbWVfaGFzaJ2gmHOTlXv8AwoAbmV0d29ya19pZEfgB4wKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAAAACAQAbmFtZRQAbWluZWNyYWZ0OndoaXRlX3dvb2wECQBuYW1lX2hhc2jRWB7vaIEDiQMKAG5ldHdvcmtfaWSO8paQCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:light_gray_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfd29vbAQJAG5hbWVfaGFzaOpdQ1a2v4b3AwoAbmV0d29ya19pZIqZCYEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:gray_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAwAACAQAbmFtZRMAbWluZWNyYWZ0OmdyYXlfd29vbAQJAG5hbWVfaGFzaLsc1Lp1xdIOAwoAbmV0d29ya19pZFUs+HgKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:black_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAwAACAQAbmFtZRQAbWluZWNyYWZ0OmJsYWNrX3dvb2wECQBuYW1lX2hhc2hP2HC6o0X4HAMKAG5ldHdvcmtfaWRUbORcCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAwAACAQAbmFtZRQAbWluZWNyYWZ0OmJyb3duX3dvb2wECQBuYW1lX2hhc2ig5IW89PrREwMKAG5ldHdvcmtfaWRjT9j8CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQrAwAACAQAbmFtZRIAbWluZWNyYWZ0OnJlZF93b29sBAkAbmFtZV9oYXNoY4TBDq+mFgUDCgBuZXR3b3JrX2lktn9lcAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:orange_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAwAACAQAbmFtZRUAbWluZWNyYWZ0Om9yYW5nZV93b29sBAkAbmFtZV9oYXNoFstfrTZfSCgDCgBuZXR3b3JrX2lk+rqywwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:yellow_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtAwAACAQAbmFtZRUAbWluZWNyYWZ0OnllbGxvd193b29sBAkAbmFtZV9oYXNoTFyus2RHegcDCgBuZXR3b3JrX2lkkKBhXAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lime_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAwAACAQAbmFtZRMAbWluZWNyYWZ0OmxpbWVfd29vbAQJAG5hbWVfaGFzaNVnnzKiMxmeAwoAbmV0d29ya19pZG9b32kKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:green_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAwAACAQAbmFtZRQAbWluZWNyYWZ0OmdyZWVuX3dvb2wECQBuYW1lX2hhc2i3mElRYHIcSQMKAG5ldHdvcmtfaWSssprwCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cyan_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwAwAACAQAbmFtZRMAbWluZWNyYWZ0OmN5YW5fd29vbAQJAG5hbWVfaGFzaBNDfvHn8dqFAwoAbmV0d29ya19pZK0hAbgKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_blue_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfd29vbAQJAG5hbWVfaGFzaLWFAUfyxFPNAwoAbmV0d29ya19pZL2oEugKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blue_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAwAACAQAbmFtZRMAbWluZWNyYWZ0OmJsdWVfd29vbAQJAG5hbWVfaGFzaLjHyxxbTWCLAwoAbmV0d29ya19pZPaLdFQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:purple_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzAwAACAQAbmFtZRUAbWluZWNyYWZ0OnB1cnBsZV93b29sBAkAbmFtZV9oYXNojvFtqzjAf/4DCgBuZXR3b3JrX2lklqASNQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:magenta_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0AwAACAQAbmFtZRYAbWluZWNyYWZ0Om1hZ2VudGFfd29vbAQJAG5hbWVfaGFzaGuOHvf+Pd4yAwoAbmV0d29ya19pZI4UoDQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:pink_wool", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1AwAACAQAbmFtZRMAbWluZWNyYWZ0OnBpbmtfd29vbAQJAG5hbWVfaGFzaPiVA2pFeoFLAwoAbmV0d29ya19pZOZRO6oKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAAAACAQAbmFtZRYAbWluZWNyYWZ0OndoaXRlX2NhcnBldAQJAG5hbWVfaGFzaNeMHTI1fWPXAwoAbmV0d29ya19pZEahDFcKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_gray_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAwAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY2FycGV0BAkAbmFtZV9oYXNoHPw6ArBAsP0DCgBuZXR3b3JrX2lkQoAeUAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gray_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaAwAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXlfY2FycGV0BAkAbmFtZV9oYXNoZVR0OI+1VRADCgBuZXR3b3JrX2lkETF4WwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:black_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJsYWNrX2NhcnBldAQJAG5hbWVfaGFzaOk7LP9NptyhAwoAbmV0d29ya19pZFjmXtIKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brown_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJyb3duX2NhcnBldAQJAG5hbWVfaGFzaNaXFyOsAvIvAwoAbmV0d29ya19pZHPjFuoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAwAACAQAbmFtZRQAbWluZWNyYWZ0OnJlZF9jYXJwZXQECQBuYW1lX2hhc2i9eSKBf6SO3wMKAG5ldHdvcmtfaWQuhI/KCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:orange_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAwAACAQAbmFtZRcAbWluZWNyYWZ0Om9yYW5nZV9jYXJwZXQECQBuYW1lX2hhc2hIUkO4HlAdygMKAG5ldHdvcmtfaWSyKV9OCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:yellow_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAwAACAQAbmFtZRcAbWluZWNyYWZ0OnllbGxvd19jYXJwZXQECQBuYW1lX2hhc2hSDKX3scCamwMKAG5ldHdvcmtfaWT8nq+ECgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lime_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAwAACAQAbmFtZRUAbWluZWNyYWZ0OmxpbWVfY2FycGV0BAkAbmFtZV9oYXNo+6KFOpzsib4DCgBuZXR3b3JrX2lkT+DS4woGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:green_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgAwAACAQAbmFtZRYAbWluZWNyYWZ0OmdyZWVuX2NhcnBldAQJAG5hbWVfaGFzaCHPMP9ltqFJAwoAbmV0d29ya19pZBgwAvAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cyan_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcAwAACAQAbmFtZRUAbWluZWNyYWZ0OmN5YW5fY2FycGV0BAkAbmFtZV9oYXNobXf62dQBJj8DCgBuZXR3b3JrX2lkKVppLgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_blue_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAwAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY2FycGV0BAkAbmFtZV9oYXNo20l4oktdZ3sDCgBuZXR3b3JrX2lkjdeMiwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blue_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfY2FycGV0BAkAbmFtZV9oYXNo3p3lsW0eQwsDCgBuZXR3b3JrX2lkAovdPQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purple_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdAwAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnBsZV9jYXJwZXQECQBuYW1lX2hhc2jwIA9pW/qp7QMKAG5ldHdvcmtfaWTqJqhjCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:magenta_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVAwAACAQAbmFtZRgAbWluZWNyYWZ0Om1hZ2VudGFfY2FycGV0BAkAbmFtZV9oYXNoFXT36YNNZhMDCgBuZXR3b3JrX2lk+tqsGAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAwAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfY2FycGV0BAkAbmFtZV9oYXNoHll72oqk+OoDCgBuZXR3b3JrX2lkrnBYDwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:white_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTtAAAACAQAbmFtZR8AbWluZWNyYWZ0OndoaXRlX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaFUk9iXVjwV8AwoAbmV0d29ya19pZJPZY8AKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_gray_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAwAACAQAbmFtZSQAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNo7EUk30hmUtYDCgBuZXR3b3JrX2lkh8jVIwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gray_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAwAACAQAbmFtZR4AbWluZWNyYWZ0OmdyYXlfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoW77af6WihdwDCgBuZXR3b3JrX2lkSsqC1woGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:black_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTSAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJsYWNrX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaAfWYp0xtgcfAwoAbmV0d29ya19pZMWTC8EKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brown_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTPAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJyb3duX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaB74EeiLO46XAwoAbmV0d29ya19pZEDHKqwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTRAwAACAQAbmFtZR0AbWluZWNyYWZ0OnJlZF9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2gjFut6Z/VH1gMKAG5ldHdvcmtfaWSvcmwYCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:orange_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAwAACAQAbmFtZSAAbWluZWNyYWZ0Om9yYW5nZV9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2gADDj2IJiw+gMKAG5ldHdvcmtfaWTHph0FCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:yellow_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTHAwAACAQAbmFtZSAAbWluZWNyYWZ0OnllbGxvd19jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2iy6qKNn3ob5wMKAG5ldHdvcmtfaWQZAI39CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lime_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAwAACAQAbmFtZR4AbWluZWNyYWZ0OmxpbWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNo4dYIPslbXPUDCgBuZXR3b3JrX2lk2O8X0AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:green_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTQAwAACAQAbmFtZR8AbWluZWNyYWZ0OmdyZWVuX2NvbmNyZXRlX3Bvd2RlcgQJAG5hbWVfaGFzaM/c9x2aJh3HAwoAbmV0d29ya19pZA0VfBMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cyan_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTMAwAACAQAbmFtZR4AbWluZWNyYWZ0OmN5YW5fY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNok+xKAe7XXjoDCgBuZXR3b3JrX2lkmkn6uwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_blue_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTGAwAACAQAbmFtZSQAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNogScpIQceyAEDCgBuZXR3b3JrX2lkOmVSbgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blue_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTOAwAACAQAbmFtZR4AbWluZWNyYWZ0OmJsdWVfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoFp7mmeL86r0DCgBuZXR3b3JrX2lkS3b3RQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purple_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAwAACAQAbmFtZSAAbWluZWNyYWZ0OnB1cnBsZV9jb25jcmV0ZV9wb3dkZXIECQBuYW1lX2hhc2iYcVU04hoStwMKAG5ldHdvcmtfaWQXimEjCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:magenta_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAwAACAQAbmFtZSEAbWluZWNyYWZ0Om1hZ2VudGFfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoy/70q6VPsWgDCgBuZXR3b3JrX2lkf9mxQwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_concrete_powder", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAwAACAQAbmFtZR4AbWluZWNyYWZ0OnBpbmtfY29uY3JldGVfcG93ZGVyBAkAbmFtZV9oYXNoVikSAf8DwV0DCgBuZXR3b3JrX2lku2MivwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:white_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTsAAAACAQAbmFtZRgAbWluZWNyYWZ0OndoaXRlX2NvbmNyZXRlBAkAbmFtZV9oYXNo6zAp7lsLlvkDCgBuZXR3b3JrX2lk3MAYQAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_gray_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AwAACAQAbmFtZR0AbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY29uY3JldGUECQBuYW1lX2hhc2hEtet5wuDIKAMKAG5ldHdvcmtfaWQISs02CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:gray_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AwAACAQAbmFtZRcAbWluZWNyYWZ0OmdyYXlfY29uY3JldGUECQBuYW1lX2hhc2j92INnb0a83AMKAG5ldHdvcmtfaWQj8RHwCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:black_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAwAACAQAbmFtZRgAbWluZWNyYWZ0OmJsYWNrX2NvbmNyZXRlBAkAbmFtZV9oYXNo2X7NDIQmZ70DCgBuZXR3b3JrX2lk2uiVDQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:brown_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AwAACAQAbmFtZRgAbWluZWNyYWZ0OmJyb3duX2NvbmNyZXRlBAkAbmFtZV9oYXNoeka02BwXf6oDCgBuZXR3b3JrX2lkYf+xDQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:red_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSAAwAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZF9jb25jcmV0ZQQJAG5hbWVfaGFzaPWmNowLGubqAwoAbmV0d29ya19pZKwyx58KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:orange_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRzAwAACAQAbmFtZRkAbWluZWNyYWZ0Om9yYW5nZV9jb25jcmV0ZQQJAG5hbWVfaGFzaAgE8XmaAi6+AwoAbmV0d29ya19pZMDQNz8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:yellow_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR2AwAACAQAbmFtZRkAbWluZWNyYWZ0OnllbGxvd19jb25jcmV0ZQQJAG5hbWVfaGFzaE6ONfJPBd0+AwoAbmV0d29ya19pZMarutwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lime_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR3AwAACAQAbmFtZRcAbWluZWNyYWZ0OmxpbWVfY29uY3JldGUECQBuYW1lX2hhc2gnd8JW6wmJcAMKAG5ldHdvcmtfaWTd47aoCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:green_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR/AwAACAQAbmFtZRgAbWluZWNyYWZ0OmdyZWVuX2NvbmNyZXRlBAkAbmFtZV9oYXNokbFxRKchQZkDCgBuZXR3b3JrX2lkmhZWUgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cyan_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AwAACAQAbmFtZRcAbWluZWNyYWZ0OmN5YW5fY29uY3JldGUECQBuYW1lX2hhc2hFRrWJ33qj1wMKAG5ldHdvcmtfaWQbi5b8CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:light_blue_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR1AwAACAQAbmFtZR0AbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY29uY3JldGUECQBuYW1lX2hhc2gHAe0kl0SE4AMKAG5ldHdvcmtfaWRL/GbSCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blue_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AwAACAQAbmFtZRcAbWluZWNyYWZ0OmJsdWVfY29uY3JldGUECQBuYW1lX2hhc2hiay301nnj1wMKAG5ldHdvcmtfaWRMvFXNCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:purple_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR8AwAACAQAbmFtZRkAbWluZWNyYWZ0OnB1cnBsZV9jb25jcmV0ZQQJAG5hbWVfaGFzaHBHflsPIwdXAwoAbmV0d29ya19pZCyKA5gKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:magenta_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0AwAACAQAbmFtZRoAbWluZWNyYWZ0Om1hZ2VudGFfY29uY3JldGUECQBuYW1lX2hhc2gN7LuB/OvdZAMKAG5ldHdvcmtfaWTc6ZOdCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:pink_concrete", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AwAACAQAbmFtZRcAbWluZWNyYWZ0OnBpbmtfY29uY3JldGUECQBuYW1lX2hhc2ii2G5F0u3SOAMKAG5ldHdvcmtfaWSszGgrCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:clay", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSAAAACAQAbmFtZQ4AbWluZWNyYWZ0OmNsYXkECQBuYW1lX2hhc2j/S6sKXRcpzwMKAG5ldHdvcmtfaWRmsb8nCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:hardened_clay", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSsAAAACAQAbmFtZRcAbWluZWNyYWZ0OmhhcmRlbmVkX2NsYXkECQBuYW1lX2hhc2jrnRwCJ0krJAMKAG5ldHdvcmtfaWRBCOrrCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:white_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSfAAAACAQAbmFtZRoAbWluZWNyYWZ0OndoaXRlX3RlcnJhY290dGEECQBuYW1lX2hhc2j3RSdgmnAIewMKAG5ldHdvcmtfaWSimKw+CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:light_gray_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAwAACAQAbmFtZR8AbWluZWNyYWZ0OmxpZ2h0X2dyYXlfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAz1Ri3wIxomAwoAbmV0d29ya19pZH5qgOcKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:gray_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAwAACAQAbmFtZRkAbWluZWNyYWZ0OmdyYXlfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAXdSLAaNZ9vAwoAbmV0d29ya19pZM1QDV0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:black_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJsYWNrX3RlcnJhY290dGEECQBuYW1lX2hhc2jxssdv5vlbpgMKAG5ldHdvcmtfaWRE3Ru/CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJyb3duX3RlcnJhY290dGEECQBuYW1lX2hhc2gG4kPenmOF9gMKAG5ldHdvcmtfaWQ/i0iNCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAwAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNo7fX56HXFejEDCgBuZXR3b3JrX2lk8tTF8QoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:orange_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAwAACAQAbmFtZRsAbWluZWNyYWZ0Om9yYW5nZV90ZXJyYWNvdHRhBAkAbmFtZV9oYXNo0Hjmql3sruMDCgBuZXR3b3JrX2lklmqmkAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:yellow_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAwAACAQAbmFtZRsAbWluZWNyYWZ0OnllbGxvd190ZXJyYWNvdHRhBAkAbmFtZV9oYXNoqkyKKrmA3VcDCgBuZXR3b3JrX2lkaM/orAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lime_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAwAACAQAbmFtZRkAbWluZWNyYWZ0OmxpbWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaANjADFOF9v7AwoAbmV0d29ya19pZJt0XsgKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:green_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAwAACAQAbmFtZRoAbWluZWNyYWZ0OmdyZWVuX3RlcnJhY290dGEECQBuYW1lX2hhc2j5Ybq36yYwRQMKAG5ldHdvcmtfaWQ8kGdHCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cyan_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAwAACAQAbmFtZRkAbWluZWNyYWZ0OmN5YW5fdGVycmFjb3R0YQQJAG5hbWVfaGFzaN09COzMuHwAAwoAbmV0d29ya19pZIWPCzoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_blue_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAwAACAQAbmFtZR8AbWluZWNyYWZ0OmxpZ2h0X2JsdWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaOMytez7cOZiAwoAbmV0d29ya19pZFHK1UsKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blue_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAwAACAQAbmFtZRkAbWluZWNyYWZ0OmJsdWVfdGVycmFjb3R0YQQJAG5hbWVfaGFzaF6inyTK5RpAAwoAbmV0d29ya19pZF5mVZIKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:purple_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAwAACAQAbmFtZRsAbWluZWNyYWZ0OnB1cnBsZV90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoKF7YG61yTbEDCgBuZXR3b3JrX2lkhtRDlwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:magenta_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAwAACAQAbmFtZRwAbWluZWNyYWZ0Om1hZ2VudGFfdGVycmFjb3R0YQQJAG5hbWVfaGFzaLWvtpAVtztyAwoAbmV0d29ya19pZN5SoakKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:pink_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAwAACAQAbmFtZRkAbWluZWNyYWZ0OnBpbmtfdGVycmFjb3R0YQQJAG5hbWVfaGFzaJ7mzvyzSQZTAwoAbmV0d29ya19pZDJWe4YKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAAAACAQAbmFtZSEAbWluZWNyYWZ0OndoaXRlX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoiVzCdoHAJo0DCgBuZXR3b3JrX2lkIlj9AAoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:silver_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAAAACAQAbmFtZSIAbWluZWNyYWZ0OnNpbHZlcl9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaAVsA0CnhzA4AwoAbmV0d29ya19pZPnxtJEKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gray_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAAAACAQAbmFtZSAAbWluZWNyYWZ0OmdyYXlfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2jvLZt9u/lF/AMKAG5ldHdvcmtfaWQVU8eFCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:black_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAAAACAQAbmFtZSEAbWluZWNyYWZ0OmJsYWNrX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoe8I4xAXbO5UDCgBuZXR3b3JrX2lk2Icb9AoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAAAACAQAbmFtZSEAbWluZWNyYWZ0OmJyb3duX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNoSiNZOobbpjoDCgBuZXR3b3JrX2lkJy0jwgoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAAAACAQAbmFtZR8AbWluZWNyYWZ0OnJlZF9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaBdWFGLmCLFVAwoAbmV0d29ya19pZMYBJSEKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:orange_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTdAAAACAQAbmFtZSIAbWluZWNyYWZ0Om9yYW5nZV9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaMyJMrnPr7szAwoAbmV0d29ya19pZN6+7TUKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:yellow_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAAAACAQAbmFtZSIAbWluZWNyYWZ0OnllbGxvd19nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaN6NaIhf6m0uAwoAbmV0d29ya19pZKRHXeoKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lime_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAAAACAQAbmFtZSAAbWluZWNyYWZ0OmxpbWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2iF3E68/rB2EAMKAG5ldHdvcmtfaWSP7qQWCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:green_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAAAACAQAbmFtZSEAbWluZWNyYWZ0OmdyZWVuX2dsYXplZF90ZXJyYWNvdHRhBAkAbmFtZV9oYXNow5mo8aQDFboDCgBuZXR3b3JrX2lkoF11kgoGAHN0YXRlcwMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:cyan_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAAAACAQAbmFtZSAAbWluZWNyYWZ0OmN5YW5fZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2gnNB+cCFRJhwMKAG5ldHdvcmtfaWT9buMtCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_blue_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAAAACAQAbmFtZSYAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2gladnCDBKCigMKAG5ldHdvcmtfaWS5CszFCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blue_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAAAACAQAbmFtZSAAbWluZWNyYWZ0OmJsdWVfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2giOZK+2nB1igMKAG5ldHdvcmtfaWR+e22CCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:purple_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTbAAAACAQAbmFtZSIAbWluZWNyYWZ0OnB1cnBsZV9nbGF6ZWRfdGVycmFjb3R0YQQJAG5hbWVfaGFzaIQU03txeAfHAwoAbmV0d29ya19pZLKbSE4KBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:magenta_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAAAACAQAbmFtZSMAbWluZWNyYWZ0Om1hZ2VudGFfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2i/SNqDJbfjMgMKAG5ldHdvcmtfaWQKf9UvCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:pink_glazed_terracotta", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAAAACAQAbmFtZSAAbWluZWNyYWZ0OnBpbmtfZ2xhemVkX3RlcnJhY290dGEECQBuYW1lX2hhc2hik8DVt4g+twMKAG5ldHdvcmtfaWTKzav2CgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:purpur_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAAAACAQAbmFtZRYAbWluZWNyYWZ0OnB1cnB1cl9ibG9jawQJAG5hbWVfaGFzaAgLwnUZGlzsAwoAbmV0d29ya19pZGCZ+4UKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purpur_pillar", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS2BAAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnB1cl9waWxsYXIECQBuYW1lX2hhc2iFcSsdykO+jgMKAG5ldHdvcmtfaWQe0+geCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:packed_mud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTcAgAACAQAbmFtZRQAbWluZWNyYWZ0OnBhY2tlZF9tdWQECQBuYW1lX2hhc2gHOMa121h4FgMKAG5ldHdvcmtfaWTUb6LyCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:mud_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAgAACAQAbmFtZRQAbWluZWNyYWZ0Om11ZF9icmlja3MECQBuYW1lX2hhc2iDL/SVl/PewQMKAG5ldHdvcmtfaWSkBjaDCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:nether_wart_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAAAACAQAbmFtZRsAbWluZWNyYWZ0Om5ldGhlcl93YXJ0X2Jsb2NrBAkAbmFtZV9oYXNo9XGS4GNnlV4DCgBuZXR3b3JrX2lkh3apIgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_wart_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAQAACAQAbmFtZRsAbWluZWNyYWZ0OndhcnBlZF93YXJ0X2Jsb2NrBAkAbmFtZV9oYXNo9IqDS9yUPJoDCgBuZXR3b3JrX2lkMpKAbAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:shroomlight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTlAQAACAQAbmFtZRUAbWluZWNyYWZ0OnNocm9vbWxpZ2h0BAkAbmFtZV9oYXNoZHCHcHX/HYADCgBuZXR3b3JrX2lkLG2JiwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:crimson_nylium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fbnlsaXVtBAkAbmFtZV9oYXNoOr6DJYW2bFYDCgBuZXR3b3JrX2lkuWpRDgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_nylium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWToAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9ueWxpdW0ECQBuYW1lX2hhc2g0Zf89cfr3rwMKAG5ldHdvcmtfaWSu/kekCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:netherrack", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXAAAACAQAbmFtZRQAbWluZWNyYWZ0Om5ldGhlcnJhY2sECQBuYW1lX2hhc2i/r5ZyRsvPyQMKAG5ldHdvcmtfaWTAiTOACgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhc2FsdAQJAG5hbWVfaGFzaH+UQO2yWodiAwoAbmV0d29ya19pZBPNSV4KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAQAACAQAbmFtZRkAbWluZWNyYWZ0OnBvbGlzaGVkX2Jhc2FsdAQJAG5hbWVfaGFzaMS+L0gMnRcBAwoAbmV0d29ya19pZF+/mHwKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:smooth_basalt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AgAACAQAbmFtZRcAbWluZWNyYWZ0OnNtb290aF9iYXNhbHQECQBuYW1lX2hhc2jKPUdz89kuNAMKAG5ldHdvcmtfaWTkb/oVCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:soul_soil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAQAACAQAbmFtZRMAbWluZWNyYWZ0OnNvdWxfc29pbAQJAG5hbWVfaGFzaC1/87ccutuTAwoAbmV0d29ya19pZKc63SMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dirt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAAAACAQAbmFtZQ4AbWluZWNyYWZ0OmRpcnQECQBuYW1lX2hhc2hXp6jnXAe+kQMKAG5ldHdvcmtfaWSG706CCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:coarse_dirt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTBBAAACAQAbmFtZRUAbWluZWNyYWZ0OmNvYXJzZV9kaXJ0BAkAbmFtZV9oYXNosd+cah7WSmoDCgBuZXR3b3JrX2lkgS5RcAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:farmland", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AAAACAQAbmFtZRIAbWluZWNyYWZ0OmZhcm1sYW5kBAkAbmFtZV9oYXNoxyQ5ag7LolADCgBuZXR3b3JrX2lkX618FQoGAHN0YXRlcwMSAG1vaXN0dXJpemVkX2Ftb3VudAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:grass_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQCAAAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXNzX2Jsb2NrBAkAbmFtZV9oYXNojPyGp3/CSZwDCgBuZXR3b3JrX2lktCgx3goGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:grass_path", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTGAAAACAQAbmFtZRQAbWluZWNyYWZ0OmdyYXNzX3BhdGgECQBuYW1lX2hhc2i0/KZV8Qsy+gMKAG5ldHdvcmtfaWT7CcdzCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:podzol", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTzAAAACAQAbmFtZRAAbWluZWNyYWZ0OnBvZHpvbAQJAG5hbWVfaGFzaBzqokRjH4Z1AwoAbmV0d29ya19pZPPS/GUKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mycelium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAAAACAQAbmFtZRIAbWluZWNyYWZ0Om15Y2VsaXVtBAkAbmFtZV9oYXNojTN09cKickIDCgBuZXR3b3JrX2lkLNPxXQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTYAgAACAQAbmFtZQ0AbWluZWNyYWZ0Om11ZAQJAG5hbWVfaGFzaPb/3P+uLy+9AwoAbmV0d29ya19pZPIUlUkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnN0b25lBAkAbmFtZV9oYXNoE3mqhJxzJycDCgBuZXR3b3JrX2lkIQ4xgAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:iron_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAAAACAQAbmFtZRIAbWluZWNyYWZ0Omlyb25fb3JlBAkAbmFtZV9oYXNoS7BYtLnfx3gDCgBuZXR3b3JrX2lk3loneQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAAAACAQAbmFtZRIAbWluZWNyYWZ0OmdvbGRfb3JlBAkAbmFtZV9oYXNoC5Y+DUGXLC4DCgBuZXR3b3JrX2lkNhvMfwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:diamond_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AAAACAQAbmFtZRUAbWluZWNyYWZ0OmRpYW1vbmRfb3JlBAkAbmFtZV9oYXNokUOJ2wZZrGQDCgBuZXR3b3JrX2lk/dChVAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lapis_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQVAAAACAQAbmFtZRMAbWluZWNyYWZ0OmxhcGlzX29yZQQJAG5hbWVfaGFzaMrmrUrSzb7qAwoAbmV0d29ya19pZMg+qK4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:redstone_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAAAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZHN0b25lX29yZQQJAG5hbWVfaGFzaFHVnp8Wc4JbAwoAbmV0d29ya19pZKDYvQoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:coal_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAAAACAQAbmFtZRIAbWluZWNyYWZ0OmNvYWxfb3JlBAkAbmFtZV9oYXNo1OjA+Iuy51oDCgBuZXR3b3JrX2lk+R/aKAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:copper_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2AgAACAQAbmFtZRQAbWluZWNyYWZ0OmNvcHBlcl9vcmUECQBuYW1lX2hhc2iSZduSntOzOwMKAG5ldHdvcmtfaWQtIuCnCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:emerald_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAAAACAQAbmFtZRUAbWluZWNyYWZ0OmVtZXJhbGRfb3JlBAkAbmFtZV9oYXNoJTovr+VgINsDCgBuZXR3b3JrX2lknbkqCgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:quartz_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAAAACAQAbmFtZRQAbWluZWNyYWZ0OnF1YXJ0el9vcmUECQBuYW1lX2hhc2g0yNHLMK9TaQMKAG5ldHdvcmtfaWSzN7nzCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:nether_gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfAgAACAQAbmFtZRkAbWluZWNyYWZ0Om5ldGhlcl9nb2xkX29yZQQJAG5hbWVfaGFzaEJZ7segIBgBAwoAbmV0d29ya19pZNI9pDgKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:ancient_debris", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAgAACAQAbmFtZRgAbWluZWNyYWZ0OmFuY2llbnRfZGVicmlzBAkAbmFtZV9oYXNoNrbxMc9AwKcDCgBuZXR3b3JrX2lkrSNjEAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:deepslate_iron_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSQAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9pcm9uX29yZQQJAG5hbWVfaGFzaB/fDL9pgvXXAwoAbmV0d29ya19pZFA0bz4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_gold_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9nb2xkX29yZQQJAG5hbWVfaGFzaF9G7WYhKFinAwoAbmV0d29ya19pZHQTfBUKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_diamond_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV9kaWFtb25kX29yZQQJAG5hbWVfaGFzaEUH5USh+iD3AwoAbmV0d29ya19pZHP6VzAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_lapis_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAgAACAQAbmFtZR0AbWluZWNyYWZ0OmRlZXBzbGF0ZV9sYXBpc19vcmUECQBuYW1lX2hhc2j+yFxU/KZs1gMKAG5ldHdvcmtfaWRKINzICgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate_redstone_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAgAACAQAbmFtZSAAbWluZWNyYWZ0OmRlZXBzbGF0ZV9yZWRzdG9uZV9vcmUECQBuYW1lX2hhc2iVgM3wWWD6ugMKAG5ldHdvcmtfaWReBdYRCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate_emerald_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSWAgAACAQAbmFtZR8AbWluZWNyYWZ0OmRlZXBzbGF0ZV9lbWVyYWxkX29yZQQJAG5hbWVfaGFzaNlfo5HTwS6wAwoAbmV0d29ya19pZNeie6sKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_coal_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSVAgAACAQAbmFtZRwAbWluZWNyYWZ0OmRlZXBzbGF0ZV9jb2FsX29yZQQJAG5hbWVfaGFzaIjikmcbRrPPAwoAbmV0d29ya19pZD9TiygKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:deepslate_copper_ore", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAgAACAQAbmFtZR4AbWluZWNyYWZ0OmRlZXBzbGF0ZV9jb3BwZXJfb3JlBAkAbmFtZV9oYXNottjV4Ev5LAQDCgBuZXR3b3JrX2lkP23rgQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gravel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAAAACAQAbmFtZRAAbWluZWNyYWZ0OmdyYXZlbAQJAG5hbWVfaGFzaOFxz8XJd2r/AwoAbmV0d29ya19pZBpfI1sKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:granite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAwAACAQAbmFtZREAbWluZWNyYWZ0OmdyYW5pdGUECQBuYW1lX2hhc2iq+Dur2pw4AwMKAG5ldHdvcmtfaWT2NMfJCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:diorite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAwAACAQAbmFtZREAbWluZWNyYWZ0OmRpb3JpdGUECQBuYW1lX2hhc2iaFsq2iinZBQMKAG5ldHdvcmtfaWQqGE6XCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:andesite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAwAACAQAbmFtZRIAbWluZWNyYWZ0OmFuZGVzaXRlBAkAbmFtZV9oYXNosaLIEnQQoSYDCgBuZXR3b3JrX2lkEApRZAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAgAACAQAbmFtZRQAbWluZWNyYWZ0OmJsYWNrc3RvbmUECQBuYW1lX2hhc2iMFYziD80D6QMKAG5ldHdvcmtfaWSrUryHCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AgAACAQAbmFtZRMAbWluZWNyYWZ0OmRlZXBzbGF0ZQQJAG5hbWVfaGFzaKX5pAblxz8TAwoAbmV0d29ya19pZOJoQjsKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_granite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBvbGlzaGVkX2dyYW5pdGUECQBuYW1lX2hhc2iLiEfys8pFIAMKAG5ldHdvcmtfaWTCxxcHCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_diorite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBvbGlzaGVkX2Rpb3JpdGUECQBuYW1lX2hhc2hTxY4fKmNmlAMKAG5ldHdvcmtfaWTmtjdRCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_andesite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSAwAACAQAbmFtZRsAbWluZWNyYWZ0OnBvbGlzaGVkX2FuZGVzaXRlBAkAbmFtZV9oYXNovl28uFk4HuQDCgBuZXR3b3JrX2lklFjuCwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_blackstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiAgAACAQAbmFtZR0AbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmUECQBuYW1lX2hhc2jT9fHCl6vWQQMKAG5ldHdvcmtfaWR/Ho6oCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AgAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaHC1edoaWF3uAwoAbmV0d29ya19pZCPeQsEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnNhbmQECQBuYW1lX2hhc2i6lthXXbAyWAMKAG5ldHdvcmtfaWRjeUMICgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:red_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS0BAAACAQAbmFtZRIAbWluZWNyYWZ0OnJlZF9zYW5kBAkAbmFtZV9oYXNoCiarI69JQCkDCgBuZXR3b3JrX2lkU8UD/AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cactus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAAAACAQAbmFtZRAAbWluZWNyYWZ0OmNhY3R1cwQJAG5hbWVfaGFzaCG9zL0N4wvGAwoAbmV0d29ya19pZDeCERAKBgBzdGF0ZXMDAwBhZ2UAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAAAACAQAbmFtZREAbWluZWNyYWZ0Om9ha19sb2cECQBuYW1lX2hhc2ho6TS+K7PZFQMKAG5ldHdvcmtfaWQjfjoxCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQJAQAACAQAbmFtZRoAbWluZWNyYWZ0OnN0cmlwcGVkX29ha19sb2cECQBuYW1lX2hhc2h8dqh+OOHU4wMKAG5ldHdvcmtfaWSYKjdrCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:spruce_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AwAACAQAbmFtZRQAbWluZWNyYWZ0OnNwcnVjZV9sb2cECQBuYW1lX2hhc2hZ03qaLoF3WgMKAG5ldHdvcmtfaWRlFD8eCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_spruce_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX3NwcnVjZV9sb2cECQBuYW1lX2hhc2iNrhKjS5IyrgMKAG5ldHdvcmtfaWRQcEC3CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:birch_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AwAACAQAbmFtZRMAbWluZWNyYWZ0OmJpcmNoX2xvZwQJAG5hbWVfaGFzaBUzT3NxsZAnAwoAbmV0d29ya19pZBKN3VQKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_birch_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAQAACAQAbmFtZRwAbWluZWNyYWZ0OnN0cmlwcGVkX2JpcmNoX2xvZwQJAG5hbWVfaGFzaCFKS4AeuSidAwoAbmV0d29ya19pZN0IONIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:jungle_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AwAACAQAbmFtZRQAbWluZWNyYWZ0Omp1bmdsZV9sb2cECQBuYW1lX2hhc2gkwW0KNulqDgMKAG5ldHdvcmtfaWQaziU/CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_jungle_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2p1bmdsZV9sb2cECQBuYW1lX2hhc2hAwMsgOk02JAMKAG5ldHdvcmtfaWQvls0eCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:acacia_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAAAACAQAbmFtZRQAbWluZWNyYWZ0OmFjYWNpYV9sb2cECQBuYW1lX2hhc2iV48VpYhjoYQMKAG5ldHdvcmtfaWRxEqe0CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_acacia_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAQAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2FjYWNpYV9sb2cECQBuYW1lX2hhc2hJb0lQqnEqlgMKAG5ldHdvcmtfaWRg3IdRCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dark_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7AwAACAQAbmFtZRYAbWluZWNyYWZ0OmRhcmtfb2FrX2xvZwQJAG5hbWVfaGFzaIWfVRd0XUo3AwoAbmV0d29ya19pZPMM7LYKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_dark_oak_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQIAQAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2Rhcmtfb2FrX2xvZwQJAG5hbWVfaGFzaPFTdxRdPwkOAwoAbmV0d29ya19pZDIzenIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mangrove_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAgAACAQAbmFtZRYAbWluZWNyYWZ0Om1hbmdyb3ZlX2xvZwQJAG5hbWVfaGFzaHZe6DzPZBobAwoAbmV0d29ya19pZG6DuYkKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_mangrove_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAgAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX21hbmdyb3ZlX2xvZwQJAG5hbWVfaGFzaLqIBo4hwA//AwoAbmV0d29ya19pZPtRn7UKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cherry_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAwAACAQAbmFtZRQAbWluZWNyYWZ0OmNoZXJyeV9sb2cECQBuYW1lX2hhc2hwFlaioppB1wMKAG5ldHdvcmtfaWS2sdXECgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_cherry_log", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQWAwAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2NoZXJyeV9sb2cECQBuYW1lX2hhc2i85H6G+WhXaAMKAG5ldHdvcmtfaWRjzoglCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTgAQAACAQAbmFtZRYAbWluZWNyYWZ0OmNyaW1zb25fc3RlbQQJAG5hbWVfaGFzaM0FzfL0UTKZAwoAbmV0d29ya19pZKvzID0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_crimson_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAQAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2NyaW1zb25fc3RlbQQJAG5hbWVfaGFzaDlA6nood57EAwoAbmV0d29ya19pZHrIqjIKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAQAACAQAbmFtZRUAbWluZWNyYWZ0OndhcnBlZF9zdGVtBAkAbmFtZV9oYXNon7cKfPZxdrUDCgBuZXR3b3JrX2lkerWyMwoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_warped_stem", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAQAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX3dhcnBlZF9zdGVtBAkAbmFtZV9oYXNoEw+y0dDPSd8DCgBuZXR3b3JrX2lkIQ9vBAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAQAACAQAbmFtZRIAbWluZWNyYWZ0Om9ha193b29kBAkAbmFtZV9oYXNoqQIkuVPyJX0DCgBuZXR3b3JrX2lku2G1YAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyBAAACAQAbmFtZRsAbWluZWNyYWZ0OnN0cmlwcGVkX29ha193b29kBAkAbmFtZV9oYXNovW6KCv+VZnsDCgBuZXR3b3JrX2lkkhWGegoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQtBAAACAQAbmFtZRUAbWluZWNyYWZ0OnNwcnVjZV93b29kBAkAbmFtZV9oYXNoTrIJ5TAQ+OgDCgBuZXR3b3JrX2lkaXLxCwoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_spruce_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzBAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX3NwcnVjZV93b29kBAkAbmFtZV9oYXNoMnuUk4Xo6icDCgBuZXR3b3JrX2lkes2ydAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuBAAACAQAbmFtZRQAbWluZWNyYWZ0OmJpcmNoX3dvb2QECQBuYW1lX2hhc2iqVjG4xt0cKQMKAG5ldHdvcmtfaWS06c5VCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_birch_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0BAAACAQAbmFtZR0AbWluZWNyYWZ0OnN0cmlwcGVkX2JpcmNoX3dvb2QECQBuYW1lX2hhc2hm88R604TKbAMKAG5ldHdvcmtfaWRleEMJCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvBAAACAQAbmFtZRUAbWluZWNyYWZ0Omp1bmdsZV93b29kBAkAbmFtZV9oYXNo9bYW29ORWCoDCgBuZXR3b3JrX2lkyFyKLQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_jungle_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ1BAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2p1bmdsZV93b29kBAkAbmFtZV9oYXNoUVs6KsZQRBoDCgBuZXR3b3JrX2lk92k8HQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQwBAAACAQAbmFtZRUAbWluZWNyYWZ0OmFjYWNpYV93b29kBAkAbmFtZV9oYXNoKkDfgzlJUcIDCgBuZXR3b3JrX2lkuTWlcgoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_acacia_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2BAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2FjYWNpYV93b29kBAkAbmFtZV9oYXNo/kOPN2bCJhUDCgBuZXR3b3JrX2lktl6LwQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxBAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhcmtfb2FrX3dvb2QECQBuYW1lX2hhc2jaKv4ORLadAAMKAG5ldHdvcmtfaWSDrNQ8CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_dark_oak_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3BAAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX2Rhcmtfb2FrX3dvb2QECQBuYW1lX2hhc2h2jFDfKVFgfAMKAG5ldHdvcmtfaWTgZQ5VCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAgAACAQAbmFtZRcAbWluZWNyYWZ0Om1hbmdyb3ZlX3dvb2QECQBuYW1lX2hhc2iXVxG0JG2fVAMKAG5ldHdvcmtfaWTok1JCCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkBDABzdHJpcHBlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_mangrove_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAgAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX21hbmdyb3ZlX3dvb2QECQBuYW1lX2hhc2h7CkbaBF7/WAMKAG5ldHdvcmtfaWQLAX88CgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhAwAACAQAbmFtZRUAbWluZWNyYWZ0OmNoZXJyeV93b29kBAkAbmFtZV9oYXNoAW8srlmpBM8DCgBuZXR3b3JrX2lkEALMfAoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AQwAc3RyaXBwZWRfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_cherry_wood", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAwAACAQAbmFtZR4AbWluZWNyYWZ0OnN0cmlwcGVkX2NoZXJyeV93b29kBAkAbmFtZV9oYXNo/e7KXv+CB38DCgBuZXR3b3JrX2lkg5aVtQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:crimson_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQqAgAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25faHlwaGFlBAkAbmFtZV9oYXNouRmKmfSqEWADCgBuZXR3b3JrX2lk+Tm5rQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stripped_crimson_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQrAgAACAQAbmFtZSEAbWluZWNyYWZ0OnN0cmlwcGVkX2NyaW1zb25faHlwaGFlBAkAbmFtZV9oYXNoFffwmABq4LUDCgBuZXR3b3JrX2lkZAlUbgoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:warped_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQpAgAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9oeXBoYWUECQBuYW1lX2hhc2hn8plQUr6pmQMKAG5ldHdvcmtfaWRU2AIBCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:stripped_warped_hyphae", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQsAgAACAQAbmFtZSAAbWluZWNyYWZ0OnN0cmlwcGVkX3dhcnBlZF9oeXBoYWUECQBuYW1lX2hhc2irKq+HYPSgjQMKAG5ldHdvcmtfaWSbrOPDCgYAc3RhdGVzCAsAcGlsbGFyX2F4aXMBAHkAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bamboo_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQOAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJhbWJvb19ibG9jawQJAG5hbWVfaGFzaAbDeur6stIBAwoAbmV0d29ya19pZCJAwn0KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:stripped_bamboo_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAwAACAQAbmFtZR8AbWluZWNyYWZ0OnN0cmlwcGVkX2JhbWJvb19ibG9jawQJAG5hbWVfaGFzaJpwytpZOZM9AwoAbmV0d29ya19pZKuRbNEKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oak_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQSAAAACAQAbmFtZRQAbWluZWNyYWZ0Om9ha19sZWF2ZXMECQBuYW1lX2hhc2h6O4xGqA2oKgMKAG5ldHdvcmtfaWT98c59CgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:spruce_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfBAAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9sZWF2ZXMECQBuYW1lX2hhc2i9x1CtNAuqZwMKAG5ldHdvcmtfaWSzF7pTCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:birch_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgBAAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX2xlYXZlcwQJAG5hbWVfaGFzaBlAGHaoaLZSAwoAbmV0d29ya19pZOjtvWcKBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:jungle_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhBAAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9sZWF2ZXMECQBuYW1lX2hhc2iW1uAH07zGhgMKAG5ldHdvcmtfaWSA5KX0CgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:acacia_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAAAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9sZWF2ZXMECQBuYW1lX2hhc2iZJf8dAgDRNQMKAG5ldHdvcmtfaWQ/G7VuCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dark_oak_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiBAAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX2xlYXZlcwQJAG5hbWVfaGFzaCk7rDipWFSjAwoAbmV0d29ya19pZJ2AkbYKBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:mangrove_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX2xlYXZlcwQJAG5hbWVfaGFzaKyI/dWvhEG8AwoAbmV0d29ya19pZPQxCZ8KBgBzdGF0ZXMBDgBwZXJzaXN0ZW50X2JpdAABCgB1cGRhdGVfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cherry_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQjAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9sZWF2ZXMECQBuYW1lX2hhc2giTs9ChhYBlQMKAG5ldHdvcmtfaWR8bPpwCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:azalea_leaves", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAgAACAQAbmFtZRcAbWluZWNyYWZ0OmF6YWxlYV9sZWF2ZXMECQBuYW1lX2hhc2iXFhD57wFS7AMKAG5ldHdvcmtfaWTNB/9ECgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:azalea_leaves_flowered", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREAgAACAQAbmFtZSAAbWluZWNyYWZ0OmF6YWxlYV9sZWF2ZXNfZmxvd2VyZWQECQBuYW1lX2hhc2gs8jxlS/pMrwMKAG5ldHdvcmtfaWQ7W4PyCgYAc3RhdGVzAQ4AcGVyc2lzdGVudF9iaXQAAQoAdXBkYXRlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:oak_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAAAACAQAbmFtZRUAbWluZWNyYWZ0Om9ha19zYXBsaW5nBAkAbmFtZV9oYXNoogXcT9QfjiUDCgBuZXR3b3JrX2lkG22C+AoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4BAAACAQAbmFtZRgAbWluZWNyYWZ0OnNwcnVjZV9zYXBsaW5nBAkAbmFtZV9oYXNoe8hz4uYP0FcDCgBuZXR3b3JrX2lkUQmhaQoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5BAAACAQAbmFtZRcAbWluZWNyYWZ0OmJpcmNoX3NhcGxpbmcECQBuYW1lX2hhc2h348iJQ/tK4wMKAG5ldHdvcmtfaWQ2Uh53CgYAc3RhdGVzAQcAYWdlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6BAAACAQAbmFtZRgAbWluZWNyYWZ0Omp1bmdsZV9zYXBsaW5nBAkAbmFtZV9oYXNo7tyTOdSrxaADCgBuZXR3b3JrX2lkXmBAdAoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7BAAACAQAbmFtZRgAbWluZWNyYWZ0OmFjYWNpYV9zYXBsaW5nBAkAbmFtZV9oYXNo99sg15uoX7ADCgBuZXR3b3JrX2lkPXX1KgoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8BAAACAQAbmFtZRoAbWluZWNyYWZ0OmRhcmtfb2FrX3NhcGxpbmcECQBuYW1lX2hhc2jnVzFplW7cHgMKAG5ldHdvcmtfaWTD4giHCgYAc3RhdGVzAQcAYWdlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_propagule", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAgAACAQAbmFtZRwAbWluZWNyYWZ0Om1hbmdyb3ZlX3Byb3BhZ3VsZQQJAG5hbWVfaGFzaJGeox6hkfLFAwoAbmV0d29ya19pZAIpvpYKBgBzdGF0ZXMBBwBoYW5naW5nAAMPAHByb3BhZ3VsZV9zdGFnZQAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_sapling", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQiAwAACAQAbmFtZRgAbWluZWNyYWZ0OmNoZXJyeV9zYXBsaW5nBAkAbmFtZV9oYXNoGrPpNMf1LtcDCgBuZXR3b3JrX2lkypakXQoGAHN0YXRlcwEHAGFnZV9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bee_nest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTZAQAACAQAbmFtZRIAbWluZWNyYWZ0OmJlZV9uZXN0BAkAbmFtZV9oYXNo2R2WBxUHEZIDCgBuZXR3b3JrX2lkiXWLEAoGAHN0YXRlcwMJAGRpcmVjdGlvbgAAAAADCwBob25leV9sZXZlbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:wheat_seeds" + }, + { + "id": "minecraft:pumpkin_seeds" + }, + { + "id": "minecraft:melon_seeds" + }, + { + "id": "minecraft:beetroot_seeds" + }, + { + "id": "minecraft:torchflower_seeds" + }, + { + "id": "minecraft:pitcher_pod" + }, + { + "id": "minecraft:wheat" + }, + { + "id": "minecraft:beetroot" + }, + { + "id": "minecraft:potato" + }, + { + "id": "minecraft:poisonous_potato" + }, + { + "id": "minecraft:carrot" + }, + { + "id": "minecraft:golden_carrot" + }, + { + "id": "minecraft:apple" + }, + { + "id": "minecraft:golden_apple" + }, + { + "id": "minecraft:enchanted_golden_apple" + }, + { + "id": "minecraft:melon_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAAAACAQAbmFtZRUAbWluZWNyYWZ0Om1lbG9uX2Jsb2NrBAkAbmFtZV9oYXNoXxSm0iYpAx8DCgBuZXR3b3JrX2lkC9rqygoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:melon_slice" + }, + { + "id": "minecraft:glistering_melon_slice" + }, + { + "id": "minecraft:sweet_berries" + }, + { + "id": "minecraft:glow_berries" + }, + { + "id": "minecraft:pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWAAAACAQAbmFtZREAbWluZWNyYWZ0OnB1bXBraW4ECQBuYW1lX2hhc2gc8A3jaSzWbgMKAG5ldHdvcmtfaWRFGA+xCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:carved_pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSaAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNhcnZlZF9wdW1wa2luBAkAbmFtZV9oYXNoPu1T0MJuG90DCgBuZXR3b3JrX2lkXNNn5QoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lit_pumpkin", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbAAAACAQAbmFtZRUAbWluZWNyYWZ0OmxpdF9wdW1wa2luBAkAbmFtZV9oYXNo7gWtEm2uPL0DCgBuZXR3b3JrX2lki8sU4AoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:honeycomb" + }, + { + "id": "minecraft:fern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPBAAACAQAbmFtZQ4AbWluZWNyYWZ0OmZlcm4ECQBuYW1lX2hhc2iHbj3yXFn4owMKAG5ldHdvcmtfaWQKC6u7CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:large_fern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRgBAAACAQAbmFtZRQAbWluZWNyYWZ0OmxhcmdlX2Zlcm4ECQBuYW1lX2hhc2gnE9sd0LzHtQMKAG5ldHdvcmtfaWTS9hG4CgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:short_grass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQfAAAACAQAbmFtZRUAbWluZWNyYWZ0OnNob3J0X2dyYXNzBAkAbmFtZV9oYXNobWQghLH0bLcDCgBuZXR3b3JrX2lkJWOOqAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tall_grass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRfBAAACAQAbmFtZRQAbWluZWNyYWZ0OnRhbGxfZ3Jhc3MECQBuYW1lX2hhc2ii5MyZJpv4sgMKAG5ldHdvcmtfaWRRfeH4CgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:nether_sprouts" + }, + { + "id": "minecraft:fire_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAwAACAQAbmFtZRQAbWluZWNyYWZ0OmZpcmVfY29yYWwECQBuYW1lX2hhc2hOHyyECVQVJwMKAG5ldHdvcmtfaWS9vF0UCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brain_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREAwAACAQAbmFtZRUAbWluZWNyYWZ0OmJyYWluX2NvcmFsBAkAbmFtZV9oYXNoRiWlLCwA2ycDCgBuZXR3b3JrX2lkrjAuhgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:bubble_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAwAACAQAbmFtZRYAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbAQJAG5hbWVfaGFzaJz6rWnl+v2qAwoAbmV0d29ya19pZImIWy0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:tube_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAQAACAQAbmFtZRQAbWluZWNyYWZ0OnR1YmVfY29yYWwECQBuYW1lX2hhc2iYa8oO/tgk7wMKAG5ldHdvcmtfaWRTfND5CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:horn_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHAwAACAQAbmFtZRQAbWluZWNyYWZ0Omhvcm5fY29yYWwECQBuYW1lX2hhc2iZnRHjZbnLPgMKAG5ldHdvcmtfaWR+GGp8CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_fire_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbAQJAG5hbWVfaGFzaEPU6tFy/latAwoAbmV0d29ya19pZNMa7V4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_brain_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAwAACAQAbmFtZRoAbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWwECQBuYW1lX2hhc2j5L6QJCISvzwMKAG5ldHdvcmtfaWQkKzeiCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKAwAACAQAbmFtZRsAbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsBAkAbmFtZV9oYXNoSTOZ/8wpeNYDCgBuZXR3b3JrX2lka6w9DAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dead_tube_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbAQJAG5hbWVfaGFzaJGjNWhlaIJeAwoAbmV0d29ya19pZO3Z0ygKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_horn_coral", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAwAACAQAbmFtZRkAbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbAQJAG5hbWVfaGFzaJBkz3qt+g2cAwoAbmV0d29ya19pZBAN+eYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:fire_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJBAAACAQAbmFtZRgAbWluZWNyYWZ0OmZpcmVfY29yYWxfZmFuBAkAbmFtZV9oYXNosOTxYYxsDLgDCgBuZXR3b3JrX2lkFKxbEgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brain_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHBAAACAQAbmFtZRkAbWluZWNyYWZ0OmJyYWluX2NvcmFsX2ZhbgQJAG5hbWVfaGFzaAi5uHizSNcqAwoAbmV0d29ya19pZFtLjNwKBgBzdGF0ZXMDEwBjb3JhbF9mYW5fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:bubble_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIBAAACAQAbmFtZRoAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hy/rX2on17DgMKAG5ldHdvcmtfaWQof60VCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:tube_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSEAQAACAQAbmFtZRgAbWluZWNyYWZ0OnR1YmVfY29yYWxfZmFuBAkAbmFtZV9oYXNo9pbJbo+PphIDCgBuZXR3b3JrX2lkenDTYgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:horn_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKBAAACAQAbmFtZRgAbWluZWNyYWZ0Omhvcm5fY29yYWxfZmFuBAkAbmFtZV9oYXNoA+ri6NPDkbUDCgBuZXR3b3JrX2lkezoHNwoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_fire_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNBAAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hpQO02NDxPvwMKAG5ldHdvcmtfaWTaOJgLCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_brain_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLBAAACAQAbmFtZR4AbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWxfZmFuBAkAbmFtZV9oYXNoI9/+Z4YqMhIDCgBuZXR3b3JrX2lkqYXxYgoGAHN0YXRlcwMTAGNvcmFsX2Zhbl9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsX2ZhbgQJAG5hbWVfaGFzaBNECtIM6VIOAwoAbmV0d29ya19pZLrNtBEKBgBzdGF0ZXMDEwBjb3JhbF9mYW5fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dead_tube_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSFAQAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbF9mYW4ECQBuYW1lX2hhc2hbBBM9jFKWvQMKAG5ldHdvcmtfaWSkJKUWCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_horn_coral_fan", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROBAAACAQAbmFtZR0AbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbF9mYW4ECQBuYW1lX2hhc2hObElFrHfPygMKAG5ldHdvcmtfaWQ1ZxvmCgYAc3RhdGVzAxMAY29yYWxfZmFuX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTeAQAACAQAbmFtZRcAbWluZWNyYWZ0OmNyaW1zb25fcm9vdHMECQBuYW1lX2hhc2j1fWgQLViv5QMKAG5ldHdvcmtfaWRLh5DXCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:warped_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTfAQAACAQAbmFtZRYAbWluZWNyYWZ0OndhcnBlZF9yb290cwQJAG5hbWVfaGFzaBc3WvbJOLlkAwoAbmV0d29ya19pZNLgDnAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dandelion", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQlAAAACAQAbmFtZRMAbWluZWNyYWZ0OmRhbmRlbGlvbgQJAG5hbWVfaGFzaBJ3bEUi+Nn/AwoAbmV0d29ya19pZBjjC44KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:poppy", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnBvcHB5BAkAbmFtZV9oYXNocMF8pITMbkcDCgBuZXR3b3JrX2lk8im6ywoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:blue_orchid", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9BAAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfb3JjaGlkBAkAbmFtZV9oYXNoBjz2MsgB21EDCgBuZXR3b3JrX2lk/iLsSwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:allium", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+BAAACAQAbmFtZRAAbWluZWNyYWZ0OmFsbGl1bQQJAG5hbWVfaGFzaDCGQBHNDTkcAwoAbmV0d29ya19pZD9Dgr0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:azure_bluet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/BAAACAQAbmFtZRUAbWluZWNyYWZ0OmF6dXJlX2JsdWV0BAkAbmFtZV9oYXNo9N5egqMT2QcDCgBuZXR3b3JrX2lkwIgDnwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:red_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRABAAACAQAbmFtZRMAbWluZWNyYWZ0OnJlZF90dWxpcAQJAG5hbWVfaGFzaAjMi9Rd+6rhAwoAbmV0d29ya19pZAZCnt8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:orange_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRBBAAACAQAbmFtZRYAbWluZWNyYWZ0Om9yYW5nZV90dWxpcAQJAG5hbWVfaGFzaP+NjxMBZ8vAAwoAbmV0d29ya19pZPYatsMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCBAAACAQAbmFtZRUAbWluZWNyYWZ0OndoaXRlX3R1bGlwBAkAbmFtZV9oYXNo5vbU4VRPh3ADCgBuZXR3b3JrX2lkok+4rQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_tulip", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDBAAACAQAbmFtZRQAbWluZWNyYWZ0OnBpbmtfdHVsaXAECQBuYW1lX2hhc2hxDHZa6OaNXAMKAG5ldHdvcmtfaWTiOT+VCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:oxeye_daisy", + "block_state_b64": "CgAAAwgAYmxvY2tfaWREBAAACAQAbmFtZRUAbWluZWNyYWZ0Om94ZXllX2RhaXN5BAkAbmFtZV9oYXNoXwxsqNQTN9gDCgBuZXR3b3JrX2lkw7R7dwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cornflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFBAAACAQAbmFtZRQAbWluZWNyYWZ0OmNvcm5mbG93ZXIECQBuYW1lX2hhc2gnhyC3EeqHgAMKAG5ldHdvcmtfaWR4VrvACgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lily_of_the_valley", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGBAAACAQAbmFtZRwAbWluZWNyYWZ0OmxpbHlfb2ZfdGhlX3ZhbGxleQQJAG5hbWVfaGFzaI64TJSf9mgQAwoAbmV0d29ya19pZFE9+nwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sunflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSvAAAACAQAbmFtZRMAbWluZWNyYWZ0OnN1bmZsb3dlcgQJAG5hbWVfaGFzaAMxYQLoqlZ0AwoAbmV0d29ya19pZA10iSoKBgBzdGF0ZXMBDwB1cHBlcl9ibG9ja19iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lilac", + "block_state_b64": "CgAAAwgAYmxvY2tfaWReBAAACAQAbmFtZQ8AbWluZWNyYWZ0OmxpbGFjBAkAbmFtZV9oYXNoD3nrQJuo7NkDCgBuZXR3b3JrX2lk5W+uFAoGAHN0YXRlcwEPAHVwcGVyX2Jsb2NrX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:rose_bush", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhBAAACAQAbmFtZRMAbWluZWNyYWZ0OnJvc2VfYnVzaAQJAG5hbWVfaGFzaLoiFk8LVpGKAwoAbmV0d29ya19pZMZPv48KBgBzdGF0ZXMBDwB1cHBlcl9ibG9ja19iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:peony", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRiBAAACAQAbmFtZQ8AbWluZWNyYWZ0OnBlb255BAkAbmFtZV9oYXNoR4dYc4QquPADCgBuZXR3b3JrX2lkrTe7RwoGAHN0YXRlcwEPAHVwcGVyX2Jsb2NrX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:pitcher_plant", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAwAACAQAbmFtZRcAbWluZWNyYWZ0OnBpdGNoZXJfcGxhbnQECQBuYW1lX2hhc2hRJHzsbDH+SQMKAG5ldHdvcmtfaWRnY76VCgYAc3RhdGVzAQ8AdXBwZXJfYmxvY2tfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_petals", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQkAwAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfcGV0YWxzBAkAbmFtZV9oYXNo6DQwN9SwV3QDCgBuZXR3b3JrX2lkNWneGgoGAHN0YXRlcwMGAGdyb3d0aAAAAAAIHABtaW5lY3JhZnQ6Y2FyZGluYWxfZGlyZWN0aW9uBQBzb3V0aAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:wither_rose", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXAQAACAQAbmFtZRUAbWluZWNyYWZ0OndpdGhlcl9yb3NlBAkAbmFtZV9oYXNoaSKxl3I516gDCgBuZXR3b3JrX2lkATXLPwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:torchflower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3AwAACAQAbmFtZRUAbWluZWNyYWZ0OnRvcmNoZmxvd2VyBAkAbmFtZV9oYXNoL+mHtElwbqQDCgBuZXR3b3JrX2lkI34O+AoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:white_dye" + }, + { + "id": "minecraft:light_gray_dye" + }, + { + "id": "minecraft:gray_dye" + }, + { + "id": "minecraft:black_dye" + }, + { + "id": "minecraft:brown_dye" + }, + { + "id": "minecraft:red_dye" + }, + { + "id": "minecraft:orange_dye" + }, + { + "id": "minecraft:yellow_dye" + }, + { + "id": "minecraft:lime_dye" + }, + { + "id": "minecraft:green_dye" + }, + { + "id": "minecraft:cyan_dye" + }, + { + "id": "minecraft:light_blue_dye" + }, + { + "id": "minecraft:blue_dye" + }, + { + "id": "minecraft:purple_dye" + }, + { + "id": "minecraft:magenta_dye" + }, + { + "id": "minecraft:pink_dye" + }, + { + "id": "minecraft:ink_sac" + }, + { + "id": "minecraft:glow_ink_sac" + }, + { + "id": "minecraft:cocoa_beans" + }, + { + "id": "minecraft:lapis_lazuli" + }, + { + "id": "minecraft:bone_meal" + }, + { + "id": "minecraft:vine", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnZpbmUECQBuYW1lX2hhc2j0Sj8/XeXOLAMKAG5ldHdvcmtfaWSUkDtbCgYAc3RhdGVzAxMAdmluZV9kaXJlY3Rpb25fYml0cwAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:weeping_vines", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAQAACAQAbmFtZRcAbWluZWNyYWZ0OndlZXBpbmdfdmluZXMECQBuYW1lX2hhc2jrLgLHkQygiwMKAG5ldHdvcmtfaWQ8NHSJCgYAc3RhdGVzAxEAd2VlcGluZ192aW5lc19hZ2UAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:twisting_vines", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAgAACAQAbmFtZRgAbWluZWNyYWZ0OnR3aXN0aW5nX3ZpbmVzBAkAbmFtZV9oYXNoDYR5QgVUQJADCgBuZXR3b3JrX2lk5kYVIQoGAHN0YXRlcwMSAHR3aXN0aW5nX3ZpbmVzX2FnZQAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:waterlily", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRvAAAACAQAbmFtZRMAbWluZWNyYWZ0OndhdGVybGlseQQJAG5hbWVfaGFzaEHgC4c1SXg0AwoAbmV0d29ya19pZOOerp8KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:seagrass", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSBAQAACAQAbmFtZRIAbWluZWNyYWZ0OnNlYWdyYXNzBAkAbmFtZV9oYXNoHSBFtoHdWxIDCgBuZXR3b3JrX2lkd3lhEAoGAHN0YXRlcwgOAHNlYV9ncmFzc190eXBlBwBkZWZhdWx0AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:kelp" + }, + { + "id": "minecraft:deadbush", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAAAACAQAbmFtZRIAbWluZWNyYWZ0OmRlYWRidXNoBAkAbmFtZV9oYXNoPFODe4IScnYDCgBuZXR3b3JrX2lkVfnl+goGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:bamboo", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhbWJvbwQJAG5hbWVfaGFzaBgpGmyzhedCAwoAbmV0d29ya19pZIZv1nYKBgBzdGF0ZXMBBwBhZ2VfYml0AAgQAGJhbWJvb19sZWFmX3NpemUJAG5vX2xlYXZlcwgWAGJhbWJvb19zdGFsa190aGlja25lc3MEAHRoaW4AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:snow", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnNub3cECQBuYW1lX2hhc2gVHr5XXdETWAMKAG5ldHdvcmtfaWQ0zCeHCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAAAACAQAbmFtZQ0AbWluZWNyYWZ0OmljZQQJAG5hbWVfaGFzaNF26f+uUT29AwoAbmV0d29ya19pZOUMaQYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:packed_ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSuAAAACAQAbmFtZRQAbWluZWNyYWZ0OnBhY2tlZF9pY2UECQBuYW1lX2hhc2hk4bu123ZrFgMKAG5ldHdvcmtfaWTr/ooaCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blue_ice", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQKAQAACAQAbmFtZRIAbWluZWNyYWZ0OmJsdWVfaWNlBAkAbmFtZV9oYXNo+EKxYgFhKcgDCgBuZXR3b3JrX2lkxfsA8goGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:snow_layer", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAAAACAQAbmFtZRQAbWluZWNyYWZ0OnNub3dfbGF5ZXIECQBuYW1lX2hhc2hXka6atMYUCQMKAG5ldHdvcmtfaWRCrIPcCgYAc3RhdGVzAQsAY292ZXJlZF9iaXQAAwYAaGVpZ2h0AAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pointed_dripstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQzAgAACAQAbmFtZRsAbWluZWNyYWZ0OnBvaW50ZWRfZHJpcHN0b25lBAkAbmFtZV9oYXNoJMISzmHQgt8DCgBuZXR3b3JrX2lkbWrtYgoGAHN0YXRlcwgTAGRyaXBzdG9uZV90aGlja25lc3MDAHRpcAEHAGhhbmdpbmcBAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dripstone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AgAACAQAbmFtZRkAbWluZWNyYWZ0OmRyaXBzdG9uZV9ibG9jawQJAG5hbWVfaGFzaIIXnEqY77YsAwoAbmV0d29ya19pZMZi2kwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:moss_carpet", + "block_state_b64": "CgAAAwgAYmxvY2tfaWROAgAACAQAbmFtZRUAbWluZWNyYWZ0Om1vc3NfY2FycGV0BAkAbmFtZV9oYXNo/NEDxRPTshYDCgBuZXR3b3JrX2lkaGG3QwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:moss_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ/AgAACAQAbmFtZRQAbWluZWNyYWZ0Om1vc3NfYmxvY2sECQBuYW1lX2hhc2iovcsPUYX2tgMKAG5ldHdvcmtfaWT3JSbfCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dirt_with_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9AgAACAQAbmFtZRkAbWluZWNyYWZ0OmRpcnRfd2l0aF9yb290cwQJAG5hbWVfaGFzaLCNDYPviDCIAwoAbmV0d29ya19pZNCkwzoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:hanging_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ+AgAACAQAbmFtZRcAbWluZWNyYWZ0Omhhbmdpbmdfcm9vdHMECQBuYW1lX2hhc2jaXn+Y5UZpDAMKAG5ldHdvcmtfaWRU4c2vCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:mangrove_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWThAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hbmdyb3ZlX3Jvb3RzBAkAbmFtZV9oYXNoa786PzQGZ6kDCgBuZXR3b3JrX2lklA0AHgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:muddy_mangrove_roots", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTiAgAACAQAbmFtZR4AbWluZWNyYWZ0Om11ZGR5X21hbmdyb3ZlX3Jvb3RzBAkAbmFtZV9oYXNo9YApdHpo1RkDCgBuZXR3b3JrX2lkH0Oc4woGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:big_dripleaf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJpZ19kcmlwbGVhZgQJAG5hbWVfaGFzaGBEhXjo6qSdAwoAbmV0d29ya19pZMETsb8KBgBzdGF0ZXMBEQBiaWdfZHJpcGxlYWZfaGVhZAEIEQBiaWdfZHJpcGxlYWZfdGlsdAQAbm9uZQgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:small_dripleaf_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRPAgAACAQAbmFtZR4AbWluZWNyYWZ0OnNtYWxsX2RyaXBsZWFmX2Jsb2NrBAkAbmFtZV9oYXNojxRAgXP9uWADCgBuZXR3b3JrX2lkozbVPwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24EAGVhc3QBDwB1cHBlcl9ibG9ja19iaXQBAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spore_blossom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRAAgAACAQAbmFtZRcAbWluZWNyYWZ0OnNwb3JlX2Jsb3Nzb20ECQBuYW1lX2hhc2il3U72Gbco2gMKAG5ldHdvcmtfaWSbbbgcCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:azalea", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQAgAACAQAbmFtZRAAbWluZWNyYWZ0OmF6YWxlYQQJAG5hbWVfaGFzaNyUl+BW9JrBAwoAbmV0d29ya19pZO/XZtQKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:flowering_azalea", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRAgAACAQAbmFtZRoAbWluZWNyYWZ0OmZsb3dlcmluZ19hemFsZWEECQBuYW1lX2hhc2ie9r33wz8kiwMKAG5ldHdvcmtfaWQ3ij0VCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:glow_lichen", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSaAgAACAQAbmFtZRUAbWluZWNyYWZ0Omdsb3dfbGljaGVuBAkAbmFtZV9oYXNobyPUrIYlo44DCgBuZXR3b3JrX2lkCh8lSAoGAHN0YXRlcwMZAG11bHRpX2ZhY2VfZGlyZWN0aW9uX2JpdHM/AAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:amethyst_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAgAACAQAbmFtZRgAbWluZWNyYWZ0OmFtZXRoeXN0X2Jsb2NrBAkAbmFtZV9oYXNob+JK1iiAthcDCgBuZXR3b3JrX2lk8HtpzgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:budding_amethyst", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRHAgAACAQAbmFtZRoAbWluZWNyYWZ0OmJ1ZGRpbmdfYW1ldGh5c3QECQBuYW1lX2hhc2gJvAwfI14fxgMKAG5ldHdvcmtfaWTQYqfACgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:amethyst_cluster", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAgAACAQAbmFtZRoAbWluZWNyYWZ0OmFtZXRoeXN0X2NsdXN0ZXIECQBuYW1lX2hhc2jK82S88Jgm8wMKAG5ldHdvcmtfaWSCPMPGCgYAc3RhdGVzCBQAbWluZWNyYWZ0OmJsb2NrX2ZhY2UCAHVwAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:large_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRJAgAACAQAbmFtZRwAbWluZWNyYWZ0OmxhcmdlX2FtZXRoeXN0X2J1ZAQJAG5hbWVfaGFzaAHhdpWD+sd5AwoAbmV0d29ya19pZKkQxOcKBgBzdGF0ZXMIFABtaW5lY3JhZnQ6YmxvY2tfZmFjZQIAdXAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:medium_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRKAgAACAQAbmFtZR0AbWluZWNyYWZ0Om1lZGl1bV9hbWV0aHlzdF9idWQECQBuYW1lX2hhc2g5lBGtC0DzZQMKAG5ldHdvcmtfaWSYiP4gCgYAc3RhdGVzCBQAbWluZWNyYWZ0OmJsb2NrX2ZhY2UCAHVwAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:small_amethyst_bud", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRLAgAACAQAbmFtZRwAbWluZWNyYWZ0OnNtYWxsX2FtZXRoeXN0X2J1ZAQJAG5hbWVfaGFzaEnb4+q9PO4YAwoAbmV0d29ya19pZGWzxrQKBgBzdGF0ZXMIFABtaW5lY3JhZnQ6YmxvY2tfZmFjZQIAdXAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAgAACAQAbmFtZQ4AbWluZWNyYWZ0OnR1ZmYECQBuYW1lX2hhc2h1Rwc1XYsBGwMKAG5ldHdvcmtfaWRwQGn0CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:tuff_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAwAACAQAbmFtZRUAbWluZWNyYWZ0OnR1ZmZfc3RhaXJzBAkAbmFtZV9oYXNoKjyNUBjcfZsDCgBuZXR3b3JrX2lk+LsycgoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tuff_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTnAwAACAQAbmFtZRMAbWluZWNyYWZ0OnR1ZmZfc2xhYgQJAG5hbWVfaGFzaIhCGdlIsnMUAwoAbmV0d29ya19pZN1dUL4KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:tuff_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTqAwAACAQAbmFtZRMAbWluZWNyYWZ0OnR1ZmZfd2FsbAQJAG5hbWVfaGFzaMyeeu1IRf03AwoAbmV0d29ya19pZDkIrosKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chiseled_tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoaXNlbGVkX3R1ZmYECQBuYW1lX2hhc2iVliOT8OTQ9AMKAG5ldHdvcmtfaWTLNKOiCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_tuff", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTrAwAACAQAbmFtZRcAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmYECQBuYW1lX2hhc2hyaLe/KEVZ0gMKAG5ldHdvcmtfaWTcX3NrCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_tuff_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTuAwAACAQAbmFtZR4AbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfc3RhaXJzBAkAbmFtZV9oYXNo8yuah8QI1dcDCgBuZXR3b3JrX2lkjLoU4AoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:polished_tuff_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTsAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfc2xhYgQJAG5hbWVfaGFzaLXdb48YvAsHAwoAbmV0d29ya19pZAnJ7W0KBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_tuff_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTvAwAACAQAbmFtZRwAbWluZWNyYWZ0OnBvbGlzaGVkX3R1ZmZfd2FsbAQJAG5hbWVfaGFzaJVZj6QYWXUrAwoAbmV0d29ya19pZLU7dooKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tuff_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTxAwAACAQAbmFtZRUAbWluZWNyYWZ0OnR1ZmZfYnJpY2tzBAkAbmFtZV9oYXNo/hbQ+mXSK7wDCgBuZXR3b3JrX2lk6gmIwQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tuff_brick_stairs", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT0AwAACAQAbmFtZRsAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfc3RhaXJzBAkAbmFtZV9oYXNoWJpkAurUfKwDCgBuZXR3b3JrX2lkUMcjiwoGAHN0YXRlcwEPAHVwc2lkZV9kb3duX2JpdAADEAB3ZWlyZG9fZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tuff_brick_slab", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTyAwAACAQAbmFtZRkAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfc2xhYgQJAG5hbWVfaGFzaLqPMjVCv5dIAwoAbmV0d29ya19pZOmeRhcKBgBzdGF0ZXMIFwBtaW5lY3JhZnQ6dmVydGljYWxfaGFsZgYAYm90dG9tAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:tuff_brick_wall", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT1AwAACAQAbmFtZRkAbWluZWNyYWZ0OnR1ZmZfYnJpY2tfd2FsbAQJAG5hbWVfaGFzaIL0IyNCOsonAwoAbmV0d29ya19pZJW4T5UKBgBzdGF0ZXMIGQB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9lYXN0BABub25lCBoAd2FsbF9jb25uZWN0aW9uX3R5cGVfbm9ydGgEAG5vbmUIGgB3YWxsX2Nvbm5lY3Rpb25fdHlwZV9zb3V0aAQAbm9uZQgZAHdhbGxfY29ubmVjdGlvbl90eXBlX3dlc3QEAG5vbmUBDQB3YWxsX3Bvc3RfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chiseled_tuff_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT2AwAACAQAbmFtZR4AbWluZWNyYWZ0OmNoaXNlbGVkX3R1ZmZfYnJpY2tzBAkAbmFtZV9oYXNo3oQw6gmxYuADCgBuZXR3b3JrX2lkm3D8AgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:calcite", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAgAACAQAbmFtZREAbWluZWNyYWZ0OmNhbGNpdGUECQBuYW1lX2hhc2ixKLu8ZIdzDQMKAG5ldHdvcmtfaWQlSbJDCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:chicken" + }, + { + "id": "minecraft:porkchop" + }, + { + "id": "minecraft:beef" + }, + { + "id": "minecraft:mutton" + }, + { + "id": "minecraft:rabbit" + }, + { + "id": "minecraft:cod" + }, + { + "id": "minecraft:salmon" + }, + { + "id": "minecraft:tropical_fish" + }, + { + "id": "minecraft:pufferfish" + }, + { + "id": "minecraft:brown_mushroom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAAAACAQAbmFtZRgAbWluZWNyYWZ0OmJyb3duX211c2hyb29tBAkAbmFtZV9oYXNonYw/FO78WDoDCgBuZXR3b3JrX2lkLh1OXAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:red_mushroom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQoAAAACAQAbmFtZRYAbWluZWNyYWZ0OnJlZF9tdXNocm9vbQQJAG5hbWVfaGFzaPpzJua7669xAwoAbmV0d29ya19pZCvWPYkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_fungus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTjAQAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fZnVuZ3VzBAkAbmFtZV9oYXNolIcCUuFM2u0DCgBuZXR3b3JrX2lkD2NN0QoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_fungus", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTkAQAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9mdW5ndXMECQBuYW1lX2hhc2gq8bSnRVTAFgMKAG5ldHdvcmtfaWTkwS+rCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkdOMhDAoGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cw4AAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAAAACAQAbmFtZRwAbWluZWNyYWZ0OnJlZF9tdXNocm9vbV9ibG9jawQJAG5hbWVfaGFzaJTTyJbth9M9AwoAbmV0d29ya19pZM+AyboKBgBzdGF0ZXMDEgBodWdlX211c2hyb29tX2JpdHMOAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkbdt3CAoGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cw8AAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brown_mushroom_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRjAAAACAQAbmFtZR4AbWluZWNyYWZ0OmJyb3duX211c2hyb29tX2Jsb2NrBAkAbmFtZV9oYXNoIyjnbI6xy9sDCgBuZXR3b3JrX2lkSrMl9goGAHN0YXRlcwMSAGh1Z2VfbXVzaHJvb21fYml0cwAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:egg" + }, + { + "id": "minecraft:sugar_cane" + }, + { + "id": "minecraft:sugar" + }, + { + "id": "minecraft:rotten_flesh" + }, + { + "id": "minecraft:bone" + }, + { + "id": "minecraft:web", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQeAAAACAQAbmFtZQ0AbWluZWNyYWZ0OndlYgQJAG5hbWVfaGFzaA4GKQCvG4i9AwoAbmV0d29ya19pZApt+jgKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:spider_eye" + }, + { + "id": "minecraft:mob_spawner", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ0AAAACAQAbmFtZRUAbWluZWNyYWZ0Om1vYl9zcGF3bmVyBAkAbmFtZV9oYXNoNwGrCV/Fkh8DCgBuZXR3b3JrX2lkM1wTmgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:trial_spawner", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AgAACAQAbmFtZRcAbWluZWNyYWZ0OnRyaWFsX3NwYXduZXIECQBuYW1lX2hhc2iNLRPB4ACz+QMKAG5ldHdvcmtfaWTWFYHGCgYAc3RhdGVzAQcAb21pbm91cwADEwB0cmlhbF9zcGF3bmVyX3N0YXRlAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:vault", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ5AgAACAQAbmFtZQ8AbWluZWNyYWZ0OnZhdWx0BAkAbmFtZV9oYXNoCAp9n3IAyqcDCgBuZXR3b3JrX2lk6/P+vwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAQcAb21pbm91cwAICwB2YXVsdF9zdGF0ZQgAaW5hY3RpdmUAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:end_portal_frame", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR4AAAACAQAbmFtZRoAbWluZWNyYWZ0OmVuZF9wb3J0YWxfZnJhbWUECQBuYW1lX2hhc2gqofyUIjGOpQMKAG5ldHdvcmtfaWRbGHf8CgYAc3RhdGVzARIAZW5kX3BvcnRhbF9leWVfYml0AAgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:infested_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRhAAAACAQAbmFtZRgAbWluZWNyYWZ0OmluZmVzdGVkX3N0b25lBAkAbmFtZV9oYXNoxnRcHDu4zqQDCgBuZXR3b3JrX2lkpfcnsgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:infested_cobblestone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZBAAACAQAbmFtZR4AbWluZWNyYWZ0OmluZmVzdGVkX2NvYmJsZXN0b25lBAkAbmFtZV9oYXNoy+LVCKG2kVMDCgBuZXR3b3JrX2lkpn+icAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:infested_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRaBAAACAQAbmFtZR8AbWluZWNyYWZ0OmluZmVzdGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaBMnals7a32CAwoAbmV0d29ya19pZNHi2UYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:infested_mossy_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRbBAAACAQAbmFtZSUAbWluZWNyYWZ0OmluZmVzdGVkX21vc3N5X3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaAmJk+HmVq0rAwoAbmV0d29ya19pZAVH8/sKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:infested_cracked_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRcBAAACAQAbmFtZScAbWluZWNyYWZ0OmluZmVzdGVkX2NyYWNrZWRfc3RvbmVfYnJpY2tzBAkAbmFtZV9oYXNoMyc60XcfcyoDCgBuZXR3b3JrX2lkaW+kbQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:infested_chiseled_stone_bricks", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRdBAAACAQAbmFtZSgAbWluZWNyYWZ0OmluZmVzdGVkX2NoaXNlbGVkX3N0b25lX2JyaWNrcwQJAG5hbWVfaGFzaNUvNIIg9dZbAwoAbmV0d29ya19pZCajGicKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:infested_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAgAACAQAbmFtZRwAbWluZWNyYWZ0OmluZmVzdGVkX2RlZXBzbGF0ZQQJAG5hbWVfaGFzaICF2VYccxF1AwoAbmV0d29ya19pZDa/624KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dragon_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR6AAAACAQAbmFtZRQAbWluZWNyYWZ0OmRyYWdvbl9lZ2cECQBuYW1lX2hhc2inMzXrV+/e1wMKAG5ldHdvcmtfaWTgO1yRCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:turtle_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAQAACAQAbmFtZRQAbWluZWNyYWZ0OnR1cnRsZV9lZ2cECQBuYW1lX2hhc2iwSRcxOJIJ9gMKAG5ldHdvcmtfaWSIRNUhCgYAc3RhdGVzCA0AY3JhY2tlZF9zdGF0ZQkAbm9fY3JhY2tzCBAAdHVydGxlX2VnZ19jb3VudAcAb25lX2VnZwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sniffer_egg", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTAwAACAQAbmFtZRUAbWluZWNyYWZ0OnNuaWZmZXJfZWdnBAkAbmFtZV9oYXNoY1lozc8lPcYDCgBuZXR3b3JrX2lk7yb/2QoGAHN0YXRlcwgNAGNyYWNrZWRfc3RhdGUJAG5vX2NyYWNrcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:frog_spawn", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTTAgAACAQAbmFtZRQAbWluZWNyYWZ0OmZyb2dfc3Bhd24ECQBuYW1lX2hhc2iWmd7idp3ZZwMKAG5ldHdvcmtfaWRFzJudCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:pearlescent_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAgAACAQAbmFtZR8AbWluZWNyYWZ0OnBlYXJsZXNjZW50X2Zyb2dsaWdodAQJAG5hbWVfaGFzaKkcFRyycYGyAwoAbmV0d29ya19pZJqYakAKBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:verdant_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAgAACAQAbmFtZRsAbWluZWNyYWZ0OnZlcmRhbnRfZnJvZ2xpZ2h0BAkAbmFtZV9oYXNoA+eXuTBohrQDCgBuZXR3b3JrX2lkDIVnsQoGAHN0YXRlcwgLAHBpbGxhcl9heGlzAQB5AAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:ochre_froglight", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTWAgAACAQAbmFtZRkAbWluZWNyYWZ0Om9jaHJlX2Zyb2dsaWdodAQJAG5hbWVfaGFzaMY59kjPe+c3AwoAbmV0d29ya19pZO2TD50KBgBzdGF0ZXMICwBwaWxsYXJfYXhpcwEAeQADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chicken_spawn_egg" + }, + { + "id": "minecraft:bee_spawn_egg" + }, + { + "id": "minecraft:cow_spawn_egg" + }, + { + "id": "minecraft:pig_spawn_egg" + }, + { + "id": "minecraft:sheep_spawn_egg" + }, + { + "id": "minecraft:wolf_spawn_egg" + }, + { + "id": "minecraft:polar_bear_spawn_egg" + }, + { + "id": "minecraft:ocelot_spawn_egg" + }, + { + "id": "minecraft:cat_spawn_egg" + }, + { + "id": "minecraft:mooshroom_spawn_egg" + }, + { + "id": "minecraft:bat_spawn_egg" + }, + { + "id": "minecraft:parrot_spawn_egg" + }, + { + "id": "minecraft:rabbit_spawn_egg" + }, + { + "id": "minecraft:llama_spawn_egg" + }, + { + "id": "minecraft:horse_spawn_egg" + }, + { + "id": "minecraft:donkey_spawn_egg" + }, + { + "id": "minecraft:mule_spawn_egg" + }, + { + "id": "minecraft:skeleton_horse_spawn_egg" + }, + { + "id": "minecraft:zombie_horse_spawn_egg" + }, + { + "id": "minecraft:tropical_fish_spawn_egg" + }, + { + "id": "minecraft:cod_spawn_egg" + }, + { + "id": "minecraft:pufferfish_spawn_egg" + }, + { + "id": "minecraft:salmon_spawn_egg" + }, + { + "id": "minecraft:dolphin_spawn_egg" + }, + { + "id": "minecraft:turtle_spawn_egg" + }, + { + "id": "minecraft:panda_spawn_egg" + }, + { + "id": "minecraft:fox_spawn_egg" + }, + { + "id": "minecraft:creeper_spawn_egg" + }, + { + "id": "minecraft:enderman_spawn_egg" + }, + { + "id": "minecraft:silverfish_spawn_egg" + }, + { + "id": "minecraft:skeleton_spawn_egg" + }, + { + "id": "minecraft:wither_skeleton_spawn_egg" + }, + { + "id": "minecraft:stray_spawn_egg" + }, + { + "id": "minecraft:slime_spawn_egg" + }, + { + "id": "minecraft:spider_spawn_egg" + }, + { + "id": "minecraft:zombie_spawn_egg" + }, + { + "id": "minecraft:zombie_pigman_spawn_egg" + }, + { + "id": "minecraft:husk_spawn_egg" + }, + { + "id": "minecraft:drowned_spawn_egg" + }, + { + "id": "minecraft:squid_spawn_egg" + }, + { + "id": "minecraft:glow_squid_spawn_egg" + }, + { + "id": "minecraft:cave_spider_spawn_egg" + }, + { + "id": "minecraft:witch_spawn_egg" + }, + { + "id": "minecraft:guardian_spawn_egg" + }, + { + "id": "minecraft:elder_guardian_spawn_egg" + }, + { + "id": "minecraft:endermite_spawn_egg" + }, + { + "id": "minecraft:magma_cube_spawn_egg" + }, + { + "id": "minecraft:strider_spawn_egg" + }, + { + "id": "minecraft:hoglin_spawn_egg" + }, + { + "id": "minecraft:piglin_spawn_egg" + }, + { + "id": "minecraft:zoglin_spawn_egg" + }, + { + "id": "minecraft:piglin_brute_spawn_egg" + }, + { + "id": "minecraft:goat_spawn_egg" + }, + { + "id": "minecraft:axolotl_spawn_egg" + }, + { + "id": "minecraft:warden_spawn_egg" + }, + { + "id": "minecraft:allay_spawn_egg" + }, + { + "id": "minecraft:frog_spawn_egg" + }, + { + "id": "minecraft:tadpole_spawn_egg" + }, + { + "id": "minecraft:trader_llama_spawn_egg" + }, + { + "id": "minecraft:camel_spawn_egg" + }, + { + "id": "minecraft:ghast_spawn_egg" + }, + { + "id": "minecraft:blaze_spawn_egg" + }, + { + "id": "minecraft:shulker_spawn_egg" + }, + { + "id": "minecraft:vindicator_spawn_egg" + }, + { + "id": "minecraft:evoker_spawn_egg" + }, + { + "id": "minecraft:vex_spawn_egg" + }, + { + "id": "minecraft:villager_spawn_egg" + }, + { + "id": "minecraft:wandering_trader_spawn_egg" + }, + { + "id": "minecraft:zombie_villager_spawn_egg" + }, + { + "id": "minecraft:phantom_spawn_egg" + }, + { + "id": "minecraft:pillager_spawn_egg" + }, + { + "id": "minecraft:ravager_spawn_egg" + }, + { + "id": "minecraft:iron_golem_spawn_egg" + }, + { + "id": "minecraft:snow_golem_spawn_egg" + }, + { + "id": "minecraft:sniffer_spawn_egg" + }, + { + "id": "minecraft:breeze_spawn_egg" + }, + { + "id": "minecraft:armadillo_spawn_egg" + }, + { + "id": "minecraft:bogged_spawn_egg" + }, + { + "id": "minecraft:obsidian", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQxAAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ic2lkaWFuBAkAbmFtZV9oYXNoiz4qrb8QjyEDCgBuZXR3b3JrX2lkuqnPpQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:crying_obsidian", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQgAgAACAQAbmFtZRkAbWluZWNyYWZ0OmNyeWluZ19vYnNpZGlhbgQJAG5hbWVfaGFzaKT0JlA7Z1K+AwoAbmV0d29ya19pZCjbPV4KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bedrock", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQHAAAACAQAbmFtZREAbWluZWNyYWZ0OmJlZHJvY2sECQBuYW1lX2hhc2hWfFrh4LVtxwMKAG5ldHdvcmtfaWT7fKz1CgYAc3RhdGVzAQ4AaW5maW5pYnVybl9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:soul_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYAAAACAQAbmFtZRMAbWluZWNyYWZ0OnNvdWxfc2FuZAQJAG5hbWVfaGFzaMaf+bccu+KTAwoAbmV0d29ya19pZBQSHrMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:magma", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTVAAAACAQAbmFtZQ8AbWluZWNyYWZ0Om1hZ21hBAkAbmFtZV9oYXNoqyTjKaIsWfYDCgBuZXR3b3JrX2lkyfWAZgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:nether_wart" + }, + { + "id": "minecraft:end_stone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR5AAAACAQAbmFtZRMAbWluZWNyYWZ0OmVuZF9zdG9uZQQJAG5hbWVfaGFzaH1J9jA39GJNAwoAbmV0d29ya19pZFeFQ7UKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chorus_flower", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAAAACAQAbmFtZRcAbWluZWNyYWZ0OmNob3J1c19mbG93ZXIECQBuYW1lX2hhc2iMpSodli5uawMKAG5ldHdvcmtfaWRnd1ZWCgYAc3RhdGVzAwMAYWdlAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:chorus_plant", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTwAAAACAQAbmFtZRYAbWluZWNyYWZ0OmNob3J1c19wbGFudAQJAG5hbWVfaGFzaJhSrmNGKwaMAwoAbmV0d29ya19pZA3uVqMKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chorus_fruit" + }, + { + "id": "minecraft:popped_chorus_fruit" + }, + { + "id": "minecraft:sponge", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQTAAAACAQAbmFtZRAAbWluZWNyYWZ0OnNwb25nZQQJAG5hbWVfaGFzaLrd2ScYRDMiAwoAbmV0d29ya19pZNmQW/oKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:wet_sponge", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTXBAAACAQAbmFtZRQAbWluZWNyYWZ0OndldF9zcG9uZ2UECQBuYW1lX2hhc2htp6nKpSHcAgMKAG5ldHdvcmtfaWQaW+fCCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:tube_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAQAACAQAbmFtZRoAbWluZWNyYWZ0OnR1YmVfY29yYWxfYmxvY2sECQBuYW1lX2hhc2iGkaiR7Eot4wMKAG5ldHdvcmtfaWQPNJ6sCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:brain_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRQBAAACAQAbmFtZRsAbWluZWNyYWZ0OmJyYWluX2NvcmFsX2Jsb2NrBAkAbmFtZV9oYXNoeDNAK18yUo4DCgBuZXR3b3JrX2lkloN1vgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:bubble_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRRBAAACAQAbmFtZRwAbWluZWNyYWZ0OmJ1YmJsZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaAI2mwMlvcNbAwoAbmV0d29ya19pZBlkxKIKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:fire_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRSBAAACAQAbmFtZRoAbWluZWNyYWZ0OmZpcmVfY29yYWxfYmxvY2sECQBuYW1lX2hhc2gg1gLeXLmKaAMKAG5ldHdvcmtfaWSp3W57CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:horn_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRTBAAACAQAbmFtZRoAbWluZWNyYWZ0Omhvcm5fY29yYWxfYmxvY2sECQBuYW1lX2hhc2hnZSLRWUwGhAMKAG5ldHdvcmtfaWRSK6ccCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_tube_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfdHViZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaB9+lY3hAkNNAwoAbmV0d29ya19pZF0hKKYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_brain_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRVBAAACAQAbmFtZSAAbWluZWNyYWZ0OmRlYWRfYnJhaW5fY29yYWxfYmxvY2sECQBuYW1lX2hhc2iHyDn52AO8uwMKAG5ldHdvcmtfaWQw7yCaCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dead_bubble_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRWBAAACAQAbmFtZSEAbWluZWNyYWZ0OmRlYWRfYnViYmxlX2NvcmFsX2Jsb2NrBAkAbmFtZV9oYXNotwkk/ITrsjADCgBuZXR3b3JrX2lk56mXUgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dead_fire_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRXBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfZmlyZV9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaG0qHxbIrBEyAwoAbmV0d29ya19pZFvnH88KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:dead_horn_coral_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRYBAAACAQAbmFtZR8AbWluZWNyYWZ0OmRlYWRfaG9ybl9jb3JhbF9ibG9jawQJAG5hbWVfaGFzaL7D8bu4Fm+0AwoAbmV0d29ya19pZEALRLoKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sculk", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAgAACAQAbmFtZQ8AbWluZWNyYWZ0OnNjdWxrBAkAbmFtZV9oYXNo2Lq7T5yQF8kDCgBuZXR3b3JrX2lkyqUPPgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sculk_vein", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAgAACAQAbmFtZRQAbWluZWNyYWZ0OnNjdWxrX3ZlaW4ECQBuYW1lX2hhc2gJUdhVooV4zwMKAG5ldHdvcmtfaWSUfn1XCgYAc3RhdGVzAxkAbXVsdGlfZmFjZV9kaXJlY3Rpb25fYml0cwAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:sculk_catalyst", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAgAACAQAbmFtZRgAbWluZWNyYWZ0OnNjdWxrX2NhdGFseXN0BAkAbmFtZV9oYXNo+gCpbrCHST4DCgBuZXR3b3JrX2lkMJ2n/woGAHN0YXRlcwEFAGJsb29tAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sculk_shrieker", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTMAgAACAQAbmFtZRgAbWluZWNyYWZ0OnNjdWxrX3Nocmlla2VyBAkAbmFtZV9oYXNo5OXtyObniQ4DCgBuZXR3b3JrX2lkxapoNAoGAHN0YXRlcwEGAGFjdGl2ZQABCgBjYW5fc3VtbW9uAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sculk_sensor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAgAACAQAbmFtZRYAbWluZWNyYWZ0OnNjdWxrX3NlbnNvcgQJAG5hbWVfaGFzaCkmHreeTgNnAwoAbmV0d29ya19pZLj2WPcKBgBzdGF0ZXMDEgBzY3Vsa19zZW5zb3JfcGhhc2UAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:calibrated_sculk_sensor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRDAwAACAQAbmFtZSEAbWluZWNyYWZ0OmNhbGlicmF0ZWRfc2N1bGtfc2Vuc29yBAkAbmFtZV9oYXNoffAcXXN/iJUDCgBuZXR3b3JrX2lkwOx3QQoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAxIAc2N1bGtfc2Vuc29yX3BoYXNlAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:reinforced_deepslate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTRAgAACAQAbmFtZR4AbWluZWNyYWZ0OnJlaW5mb3JjZWRfZGVlcHNsYXRlBAkAbmFtZV9oYXNoldDmj91EapQDCgBuZXR3b3JrX2lkHIt+aQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:leather_helmet" + }, + { + "id": "minecraft:chainmail_helmet" + }, + { + "id": "minecraft:iron_helmet" + }, + { + "id": "minecraft:golden_helmet" + }, + { + "id": "minecraft:diamond_helmet" + }, + { + "id": "minecraft:netherite_helmet" + }, + { + "id": "minecraft:leather_chestplate" + }, + { + "id": "minecraft:chainmail_chestplate" + }, + { + "id": "minecraft:iron_chestplate" + }, + { + "id": "minecraft:golden_chestplate" + }, + { + "id": "minecraft:diamond_chestplate" + }, + { + "id": "minecraft:netherite_chestplate" + }, + { + "id": "minecraft:leather_leggings" + }, + { + "id": "minecraft:chainmail_leggings" + }, + { + "id": "minecraft:iron_leggings" + }, + { + "id": "minecraft:golden_leggings" + }, + { + "id": "minecraft:diamond_leggings" + }, + { + "id": "minecraft:netherite_leggings" + }, + { + "id": "minecraft:leather_boots" + }, + { + "id": "minecraft:chainmail_boots" + }, + { + "id": "minecraft:iron_boots" + }, + { + "id": "minecraft:golden_boots" + }, + { + "id": "minecraft:diamond_boots" + }, + { + "id": "minecraft:netherite_boots" + }, + { + "id": "minecraft:wooden_sword" + }, + { + "id": "minecraft:stone_sword" + }, + { + "id": "minecraft:iron_sword" + }, + { + "id": "minecraft:golden_sword" + }, + { + "id": "minecraft:diamond_sword" + }, + { + "id": "minecraft:netherite_sword" + }, + { + "id": "minecraft:wooden_axe" + }, + { + "id": "minecraft:stone_axe" + }, + { + "id": "minecraft:iron_axe" + }, + { + "id": "minecraft:golden_axe" + }, + { + "id": "minecraft:diamond_axe" + }, + { + "id": "minecraft:netherite_axe" + }, + { + "id": "minecraft:wooden_pickaxe" + }, + { + "id": "minecraft:stone_pickaxe" + }, + { + "id": "minecraft:iron_pickaxe" + }, + { + "id": "minecraft:golden_pickaxe" + }, + { + "id": "minecraft:diamond_pickaxe" + }, + { + "id": "minecraft:netherite_pickaxe" + }, + { + "id": "minecraft:wooden_shovel" + }, + { + "id": "minecraft:stone_shovel" + }, + { + "id": "minecraft:iron_shovel" + }, + { + "id": "minecraft:golden_shovel" + }, + { + "id": "minecraft:diamond_shovel" + }, + { + "id": "minecraft:netherite_shovel" + }, + { + "id": "minecraft:wooden_hoe" + }, + { + "id": "minecraft:stone_hoe" + }, + { + "id": "minecraft:iron_hoe" + }, + { + "id": "minecraft:golden_hoe" + }, + { + "id": "minecraft:diamond_hoe" + }, + { + "id": "minecraft:netherite_hoe" + }, + { + "id": "minecraft:bow" + }, + { + "id": "minecraft:crossbow" + }, + { + "id": "minecraft:mace" + }, + { + "id": "minecraft:arrow" + }, + { + "id": "minecraft:arrow", + "damage": 6 + }, + { + "id": "minecraft:arrow", + "damage": 7 + }, + { + "id": "minecraft:arrow", + "damage": 8 + }, + { + "id": "minecraft:arrow", + "damage": 9 + }, + { + "id": "minecraft:arrow", + "damage": 10 + }, + { + "id": "minecraft:arrow", + "damage": 11 + }, + { + "id": "minecraft:arrow", + "damage": 12 + }, + { + "id": "minecraft:arrow", + "damage": 13 + }, + { + "id": "minecraft:arrow", + "damage": 14 + }, + { + "id": "minecraft:arrow", + "damage": 15 + }, + { + "id": "minecraft:arrow", + "damage": 16 + }, + { + "id": "minecraft:arrow", + "damage": 17 + }, + { + "id": "minecraft:arrow", + "damage": 18 + }, + { + "id": "minecraft:arrow", + "damage": 19 + }, + { + "id": "minecraft:arrow", + "damage": 20 + }, + { + "id": "minecraft:arrow", + "damage": 21 + }, + { + "id": "minecraft:arrow", + "damage": 22 + }, + { + "id": "minecraft:arrow", + "damage": 23 + }, + { + "id": "minecraft:arrow", + "damage": 24 + }, + { + "id": "minecraft:arrow", + "damage": 25 + }, + { + "id": "minecraft:arrow", + "damage": 26 + }, + { + "id": "minecraft:arrow", + "damage": 27 + }, + { + "id": "minecraft:arrow", + "damage": 28 + }, + { + "id": "minecraft:arrow", + "damage": 29 + }, + { + "id": "minecraft:arrow", + "damage": 30 + }, + { + "id": "minecraft:arrow", + "damage": 31 + }, + { + "id": "minecraft:arrow", + "damage": 32 + }, + { + "id": "minecraft:arrow", + "damage": 33 + }, + { + "id": "minecraft:arrow", + "damage": 34 + }, + { + "id": "minecraft:arrow", + "damage": 35 + }, + { + "id": "minecraft:arrow", + "damage": 36 + }, + { + "id": "minecraft:arrow", + "damage": 37 + }, + { + "id": "minecraft:arrow", + "damage": 38 + }, + { + "id": "minecraft:arrow", + "damage": 39 + }, + { + "id": "minecraft:arrow", + "damage": 40 + }, + { + "id": "minecraft:arrow", + "damage": 41 + }, + { + "id": "minecraft:arrow", + "damage": 42 + }, + { + "id": "minecraft:arrow", + "damage": 43 + }, + { + "id": "minecraft:arrow", + "damage": 44 + }, + { + "id": "minecraft:arrow", + "damage": 45 + }, + { + "id": "minecraft:arrow", + "damage": 46 + }, + { + "id": "minecraft:arrow", + "damage": 47 + }, + { + "id": "minecraft:ominous_bottle" + }, + { + "id": "minecraft:ominous_bottle", + "damage": 1 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 2 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 3 + }, + { + "id": "minecraft:ominous_bottle", + "damage": 4 + }, + { + "id": "minecraft:shield" + }, + { + "id": "minecraft:cooked_chicken" + }, + { + "id": "minecraft:cooked_porkchop" + }, + { + "id": "minecraft:cooked_beef" + }, + { + "id": "minecraft:cooked_mutton" + }, + { + "id": "minecraft:cooked_rabbit" + }, + { + "id": "minecraft:cooked_cod" + }, + { + "id": "minecraft:cooked_salmon" + }, + { + "id": "minecraft:bread" + }, + { + "id": "minecraft:mushroom_stew" + }, + { + "id": "minecraft:beetroot_soup" + }, + { + "id": "minecraft:rabbit_stew" + }, + { + "id": "minecraft:baked_potato" + }, + { + "id": "minecraft:cookie" + }, + { + "id": "minecraft:pumpkin_pie" + }, + { + "id": "minecraft:cake" + }, + { + "id": "minecraft:dried_kelp" + }, + { + "id": "minecraft:fishing_rod" + }, + { + "id": "minecraft:carrot_on_a_stick" + }, + { + "id": "minecraft:warped_fungus_on_a_stick" + }, + { + "id": "minecraft:snowball" + }, + { + "id": "minecraft:wind_charge" + }, + { + "id": "minecraft:shears" + }, + { + "id": "minecraft:flint_and_steel" + }, + { + "id": "minecraft:lead" + }, + { + "id": "minecraft:clock" + }, + { + "id": "minecraft:compass" + }, + { + "id": "minecraft:recovery_compass" + }, + { + "id": "minecraft:goat_horn" + }, + { + "id": "minecraft:goat_horn", + "damage": 1 + }, + { + "id": "minecraft:goat_horn", + "damage": 2 + }, + { + "id": "minecraft:goat_horn", + "damage": 3 + }, + { + "id": "minecraft:goat_horn", + "damage": 4 + }, + { + "id": "minecraft:goat_horn", + "damage": 5 + }, + { + "id": "minecraft:goat_horn", + "damage": 6 + }, + { + "id": "minecraft:goat_horn", + "damage": 7 + }, + { + "id": "minecraft:empty_map" + }, + { + "id": "minecraft:empty_map", + "damage": 2 + }, + { + "id": "minecraft:saddle" + }, + { + "id": "minecraft:leather_horse_armor" + }, + { + "id": "minecraft:iron_horse_armor" + }, + { + "id": "minecraft:golden_horse_armor" + }, + { + "id": "minecraft:diamond_horse_armor" + }, + { + "id": "minecraft:wolf_armor" + }, + { + "id": "minecraft:trident" + }, + { + "id": "minecraft:turtle_helmet" + }, + { + "id": "minecraft:elytra" + }, + { + "id": "minecraft:totem_of_undying" + }, + { + "id": "minecraft:glass_bottle" + }, + { + "id": "minecraft:experience_bottle" + }, + { + "id": "minecraft:potion" + }, + { + "id": "minecraft:potion", + "damage": 1 + }, + { + "id": "minecraft:potion", + "damage": 2 + }, + { + "id": "minecraft:potion", + "damage": 3 + }, + { + "id": "minecraft:potion", + "damage": 4 + }, + { + "id": "minecraft:potion", + "damage": 5 + }, + { + "id": "minecraft:potion", + "damage": 6 + }, + { + "id": "minecraft:potion", + "damage": 7 + }, + { + "id": "minecraft:potion", + "damage": 8 + }, + { + "id": "minecraft:potion", + "damage": 9 + }, + { + "id": "minecraft:potion", + "damage": 10 + }, + { + "id": "minecraft:potion", + "damage": 11 + }, + { + "id": "minecraft:potion", + "damage": 12 + }, + { + "id": "minecraft:potion", + "damage": 13 + }, + { + "id": "minecraft:potion", + "damage": 14 + }, + { + "id": "minecraft:potion", + "damage": 15 + }, + { + "id": "minecraft:potion", + "damage": 16 + }, + { + "id": "minecraft:potion", + "damage": 17 + }, + { + "id": "minecraft:potion", + "damage": 18 + }, + { + "id": "minecraft:potion", + "damage": 19 + }, + { + "id": "minecraft:potion", + "damage": 20 + }, + { + "id": "minecraft:potion", + "damage": 21 + }, + { + "id": "minecraft:potion", + "damage": 22 + }, + { + "id": "minecraft:potion", + "damage": 23 + }, + { + "id": "minecraft:potion", + "damage": 24 + }, + { + "id": "minecraft:potion", + "damage": 25 + }, + { + "id": "minecraft:potion", + "damage": 26 + }, + { + "id": "minecraft:potion", + "damage": 27 + }, + { + "id": "minecraft:potion", + "damage": 28 + }, + { + "id": "minecraft:potion", + "damage": 29 + }, + { + "id": "minecraft:potion", + "damage": 30 + }, + { + "id": "minecraft:potion", + "damage": 31 + }, + { + "id": "minecraft:potion", + "damage": 32 + }, + { + "id": "minecraft:potion", + "damage": 33 + }, + { + "id": "minecraft:potion", + "damage": 34 + }, + { + "id": "minecraft:potion", + "damage": 35 + }, + { + "id": "minecraft:potion", + "damage": 36 + }, + { + "id": "minecraft:potion", + "damage": 37 + }, + { + "id": "minecraft:potion", + "damage": 38 + }, + { + "id": "minecraft:potion", + "damage": 39 + }, + { + "id": "minecraft:potion", + "damage": 40 + }, + { + "id": "minecraft:potion", + "damage": 41 + }, + { + "id": "minecraft:potion", + "damage": 42 + }, + { + "id": "minecraft:potion", + "damage": 43 + }, + { + "id": "minecraft:potion", + "damage": 44 + }, + { + "id": "minecraft:potion", + "damage": 45 + }, + { + "id": "minecraft:potion", + "damage": 46 + }, + { + "id": "minecraft:splash_potion" + }, + { + "id": "minecraft:splash_potion", + "damage": 1 + }, + { + "id": "minecraft:splash_potion", + "damage": 2 + }, + { + "id": "minecraft:splash_potion", + "damage": 3 + }, + { + "id": "minecraft:splash_potion", + "damage": 4 + }, + { + "id": "minecraft:splash_potion", + "damage": 5 + }, + { + "id": "minecraft:splash_potion", + "damage": 6 + }, + { + "id": "minecraft:splash_potion", + "damage": 7 + }, + { + "id": "minecraft:splash_potion", + "damage": 8 + }, + { + "id": "minecraft:splash_potion", + "damage": 9 + }, + { + "id": "minecraft:splash_potion", + "damage": 10 + }, + { + "id": "minecraft:splash_potion", + "damage": 11 + }, + { + "id": "minecraft:splash_potion", + "damage": 12 + }, + { + "id": "minecraft:splash_potion", + "damage": 13 + }, + { + "id": "minecraft:splash_potion", + "damage": 14 + }, + { + "id": "minecraft:splash_potion", + "damage": 15 + }, + { + "id": "minecraft:splash_potion", + "damage": 16 + }, + { + "id": "minecraft:splash_potion", + "damage": 17 + }, + { + "id": "minecraft:splash_potion", + "damage": 18 + }, + { + "id": "minecraft:splash_potion", + "damage": 19 + }, + { + "id": "minecraft:splash_potion", + "damage": 20 + }, + { + "id": "minecraft:splash_potion", + "damage": 21 + }, + { + "id": "minecraft:splash_potion", + "damage": 22 + }, + { + "id": "minecraft:splash_potion", + "damage": 23 + }, + { + "id": "minecraft:splash_potion", + "damage": 24 + }, + { + "id": "minecraft:splash_potion", + "damage": 25 + }, + { + "id": "minecraft:splash_potion", + "damage": 26 + }, + { + "id": "minecraft:splash_potion", + "damage": 27 + }, + { + "id": "minecraft:splash_potion", + "damage": 28 + }, + { + "id": "minecraft:splash_potion", + "damage": 29 + }, + { + "id": "minecraft:splash_potion", + "damage": 30 + }, + { + "id": "minecraft:splash_potion", + "damage": 31 + }, + { + "id": "minecraft:splash_potion", + "damage": 32 + }, + { + "id": "minecraft:splash_potion", + "damage": 33 + }, + { + "id": "minecraft:splash_potion", + "damage": 34 + }, + { + "id": "minecraft:splash_potion", + "damage": 35 + }, + { + "id": "minecraft:splash_potion", + "damage": 36 + }, + { + "id": "minecraft:splash_potion", + "damage": 37 + }, + { + "id": "minecraft:splash_potion", + "damage": 38 + }, + { + "id": "minecraft:splash_potion", + "damage": 39 + }, + { + "id": "minecraft:splash_potion", + "damage": 40 + }, + { + "id": "minecraft:splash_potion", + "damage": 41 + }, + { + "id": "minecraft:splash_potion", + "damage": 42 + }, + { + "id": "minecraft:splash_potion", + "damage": 43 + }, + { + "id": "minecraft:splash_potion", + "damage": 44 + }, + { + "id": "minecraft:splash_potion", + "damage": 45 + }, + { + "id": "minecraft:splash_potion", + "damage": 46 + }, + { + "id": "minecraft:lingering_potion" + }, + { + "id": "minecraft:lingering_potion", + "damage": 1 + }, + { + "id": "minecraft:lingering_potion", + "damage": 2 + }, + { + "id": "minecraft:lingering_potion", + "damage": 3 + }, + { + "id": "minecraft:lingering_potion", + "damage": 4 + }, + { + "id": "minecraft:lingering_potion", + "damage": 5 + }, + { + "id": "minecraft:lingering_potion", + "damage": 6 + }, + { + "id": "minecraft:lingering_potion", + "damage": 7 + }, + { + "id": "minecraft:lingering_potion", + "damage": 8 + }, + { + "id": "minecraft:lingering_potion", + "damage": 9 + }, + { + "id": "minecraft:lingering_potion", + "damage": 10 + }, + { + "id": "minecraft:lingering_potion", + "damage": 11 + }, + { + "id": "minecraft:lingering_potion", + "damage": 12 + }, + { + "id": "minecraft:lingering_potion", + "damage": 13 + }, + { + "id": "minecraft:lingering_potion", + "damage": 14 + }, + { + "id": "minecraft:lingering_potion", + "damage": 15 + }, + { + "id": "minecraft:lingering_potion", + "damage": 16 + }, + { + "id": "minecraft:lingering_potion", + "damage": 17 + }, + { + "id": "minecraft:lingering_potion", + "damage": 18 + }, + { + "id": "minecraft:lingering_potion", + "damage": 19 + }, + { + "id": "minecraft:lingering_potion", + "damage": 20 + }, + { + "id": "minecraft:lingering_potion", + "damage": 21 + }, + { + "id": "minecraft:lingering_potion", + "damage": 22 + }, + { + "id": "minecraft:lingering_potion", + "damage": 23 + }, + { + "id": "minecraft:lingering_potion", + "damage": 24 + }, + { + "id": "minecraft:lingering_potion", + "damage": 25 + }, + { + "id": "minecraft:lingering_potion", + "damage": 26 + }, + { + "id": "minecraft:lingering_potion", + "damage": 27 + }, + { + "id": "minecraft:lingering_potion", + "damage": 28 + }, + { + "id": "minecraft:lingering_potion", + "damage": 29 + }, + { + "id": "minecraft:lingering_potion", + "damage": 30 + }, + { + "id": "minecraft:lingering_potion", + "damage": 31 + }, + { + "id": "minecraft:lingering_potion", + "damage": 32 + }, + { + "id": "minecraft:lingering_potion", + "damage": 33 + }, + { + "id": "minecraft:lingering_potion", + "damage": 34 + }, + { + "id": "minecraft:lingering_potion", + "damage": 35 + }, + { + "id": "minecraft:lingering_potion", + "damage": 36 + }, + { + "id": "minecraft:lingering_potion", + "damage": 37 + }, + { + "id": "minecraft:lingering_potion", + "damage": 38 + }, + { + "id": "minecraft:lingering_potion", + "damage": 39 + }, + { + "id": "minecraft:lingering_potion", + "damage": 40 + }, + { + "id": "minecraft:lingering_potion", + "damage": 41 + }, + { + "id": "minecraft:lingering_potion", + "damage": 42 + }, + { + "id": "minecraft:lingering_potion", + "damage": 43 + }, + { + "id": "minecraft:lingering_potion", + "damage": 44 + }, + { + "id": "minecraft:lingering_potion", + "damage": 45 + }, + { + "id": "minecraft:lingering_potion", + "damage": 46 + }, + { + "id": "minecraft:spyglass" + }, + { + "id": "minecraft:brush" + }, + { + "id": "minecraft:stick" + }, + { + "id": "minecraft:bed" + }, + { + "id": "minecraft:bed", + "damage": 8 + }, + { + "id": "minecraft:bed", + "damage": 7 + }, + { + "id": "minecraft:bed", + "damage": 15 + }, + { + "id": "minecraft:bed", + "damage": 12 + }, + { + "id": "minecraft:bed", + "damage": 14 + }, + { + "id": "minecraft:bed", + "damage": 1 + }, + { + "id": "minecraft:bed", + "damage": 4 + }, + { + "id": "minecraft:bed", + "damage": 5 + }, + { + "id": "minecraft:bed", + "damage": 13 + }, + { + "id": "minecraft:bed", + "damage": 9 + }, + { + "id": "minecraft:bed", + "damage": 3 + }, + { + "id": "minecraft:bed", + "damage": 11 + }, + { + "id": "minecraft:bed", + "damage": 10 + }, + { + "id": "minecraft:bed", + "damage": 2 + }, + { + "id": "minecraft:bed", + "damage": 6 + }, + { + "id": "minecraft:torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQyAAAACAQAbmFtZQ8AbWluZWNyYWZ0OnRvcmNoBAkAbmFtZV9oYXNoagn7rmDBzisDCgBuZXR3b3JrX2lk+BwwuQoGAHN0YXRlcwgWAHRvcmNoX2ZhY2luZ19kaXJlY3Rpb24HAHVua25vd24AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:soul_torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQLAgAACAQAbmFtZRQAbWluZWNyYWZ0OnNvdWxfdG9yY2gECQBuYW1lX2hhc2huixOT04BRdQMKAG5ldHdvcmtfaWShbFILCgYAc3RhdGVzCBYAdG9yY2hfZmFjaW5nX2RpcmVjdGlvbgcAdW5rbm93bgADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sea_pickle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAQAACAQAbmFtZRQAbWluZWNyYWZ0OnNlYV9waWNrbGUECQBuYW1lX2hhc2iONEfZJB+glgMKAG5ldHdvcmtfaWSINWQyCgYAc3RhdGVzAw0AY2x1c3Rlcl9jb3VudAAAAAABCABkZWFkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTPAQAACAQAbmFtZREAbWluZWNyYWZ0OmxhbnRlcm4ECQBuYW1lX2hhc2hMw44VI2HWygMKAG5ldHdvcmtfaWRkjQvzCgYAc3RhdGVzAQcAaGFuZ2luZwAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:soul_lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQMAgAACAQAbmFtZRYAbWluZWNyYWZ0OnNvdWxfbGFudGVybgQJAG5hbWVfaGFzaGjIpjxk9z+RAwoAbmV0d29ya19pZGfoP8cKBgBzdGF0ZXMBBwBoYW5naW5nAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSbAgAACAQAbmFtZRAAbWluZWNyYWZ0OmNhbmRsZQQJAG5hbWVfaGFzaHPd+MsNdWTfAwoAbmV0d29ya19pZHsBMA0KBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAgAACAQAbmFtZRYAbWluZWNyYWZ0OndoaXRlX2NhbmRsZQQJAG5hbWVfaGFzaN1EG5Q1mHiEAwoAbmV0d29ya19pZKN1mmgKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:orange_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSdAgAACAQAbmFtZRcAbWluZWNyYWZ0Om9yYW5nZV9jYW5kbGUECQBuYW1lX2hhc2jySEVWHgUIHQMKAG5ldHdvcmtfaWSfVz82CgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:magenta_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSeAgAACAQAbmFtZRgAbWluZWNyYWZ0Om1hZ2VudGFfY2FuZGxlBAkAbmFtZV9oYXNoG0u6YIOoBSEDCgBuZXR3b3JrX2lk9xGNkQoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_blue_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSfAgAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfY2FuZGxlBAkAbmFtZV9oYXNocXGeK0zgrG0DCgBuZXR3b3JrX2lk2m1y8goGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:yellow_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSgAgAACAQAbmFtZRcAbWluZWNyYWZ0OnllbGxvd19jYW5kbGUECQBuYW1lX2hhc2i00dtusU3CqQMKAG5ldHdvcmtfaWR9LTmpCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:lime_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWShAgAACAQAbmFtZRUAbWluZWNyYWZ0OmxpbWVfY2FuZGxlBAkAbmFtZV9oYXNokcmrw5xvz7ADCgBuZXR3b3JrX2lkIAUu6QoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:pink_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSiAgAACAQAbmFtZRUAbWluZWNyYWZ0OnBpbmtfY2FuZGxlBAkAbmFtZV9oYXNoQJdEY4sZ0dwDCgBuZXR3b3JrX2lk23Rn5AoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:gray_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSjAgAACAQAbmFtZRUAbWluZWNyYWZ0OmdyYXlfY2FuZGxlBAkAbmFtZV9oYXNoS5poSo9wBDEDCgBuZXR3b3JrX2lk3trRCAoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_gray_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSkAgAACAQAbmFtZRsAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfY2FuZGxlBAkAbmFtZV9oYXNo9ruTZLBNMasDCgBuZXR3b3JrX2lkb6DOegoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cyan_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSlAgAACAQAbmFtZRUAbWluZWNyYWZ0OmN5YW5fY2FuZGxlBAkAbmFtZV9oYXNoc/M8PNVcjOwDCgBuZXR3b3JrX2lkZoIQOQoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:purple_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSmAgAACAQAbmFtZRcAbWluZWNyYWZ0OnB1cnBsZV9jYW5kbGUECQBuYW1lX2hhc2jaI3xUW0/myQMKAG5ldHdvcmtfaWSnLI2BCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blue_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSnAgAACAQAbmFtZRUAbWluZWNyYWZ0OmJsdWVfY2FuZGxlBAkAbmFtZV9oYXNoAASSPW6TgQADCgBuZXR3b3JrX2lkrxrjQAoGAHN0YXRlcwMHAGNhbmRsZXMAAAAAAQMAbGl0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:brown_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSoAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJyb3duX2NhbmRsZQQJAG5hbWVfaGFzaDia0l6s1+WYAwoAbmV0d29ya19pZKSkBXYKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:green_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAgAACAQAbmFtZRYAbWluZWNyYWZ0OmdyZWVuX2NhbmRsZQQJAG5hbWVfaGFzaLeFPO1l+fIoAwoAbmV0d29ya19pZBkznDsKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:red_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSqAgAACAQAbmFtZRQAbWluZWNyYWZ0OnJlZF9jYW5kbGUECQBuYW1lX2hhc2jjAQpGf59ZdwMKAG5ldHdvcmtfaWRbb88GCgYAc3RhdGVzAwcAY2FuZGxlcwAAAAABAwBsaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:black_candle", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSrAgAACAQAbmFtZRYAbWluZWNyYWZ0OmJsYWNrX2NhbmRsZQQJAG5hbWVfaGFzaB+wRDpOqREKAwoAbmV0d29ya19pZNnOnuEKBgBzdGF0ZXMDBwBjYW5kbGVzAAAAAAEDAGxpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crafting_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ6AAAACAQAbmFtZRgAbWluZWNyYWZ0OmNyYWZ0aW5nX3RhYmxlBAkAbmFtZV9oYXNoe76VAmjvbpYDCgBuZXR3b3JrX2lkwCxwaAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cartography_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTHAQAACAQAbmFtZRsAbWluZWNyYWZ0OmNhcnRvZ3JhcGh5X3RhYmxlBAkAbmFtZV9oYXNomaWiiD/znP8DCgBuZXR3b3JrX2lkI6FzMwoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:fletching_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTIAQAACAQAbmFtZRkAbWluZWNyYWZ0OmZsZXRjaGluZ190YWJsZQQJAG5hbWVfaGFzaPFibh8unKyUAwoAbmV0d29ya19pZJ2mW0oKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:smithing_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTJAQAACAQAbmFtZRgAbWluZWNyYWZ0OnNtaXRoaW5nX3RhYmxlBAkAbmFtZV9oYXNo4tFES2xOXEYDCgBuZXR3b3JrX2lkXWMBzQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:beehive", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAQAACAQAbmFtZREAbWluZWNyYWZ0OmJlZWhpdmUECQBuYW1lX2hhc2hCcqn12UbNpwMKAG5ldHdvcmtfaWR/idcaCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAMLAGhvbmV5X2xldmVsAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:suspicious_sand", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQQAwAACAQAbmFtZRkAbWluZWNyYWZ0OnN1c3BpY2lvdXNfc2FuZAQJAG5hbWVfaGFzaL67QsuvLP00AwoAbmV0d29ya19pZKnkaIAKBgBzdGF0ZXMDEABicnVzaGVkX3Byb2dyZXNzAAAAAAEHAGhhbmdpbmcBAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:suspicious_gravel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ8AwAACAQAbmFtZRsAbWluZWNyYWZ0OnN1c3BpY2lvdXNfZ3JhdmVsBAkAbmFtZV9oYXNoJSVbGNk7C3oDCgBuZXR3b3JrX2lkvIEJAAoGAHN0YXRlcwMQAGJydXNoZWRfcHJvZ3Jlc3MAAAAAAQcAaGFuZ2luZwEAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:campfire" + }, + { + "id": "minecraft:soul_campfire" + }, + { + "id": "minecraft:furnace", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ9AAAACAQAbmFtZREAbWluZWNyYWZ0OmZ1cm5hY2UECQBuYW1lX2hhc2ioOQrludYY8wMKAG5ldHdvcmtfaWRZxnDOCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:blast_furnace", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTDAQAACAQAbmFtZRcAbWluZWNyYWZ0OmJsYXN0X2Z1cm5hY2UECQBuYW1lX2hhc2ivDbnjkpGm5QMKAG5ldHdvcmtfaWTcEbV/CgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:smoker", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTFAQAACAQAbmFtZRAAbWluZWNyYWZ0OnNtb2tlcgQJAG5hbWVfaGFzaJd1rDMkRWomAwoAbmV0d29ya19pZGWswMwKBgBzdGF0ZXMIHABtaW5lY3JhZnQ6Y2FyZGluYWxfZGlyZWN0aW9uBQBzb3V0aAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:respawn_anchor", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQPAgAACAQAbmFtZRgAbWluZWNyYWZ0OnJlc3Bhd25fYW5jaG9yBAkAbmFtZV9oYXNoZOdcjW05qigDCgBuZXR3b3JrX2lkmhMcaQoGAHN0YXRlcwMVAHJlc3Bhd25fYW5jaG9yX2NoYXJnZQAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:brewing_stand" + }, + { + "id": "minecraft:anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSRAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmFudmlsBAkAbmFtZV9oYXNoNqB3fgcUCbwDCgBuZXR3b3JrX2lkqXzNjwoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAHNvdXRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:chipped_anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS+BAAACAQAbmFtZRcAbWluZWNyYWZ0OmNoaXBwZWRfYW52aWwECQBuYW1lX2hhc2ge+QY3vlS/eQMKAG5ldHdvcmtfaWRJ15iUCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:damaged_anvil", + "block_state_b64": "CgAAAwgAYmxvY2tfaWS/BAAACAQAbmFtZRcAbWluZWNyYWZ0OmRhbWFnZWRfYW52aWwECQBuYW1lX2hhc2imJ12Be2V8+AMKAG5ldHdvcmtfaWRh5SHkCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:grindstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTCAQAACAQAbmFtZRQAbWluZWNyYWZ0OmdyaW5kc3RvbmUECQBuYW1lX2hhc2id56zc0nk99wMKAG5ldHdvcmtfaWS4Es07CgYAc3RhdGVzCAoAYXR0YWNobWVudAgAc3RhbmRpbmcDCQBkaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:enchanting_table", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR0AAAACAQAbmFtZRoAbWluZWNyYWZ0OmVuY2hhbnRpbmdfdGFibGUECQBuYW1lX2hhc2jgIx24VLvMvwMKAG5ldHdvcmtfaWRliFFJCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bookshelf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQvAAAACAQAbmFtZRMAbWluZWNyYWZ0OmJvb2tzaGVsZgQJAG5hbWVfaGFzaDU04DrgJCS9AwoAbmV0d29ya19pZBcWwIwKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chiseled_bookshelf", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQNAwAACAQAbmFtZRwAbWluZWNyYWZ0OmNoaXNlbGVkX2Jvb2tzaGVsZgQJAG5hbWVfaGFzaNXDBnsIsywYAwoAbmV0d29ya19pZIprt5IKBgBzdGF0ZXMDDABib29rc19zdG9yZWQAAAAAAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lectern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTBAQAACAQAbmFtZREAbWluZWNyYWZ0OmxlY3Rlcm4ECQBuYW1lX2hhc2j5Z4Mmi/1QxAMKAG5ldHdvcmtfaWR4JfDHCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAc291dGgBCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cauldron" + }, + { + "id": "minecraft:composter", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTUAQAACAQAbmFtZRMAbWluZWNyYWZ0OmNvbXBvc3RlcgQJAG5hbWVfaGFzaPAADHptzeWJAwoAbmV0d29ya19pZHIL6i4KBgBzdGF0ZXMDFABjb21wb3N0ZXJfZmlsbF9sZXZlbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ2AAAACAQAbmFtZQ8AbWluZWNyYWZ0OmNoZXN0BAkAbmFtZV9oYXNog9ozMxlcA88DCgBuZXR3b3JrX2lkDkOFvAoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:trapped_chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSSAAAACAQAbmFtZRcAbWluZWNyYWZ0OnRyYXBwZWRfY2hlc3QECQBuYW1lX2hhc2g2qpF9stsEjgMKAG5ldHdvcmtfaWTjJWYxCgYAc3RhdGVzCBwAbWluZWNyYWZ0OmNhcmRpbmFsX2RpcmVjdGlvbgUAbm9ydGgAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:ender_chest", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSCAAAACAQAbmFtZRUAbWluZWNyYWZ0OmVuZGVyX2NoZXN0BAkAbmFtZV9oYXNohEZzOFdg0WUDCgBuZXR3b3JrX2lkx4jiSQoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:barrel", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTKAQAACAQAbmFtZRAAbWluZWNyYWZ0OmJhcnJlbAQJAG5hbWVfaGFzaHDkRPGymiRqAwoAbmV0d29ya19pZPnxzgsKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAAAAAAEIAG9wZW5fYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:undyed_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAAAACAQAbmFtZRwAbWluZWNyYWZ0OnVuZHllZF9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaOC9mypm/MlBAwoAbmV0d29ya19pZJ8rxp0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:white_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTaAAAACAQAbmFtZRsAbWluZWNyYWZ0OndoaXRlX3NodWxrZXJfYm94BAkAbmFtZV9oYXNosK79m1rPUBwDCgBuZXR3b3JrX2lkjrET6goGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:light_gray_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRrAwAACAQAbmFtZSAAbWluZWNyYWZ0OmxpZ2h0X2dyYXlfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2iBe5zq7PxHmgMKAG5ldHdvcmtfaWSCVJv0CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:gray_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRqAwAACAQAbmFtZRoAbWluZWNyYWZ0OmdyYXlfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2ga2s8ctjHUhgMKAG5ldHdvcmtfaWS3WMsWCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:black_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRyAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJsYWNrX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoPm03OZphrp8DCgBuZXR3b3JrX2lkXHztNAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:brown_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRvAwAACAQAbmFtZRsAbWluZWNyYWZ0OmJyb3duX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoT3DD6qAL9cADCgBuZXR3b3JrX2lkaXxpYQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:red_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRxAwAACAQAbmFtZRkAbWluZWNyYWZ0OnJlZF9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaMIlKSCzqSZoAwoAbmV0d29ya19pZNrf+icKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:orange_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRkAwAACAQAbmFtZRwAbWluZWNyYWZ0Om9yYW5nZV9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaG2MAXU67wGrAwoAbmV0d29ya19pZGoO05gKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:yellow_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRnAwAACAQAbmFtZRwAbWluZWNyYWZ0OnllbGxvd19zaHVsa2VyX2JveAQJAG5hbWVfaGFzaIsLwQHYjcIEAwoAbmV0d29ya19pZBCBSiYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lime_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRoAwAACAQAbmFtZRoAbWluZWNyYWZ0OmxpbWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2hUwBkg+faUGAMKAG5ldHdvcmtfaWRJeKqqCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:green_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRwAwAACAQAbmFtZRsAbWluZWNyYWZ0OmdyZWVuX3NodWxrZXJfYm94BAkAbmFtZV9oYXNoZgUeT3LupLUDCgBuZXR3b3JrX2lkzJiohQoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:cyan_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRsAwAACAQAbmFtZRoAbWluZWNyYWZ0OmN5YW5fc2h1bGtlcl9ib3gECQBuYW1lX2hhc2gSfbjteXg5yAMKAG5ldHdvcmtfaWTHeliECgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:light_blue_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRmAwAACAQAbmFtZSAAbWluZWNyYWZ0OmxpZ2h0X2JsdWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2h0VFCX0qsRxQMKAG5ldHdvcmtfaWQXD8U0CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blue_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRuAwAACAQAbmFtZRoAbWluZWNyYWZ0OmJsdWVfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2hn9gS0XIe6rAMKAG5ldHdvcmtfaWTO4PJaCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:purple_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRtAwAACAQAbmFtZRwAbWluZWNyYWZ0OnB1cnBsZV9zaHVsa2VyX2JveAQJAG5hbWVfaGFzaEV/lkNPxRDdAwoAbmV0d29ya19pZFK25GAKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:magenta_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRlAwAACAQAbmFtZR0AbWluZWNyYWZ0Om1hZ2VudGFfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2iqWM7IJHxcFgMKAG5ldHdvcmtfaWTyyudTCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:pink_shulker_box", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRpAwAACAQAbmFtZRoAbWluZWNyYWZ0OnBpbmtfc2h1bGtlcl9ib3gECQBuYW1lX2hhc2in1tkJ1GNcZgMKAG5ldHdvcmtfaWQOEGXjCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:armor_stand" + }, + { + "id": "minecraft:noteblock", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAAAACAQAbmFtZRMAbWluZWNyYWZ0Om5vdGVibG9jawQJAG5hbWVfaGFzaHPA8dBBH0UaAwoAbmV0d29ya19pZH1U5QkKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jukebox", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRUAAAACAQAbmFtZREAbWluZWNyYWZ0Omp1a2Vib3gECQBuYW1lX2hhc2ieAIPExf/ZfgMKAG5ldHdvcmtfaWSmR7JfCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:music_disc_13" + }, + { + "id": "minecraft:music_disc_cat" + }, + { + "id": "minecraft:music_disc_blocks" + }, + { + "id": "minecraft:music_disc_chirp" + }, + { + "id": "minecraft:music_disc_far" + }, + { + "id": "minecraft:music_disc_mall" + }, + { + "id": "minecraft:music_disc_mellohi" + }, + { + "id": "minecraft:music_disc_stal" + }, + { + "id": "minecraft:music_disc_strad" + }, + { + "id": "minecraft:music_disc_ward" + }, + { + "id": "minecraft:music_disc_11" + }, + { + "id": "minecraft:music_disc_wait" + }, + { + "id": "minecraft:music_disc_otherside" + }, + { + "id": "minecraft:music_disc_5" + }, + { + "id": "minecraft:music_disc_pigstep" + }, + { + "id": "minecraft:music_disc_relic" + }, + { + "id": "minecraft:music_disc_creator" + }, + { + "id": "minecraft:music_disc_creator_music_box" + }, + { + "id": "minecraft:music_disc_precipice" + }, + { + "id": "minecraft:disc_fragment_5" + }, + { + "id": "minecraft:glowstone_dust" + }, + { + "id": "minecraft:glowstone", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRZAAAACAQAbmFtZRMAbWluZWNyYWZ0Omdsb3dzdG9uZQQJAG5hbWVfaGFzaFYqXNkefIlPAwoAbmV0d29ya19pZGT7WYYKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:redstone_lamp", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR7AAAACAQAbmFtZRcAbWluZWNyYWZ0OnJlZHN0b25lX2xhbXAECQBuYW1lX2hhc2hJ9V80caPvEgMKAG5ldHdvcmtfaWRvNPwnCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:sea_lantern", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSpAAAACAQAbmFtZRUAbWluZWNyYWZ0OnNlYV9sYW50ZXJuBAkAbmFtZV9oYXNoLPsv1TX9M+QDCgBuZXR3b3JrX2lk1PPVyAoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:oak_sign" + }, + { + "id": "minecraft:spruce_sign" + }, + { + "id": "minecraft:birch_sign" + }, + { + "id": "minecraft:jungle_sign" + }, + { + "id": "minecraft:acacia_sign" + }, + { + "id": "minecraft:dark_oak_sign" + }, + { + "id": "minecraft:mangrove_sign" + }, + { + "id": "minecraft:cherry_sign" + }, + { + "id": "minecraft:bamboo_sign" + }, + { + "id": "minecraft:crimson_sign" + }, + { + "id": "minecraft:warped_sign" + }, + { + "id": "minecraft:oak_hanging_sign" + }, + { + "id": "minecraft:spruce_hanging_sign" + }, + { + "id": "minecraft:birch_hanging_sign" + }, + { + "id": "minecraft:jungle_hanging_sign" + }, + { + "id": "minecraft:acacia_hanging_sign" + }, + { + "id": "minecraft:dark_oak_hanging_sign" + }, + { + "id": "minecraft:mangrove_hanging_sign" + }, + { + "id": "minecraft:cherry_hanging_sign" + }, + { + "id": "minecraft:bamboo_hanging_sign" + }, + { + "id": "minecraft:crimson_hanging_sign" + }, + { + "id": "minecraft:warped_hanging_sign" + }, + { + "id": "minecraft:painting" + }, + { + "id": "minecraft:frame" + }, + { + "id": "minecraft:glow_frame" + }, + { + "id": "minecraft:honey_bottle" + }, + { + "id": "minecraft:flower_pot" + }, + { + "id": "minecraft:bowl" + }, + { + "id": "minecraft:bucket" + }, + { + "id": "minecraft:milk_bucket" + }, + { + "id": "minecraft:water_bucket" + }, + { + "id": "minecraft:lava_bucket" + }, + { + "id": "minecraft:cod_bucket" + }, + { + "id": "minecraft:salmon_bucket" + }, + { + "id": "minecraft:tropical_fish_bucket" + }, + { + "id": "minecraft:pufferfish_bucket" + }, + { + "id": "minecraft:powder_snow_bucket" + }, + { + "id": "minecraft:axolotl_bucket" + }, + { + "id": "minecraft:tadpole_bucket" + }, + { + "id": "minecraft:skull", + "damage": 3 + }, + { + "id": "minecraft:skull", + "damage": 2 + }, + { + "id": "minecraft:skull", + "damage": 4 + }, + { + "id": "minecraft:skull", + "damage": 5 + }, + { + "id": "minecraft:skull" + }, + { + "id": "minecraft:skull", + "damage": 1 + }, + { + "id": "minecraft:skull", + "damage": 6 + }, + { + "id": "minecraft:beacon", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSKAAAACAQAbmFtZRAAbWluZWNyYWZ0OmJlYWNvbgQJAG5hbWVfaGFzaACwhhfSkdkHAwoAbmV0d29ya19pZF8jfiEKBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:bell", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTNAQAACAQAbmFtZQ4AbWluZWNyYWZ0OmJlbGwECQBuYW1lX2hhc2iPqsgDXRcsxAMKAG5ldHdvcmtfaWT7zhOoCgYAc3RhdGVzCAoAYXR0YWNobWVudAgAc3RhbmRpbmcDCQBkaXJlY3Rpb24AAAAAAQoAdG9nZ2xlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:conduit", + "block_state_b64": "CgAAAwgAYmxvY2tfaWScAQAACAQAbmFtZREAbWluZWNyYWZ0OmNvbmR1aXQECQBuYW1lX2hhc2jqxKAxq2EaWQMKAG5ldHdvcmtfaWTWcBVnCgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stonecutter_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTEAQAACAQAbmFtZRsAbWluZWNyYWZ0OnN0b25lY3V0dGVyX2Jsb2NrBAkAbmFtZV9oYXNoQAXTbAM3MeYDCgBuZXR3b3JrX2lkWS4RjAoGAHN0YXRlcwgcAG1pbmVjcmFmdDpjYXJkaW5hbF9kaXJlY3Rpb24FAG5vcnRoAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:coal" + }, + { + "id": "minecraft:charcoal" + }, + { + "id": "minecraft:diamond" + }, + { + "id": "minecraft:iron_nugget" + }, + { + "id": "minecraft:raw_iron" + }, + { + "id": "minecraft:raw_gold" + }, + { + "id": "minecraft:raw_copper" + }, + { + "id": "minecraft:copper_ingot" + }, + { + "id": "minecraft:iron_ingot" + }, + { + "id": "minecraft:netherite_scrap" + }, + { + "id": "minecraft:netherite_ingot" + }, + { + "id": "minecraft:gold_nugget" + }, + { + "id": "minecraft:gold_ingot" + }, + { + "id": "minecraft:emerald" + }, + { + "id": "minecraft:quartz" + }, + { + "id": "minecraft:clay_ball" + }, + { + "id": "minecraft:brick" + }, + { + "id": "minecraft:netherbrick" + }, + { + "id": "minecraft:prismarine_shard" + }, + { + "id": "minecraft:amethyst_shard" + }, + { + "id": "minecraft:prismarine_crystals" + }, + { + "id": "minecraft:nautilus_shell" + }, + { + "id": "minecraft:heart_of_the_sea" + }, + { + "id": "minecraft:turtle_scute" + }, + { + "id": "minecraft:armadillo_scute" + }, + { + "id": "minecraft:phantom_membrane" + }, + { + "id": "minecraft:string" + }, + { + "id": "minecraft:feather" + }, + { + "id": "minecraft:flint" + }, + { + "id": "minecraft:gunpowder" + }, + { + "id": "minecraft:leather" + }, + { + "id": "minecraft:rabbit_hide" + }, + { + "id": "minecraft:rabbit_foot" + }, + { + "id": "minecraft:fire_charge" + }, + { + "id": "minecraft:blaze_rod" + }, + { + "id": "minecraft:breeze_rod" + }, + { + "id": "minecraft:heavy_core", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ7AgAACAQAbmFtZRQAbWluZWNyYWZ0OmhlYXZ5X2NvcmUECQBuYW1lX2hhc2hhz/uNCtrC2QMKAG5ldHdvcmtfaWRaFu+8CgYAc3RhdGVzAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:blaze_powder" + }, + { + "id": "minecraft:magma_cream" + }, + { + "id": "minecraft:fermented_spider_eye" + }, + { + "id": "minecraft:echo_shard" + }, + { + "id": "minecraft:dragon_breath" + }, + { + "id": "minecraft:shulker_shell" + }, + { + "id": "minecraft:ghast_tear" + }, + { + "id": "minecraft:slime_ball" + }, + { + "id": "minecraft:ender_pearl" + }, + { + "id": "minecraft:ender_eye" + }, + { + "id": "minecraft:nether_star" + }, + { + "id": "minecraft:end_rod", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTQAAAACAQAbmFtZREAbWluZWNyYWZ0OmVuZF9yb2QECQBuYW1lX2hhc2jx/q5cEA0hmQMKAG5ldHdvcmtfaWQ2eM8kCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lightning_rod", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ3AgAACAQAbmFtZRcAbWluZWNyYWZ0OmxpZ2h0bmluZ19yb2QECQBuYW1lX2hhc2ioXQF1xvfHNQMKAG5ldHdvcmtfaWRLuHyACgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:end_crystal" + }, + { + "id": "minecraft:paper" + }, + { + "id": "minecraft:book" + }, + { + "id": "minecraft:writable_book" + }, + { + "id": "minecraft:trial_key" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAUAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAEAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAIAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAMAAAA=" + }, + { + "id": "minecraft:enchanted_book", + "nbt_b64": "CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAQAAAA=" + }, + { + "id": "minecraft:oak_boat" + }, + { + "id": "minecraft:spruce_boat" + }, + { + "id": "minecraft:birch_boat" + }, + { + "id": "minecraft:jungle_boat" + }, + { + "id": "minecraft:acacia_boat" + }, + { + "id": "minecraft:dark_oak_boat" + }, + { + "id": "minecraft:mangrove_boat" + }, + { + "id": "minecraft:cherry_boat" + }, + { + "id": "minecraft:bamboo_raft" + }, + { + "id": "minecraft:oak_chest_boat" + }, + { + "id": "minecraft:spruce_chest_boat" + }, + { + "id": "minecraft:birch_chest_boat" + }, + { + "id": "minecraft:jungle_chest_boat" + }, + { + "id": "minecraft:acacia_chest_boat" + }, + { + "id": "minecraft:dark_oak_chest_boat" + }, + { + "id": "minecraft:mangrove_chest_boat" + }, + { + "id": "minecraft:cherry_chest_boat" + }, + { + "id": "minecraft:bamboo_chest_raft" + }, + { + "id": "minecraft:rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRCAAAACAQAbmFtZQ4AbWluZWNyYWZ0OnJhaWwECQBuYW1lX2hhc2hUzmhUXYJDUQMKAG5ldHdvcmtfaWR+Sp6YCgYAc3RhdGVzAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:golden_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQbAAAACAQAbmFtZRUAbWluZWNyYWZ0OmdvbGRlbl9yYWlsBAkAbmFtZV9oYXNoOoV5MaKipoUDCgBuZXR3b3JrX2lkfAcxLwoGAHN0YXRlcwENAHJhaWxfZGF0YV9iaXQAAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:detector_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQcAAAACAQAbmFtZRcAbWluZWNyYWZ0OmRldGVjdG9yX3JhaWwECQBuYW1lX2hhc2gVUk31qOysUQMKAG5ldHdvcmtfaWRVW/aICgYAc3RhdGVzAQ0AcmFpbF9kYXRhX2JpdAADDgByYWlsX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:activator_rail", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR+AAAACAQAbmFtZRgAbWluZWNyYWZ0OmFjdGl2YXRvcl9yYWlsBAkAbmFtZV9oYXNosIL91qriCRkDCgBuZXR3b3JrX2lkZfckmwoGAHN0YXRlcwENAHJhaWxfZGF0YV9iaXQAAw4AcmFpbF9kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:minecart" + }, + { + "id": "minecraft:chest_minecart" + }, + { + "id": "minecraft:hopper_minecart" + }, + { + "id": "minecraft:tnt_minecart" + }, + { + "id": "minecraft:redstone" + }, + { + "id": "minecraft:redstone_block", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAAAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZHN0b25lX2Jsb2NrBAkAbmFtZV9oYXNoRhULL0r8o0sDCgBuZXR3b3JrX2lklayOHgoGAHN0YXRlcwADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:redstone_torch", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRMAAAACAQAbmFtZRgAbWluZWNyYWZ0OnJlZHN0b25lX3RvcmNoBAkAbmFtZV9oYXNoizFRjpYMIDgDCgBuZXR3b3JrX2lkuHz7yAoGAHN0YXRlcwgWAHRvcmNoX2ZhY2luZ19kaXJlY3Rpb24HAHVua25vd24AAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:lever", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRFAAAACAQAbmFtZQ8AbWluZWNyYWZ0OmxldmVyBAkAbmFtZV9oYXNoGMJeLJsUMLYDCgBuZXR3b3JrX2lkEF/GuAoGAHN0YXRlcwgPAGxldmVyX2RpcmVjdGlvbg4AZG93bl9lYXN0X3dlc3QBCABvcGVuX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:wooden_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAAAACAQAbmFtZRcAbWluZWNyYWZ0Ondvb2Rlbl9idXR0b24ECQBuYW1lX2hhc2hR7PgSTQt0sQMKAG5ldHdvcmtfaWSU07kYCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSPAQAACAQAbmFtZRcAbWluZWNyYWZ0OnNwcnVjZV9idXR0b24ECQBuYW1lX2hhc2jBW9Z8aYE7YQMKAG5ldHdvcmtfaWTkUIGuCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSMAQAACAQAbmFtZRYAbWluZWNyYWZ0OmJpcmNoX2J1dHRvbgQJAG5hbWVfaGFzaJXYgGuSHbTwAwoAbmV0d29ya19pZGWp3yoKBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSOAQAACAQAbmFtZRcAbWluZWNyYWZ0Omp1bmdsZV9idXR0b24ECQBuYW1lX2hhc2iCgNANcJs+BQMKAG5ldHdvcmtfaWT9fImWCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSLAQAACAQAbmFtZRcAbWluZWNyYWZ0OmFjYWNpYV9idXR0b24ECQBuYW1lX2hhc2gVvmcT7LTO0wMKAG5ldHdvcmtfaWRQnxIJCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSNAQAACAQAbmFtZRkAbWluZWNyYWZ0OmRhcmtfb2FrX2J1dHRvbgQJAG5hbWVfaGFzaIV10ZGGrCIEAwoAbmV0d29ya19pZN5vAmIKBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTmAgAACAQAbmFtZRkAbWluZWNyYWZ0Om1hbmdyb3ZlX2J1dHRvbgQJAG5hbWVfaGFzaNzeYYKLgOzJAwoAbmV0d29ya19pZAFEGQ0KBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQRAwAACAQAbmFtZRcAbWluZWNyYWZ0OmNoZXJyeV9idXR0b24ECQBuYW1lX2hhc2j2/IHjeAbUcwMKAG5ldHdvcmtfaWRJ1irQCgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT+AgAACAQAbmFtZRcAbWluZWNyYWZ0OmJhbWJvb19idXR0b24ECQBuYW1lX2hhc2j7AddMi+6nsgMKAG5ldHdvcmtfaWSa9w4/CgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stone_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRNAAAACAQAbmFtZRYAbWluZWNyYWZ0OnN0b25lX2J1dHRvbgQJAG5hbWVfaGFzaM4ejMctmvohAwoAbmV0d29ya19pZMw+aC0KBgBzdGF0ZXMBEgBidXR0b25fcHJlc3NlZF9iaXQAAxAAZmFjaW5nX2RpcmVjdGlvbgAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:crimson_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQDAgAACAQAbmFtZRgAbWluZWNyYWZ0OmNyaW1zb25fYnV0dG9uBAkAbmFtZV9oYXNofnjYHaYIeWgDCgBuZXR3b3JrX2lk+n1vyQoGAHN0YXRlcwESAGJ1dHRvbl9wcmVzc2VkX2JpdAADEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQEAgAACAQAbmFtZRcAbWluZWNyYWZ0OndhcnBlZF9idXR0b24ECQBuYW1lX2hhc2jwkV2EU6Cn1QMKAG5ldHdvcmtfaWTnHnk1CgYAc3RhdGVzARIAYnV0dG9uX3ByZXNzZWRfYml0AAMQAGZhY2luZ19kaXJlY3Rpb24AAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:polished_blackstone_button", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQnAgAACAQAbmFtZSQAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfYnV0dG9uBAkAbmFtZV9oYXNojmxzQKS0S/EDCgBuZXR3b3JrX2lkDtQ95woGAHN0YXRlcwESAGJ1dHRvbl9wcmVzc2VkX2JpdAADEABmYWNpbmdfZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:tripwire_hook", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSDAAAACAQAbmFtZRcAbWluZWNyYWZ0OnRyaXB3aXJlX2hvb2sECQBuYW1lX2hhc2gQdp+oGZLNnAMKAG5ldHdvcmtfaWSy+1KJCgYAc3RhdGVzAQwAYXR0YWNoZWRfYml0AAMJAGRpcmVjdGlvbgAAAAABCwBwb3dlcmVkX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:wooden_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRIAAAACAQAbmFtZR8AbWluZWNyYWZ0Ondvb2Rlbl9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaGkGs5kCuA74AwoAbmV0d29ya19pZDRzPNwKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:spruce_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSZAQAACAQAbmFtZR8AbWluZWNyYWZ0OnNwcnVjZV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaNmwuq549fJKAwoAbmV0d29ya19pZLQMCw0KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:birch_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSWAQAACAQAbmFtZR4AbWluZWNyYWZ0OmJpcmNoX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNorQkT9kDdlTwDCgBuZXR3b3JrX2lkH0G97AoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:jungle_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSYAQAACAQAbmFtZR8AbWluZWNyYWZ0Omp1bmdsZV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaJ7DcteCkb8/AwoAbmV0d29ya19pZLdPBSAKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:acacia_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSVAQAACAQAbmFtZR8AbWluZWNyYWZ0OmFjYWNpYV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaC2frZtfoYqCAwoAbmV0d29ya19pZIDdI18KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:dark_oak_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAQAACAQAbmFtZSEAbWluZWNyYWZ0OmRhcmtfb2FrX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoHUCJsTy52pwDCgBuZXR3b3JrX2lkKpi8rAoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:mangrove_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTpAgAACAQAbmFtZSEAbWluZWNyYWZ0Om1hbmdyb3ZlX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoiDsTfJaX100DCgBuZXR3b3JrX2lkuwWDyQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:cherry_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQZAwAACAQAbmFtZR8AbWluZWNyYWZ0OmNoZXJyeV9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaALMqYEZDUQHAwoAbmV0d29ya19pZPNT+r0KBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:bamboo_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQBAwAACAQAbmFtZR8AbWluZWNyYWZ0OmJhbWJvb19wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaNvxJ7NIAaqlAwoAbmV0d29ya19pZIZ8XnYKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:crimson_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQFAgAACAQAbmFtZSAAbWluZWNyYWZ0OmNyaW1zb25fcHJlc3N1cmVfcGxhdGUECQBuYW1lX2hhc2hqBDVDAd31/gMKAG5ldHdvcmtfaWRmV18LCgYAc3RhdGVzAw8AcmVkc3RvbmVfc2lnbmFsAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:warped_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQGAgAACAQAbmFtZR8AbWluZWNyYWZ0OndhcnBlZF9wcmVzc3VyZV9wbGF0ZQQJAG5hbWVfaGFzaBxFoQksWtYUAwoAbmV0d29ya19pZJVRoIcKBgBzdGF0ZXMDDwByZWRzdG9uZV9zaWduYWwAAAAAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:stone_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWRGAAAACAQAbmFtZR4AbWluZWNyYWZ0OnN0b25lX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNounJuTBUTrU8DCgBuZXR3b3JrX2lkjDydwQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:light_weighted_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSTAAAACAQAbmFtZScAbWluZWNyYWZ0OmxpZ2h0X3dlaWdodGVkX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoOyOJkNxLtkEDCgBuZXR3b3JrX2lkrr2AjgoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:heavy_weighted_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSUAAAACAQAbmFtZScAbWluZWNyYWZ0OmhlYXZ5X3dlaWdodGVkX3ByZXNzdXJlX3BsYXRlBAkAbmFtZV9oYXNoltgDmDvTajUDCgBuZXR3b3JrX2lkFxVKuQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:polished_blackstone_pressure_plate", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAgAACAQAbmFtZSwAbWluZWNyYWZ0OnBvbGlzaGVkX2JsYWNrc3RvbmVfcHJlc3N1cmVfcGxhdGUECQBuYW1lX2hhc2h65Ci6/CeGqwMKAG5ldHdvcmtfaWTaSW5xCgYAc3RhdGVzAw8AcmVkc3RvbmVfc2lnbmFsAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:observer", + "block_state_b64": "CgAAAwgAYmxvY2tfaWT7AAAACAQAbmFtZRIAbWluZWNyYWZ0Om9ic2VydmVyBAkAbmFtZV9oYXNoYhlh1lpmHTgDCgBuZXR3b3JrX2lkQEh55goGAHN0YXRlcwgaAG1pbmVjcmFmdDpmYWNpbmdfZGlyZWN0aW9uBABkb3duAQsAcG93ZXJlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:daylight_detector", + "block_state_b64": "CgAAAwgAYmxvY2tfaWSXAAAACAQAbmFtZRsAbWluZWNyYWZ0OmRheWxpZ2h0X2RldGVjdG9yBAkAbmFtZV9oYXNoV0F0s7B7PVgDCgBuZXR3b3JrX2lkri5afQoGAHN0YXRlcwMPAHJlZHN0b25lX3NpZ25hbAAAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:repeater" + }, + { + "id": "minecraft:comparator" + }, + { + "id": "minecraft:hopper" + }, + { + "id": "minecraft:dropper", + "block_state_b64": "CgAAAwgAYmxvY2tfaWR9AAAACAQAbmFtZREAbWluZWNyYWZ0OmRyb3BwZXIECQBuYW1lX2hhc2joXP7XqU0l3QMKAG5ldHdvcmtfaWQfQN6zCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgMAAAABDQB0cmlnZ2VyZWRfYml0AAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:dispenser", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQXAAAACAQAbmFtZRMAbWluZWNyYWZ0OmRpc3BlbnNlcgQJAG5hbWVfaGFzaP1RR+zAbYP2AwoAbmV0d29ya19pZGAayD0KBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAwAAAAENAHRyaWdnZXJlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:crafter", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQ4AgAACAQAbmFtZREAbWluZWNyYWZ0OmNyYWZ0ZXIECQBuYW1lX2hhc2iLCT/rJmRN8QMKAG5ldHdvcmtfaWTPTbvrCgYAc3RhdGVzAQgAY3JhZnRpbmcACAsAb3JpZW50YXRpb24JAGRvd25fZWFzdAENAHRyaWdnZXJlZF9iaXQAAAMHAHZlcnNpb24HHhUBAA==" + }, + { + "id": "minecraft:piston", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQhAAAACAQAbmFtZRAAbWluZWNyYWZ0OnBpc3RvbgQJAG5hbWVfaGFzaDs3AFh1fL0uAwoAbmV0d29ya19pZLD/5XQKBgBzdGF0ZXMDEABmYWNpbmdfZGlyZWN0aW9uAQAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:sticky_piston", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQdAAAACAQAbmFtZRcAbWluZWNyYWZ0OnN0aWNreV9waXN0b24ECQBuYW1lX2hhc2hPFJFJSiJ0ZQMKAG5ldHdvcmtfaWT/MzCJCgYAc3RhdGVzAxAAZmFjaW5nX2RpcmVjdGlvbgEAAAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:tnt", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQuAAAACAQAbmFtZQ0AbWluZWNyYWZ0OnRudAQJAG5hbWVfaGFzaEYOHwCvJH29AwoAbmV0d29ya19pZAXzHyUKBgBzdGF0ZXMBCwBleHBsb2RlX2JpdAAAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:name_tag" + }, + { + "id": "minecraft:loom", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTLAQAACAQAbmFtZQ4AbWluZWNyYWZ0Omxvb20ECQBuYW1lX2hhc2i7DKjAXNq8TAMKAG5ldHdvcmtfaWR/49HXCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:banner", + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 8, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 7, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 15, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 12, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 14, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 1, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 4, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 5, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 13, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 9, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 3, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 11, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 10, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 2, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 6, + "nbt_b64": "CgAAAwQAVHlwZQAAAAAA" + }, + { + "id": "minecraft:banner", + "damage": 15, + "nbt_b64": "CgAAAwQAVHlwZQEAAAAA" + }, + { + "id": "minecraft:creeper_banner_pattern" + }, + { + "id": "minecraft:skull_banner_pattern" + }, + { + "id": "minecraft:flower_banner_pattern" + }, + { + "id": "minecraft:mojang_banner_pattern" + }, + { + "id": "minecraft:field_masoned_banner_pattern" + }, + { + "id": "minecraft:bordure_indented_banner_pattern" + }, + { + "id": "minecraft:piglin_banner_pattern" + }, + { + "id": "minecraft:globe_banner_pattern" + }, + { + "id": "minecraft:flow_banner_pattern" + }, + { + "id": "minecraft:guster_banner_pattern" + }, + { + "id": "minecraft:angler_pottery_sherd" + }, + { + "id": "minecraft:archer_pottery_sherd" + }, + { + "id": "minecraft:arms_up_pottery_sherd" + }, + { + "id": "minecraft:blade_pottery_sherd" + }, + { + "id": "minecraft:brewer_pottery_sherd" + }, + { + "id": "minecraft:burn_pottery_sherd" + }, + { + "id": "minecraft:danger_pottery_sherd" + }, + { + "id": "minecraft:explorer_pottery_sherd" + }, + { + "id": "minecraft:flow_pottery_sherd" + }, + { + "id": "minecraft:friend_pottery_sherd" + }, + { + "id": "minecraft:guster_pottery_sherd" + }, + { + "id": "minecraft:heart_pottery_sherd" + }, + { + "id": "minecraft:heartbreak_pottery_sherd" + }, + { + "id": "minecraft:howl_pottery_sherd" + }, + { + "id": "minecraft:miner_pottery_sherd" + }, + { + "id": "minecraft:mourner_pottery_sherd" + }, + { + "id": "minecraft:plenty_pottery_sherd" + }, + { + "id": "minecraft:prize_pottery_sherd" + }, + { + "id": "minecraft:scrape_pottery_sherd" + }, + { + "id": "minecraft:sheaf_pottery_sherd" + }, + { + "id": "minecraft:shelter_pottery_sherd" + }, + { + "id": "minecraft:skull_pottery_sherd" + }, + { + "id": "minecraft:snort_pottery_sherd" + }, + { + "id": "minecraft:netherite_upgrade_smithing_template" + }, + { + "id": "minecraft:sentry_armor_trim_smithing_template" + }, + { + "id": "minecraft:vex_armor_trim_smithing_template" + }, + { + "id": "minecraft:wild_armor_trim_smithing_template" + }, + { + "id": "minecraft:coast_armor_trim_smithing_template" + }, + { + "id": "minecraft:dune_armor_trim_smithing_template" + }, + { + "id": "minecraft:wayfinder_armor_trim_smithing_template" + }, + { + "id": "minecraft:shaper_armor_trim_smithing_template" + }, + { + "id": "minecraft:raiser_armor_trim_smithing_template" + }, + { + "id": "minecraft:host_armor_trim_smithing_template" + }, + { + "id": "minecraft:ward_armor_trim_smithing_template" + }, + { + "id": "minecraft:silence_armor_trim_smithing_template" + }, + { + "id": "minecraft:tide_armor_trim_smithing_template" + }, + { + "id": "minecraft:snout_armor_trim_smithing_template" + }, + { + "id": "minecraft:rib_armor_trim_smithing_template" + }, + { + "id": "minecraft:eye_armor_trim_smithing_template" + }, + { + "id": "minecraft:spire_armor_trim_smithing_template" + }, + { + "id": "minecraft:flow_armor_trim_smithing_template" + }, + { + "id": "minecraft:bolt_armor_trim_smithing_template" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_rocket", + "nbt_b64": "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id": "minecraft:firework_star", + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 8, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 7, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 15, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 12, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 14, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 1, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 4, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 5, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 13, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 9, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 3, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 11, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 10, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 2, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" + }, + { + "id": "minecraft:firework_star", + "damage": 6, + "nbt_b64": "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id": "minecraft:chain" + }, + { + "id": "minecraft:target", + "block_state_b64": "CgAAAwgAYmxvY2tfaWTuAQAACAQAbmFtZRAAbWluZWNyYWZ0OnRhcmdldAQJAG5hbWVfaGFzaJc66SVbYlaxAwoAbmV0d29ya19pZPBozs0KBgBzdGF0ZXMAAwcAdmVyc2lvbgceFQEA" + }, + { + "id": "minecraft:decorated_pot", + "block_state_b64": "CgAAAwgAYmxvY2tfaWQmAwAACAQAbmFtZRcAbWluZWNyYWZ0OmRlY29yYXRlZF9wb3QECQBuYW1lX2hhc2jjQgckn8VTvwMKAG5ldHdvcmtfaWRwvkUUCgYAc3RhdGVzAwkAZGlyZWN0aW9uAAAAAAADBwB2ZXJzaW9uBx4VAQA=" + }, + { + "id": "minecraft:lodestone_compass" + }, + { + "id": "minecraft:wither_spawn_egg" + }, + { + "id": "minecraft:ender_dragon_spawn_egg" + }, + { + "id": "minecraft:ominous_trial_key" + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/entity_identifiers.dat b/core/src/main/resources/bedrock/entity_identifiers.dat index 95d00c246..90ca16be3 100644 Binary files a/core/src/main/resources/bedrock/entity_identifiers.dat and b/core/src/main/resources/bedrock/entity_identifiers.dat differ diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_21_20.json b/core/src/main/resources/bedrock/runtime_item_states.1_21_20.json new file mode 100644 index 000000000..72c682293 --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_21_20.json @@ -0,0 +1,6794 @@ +[ + { + "name": "minecraft:acacia_boat", + "id": 388 + }, + { + "name": "minecraft:acacia_button", + "id": -140 + }, + { + "name": "minecraft:acacia_chest_boat", + "id": 662 + }, + { + "name": "minecraft:acacia_door", + "id": 573 + }, + { + "name": "minecraft:acacia_double_slab", + "id": -812 + }, + { + "name": "minecraft:acacia_fence", + "id": -575 + }, + { + "name": "minecraft:acacia_fence_gate", + "id": 187 + }, + { + "name": "minecraft:acacia_hanging_sign", + "id": -504 + }, + { + "name": "minecraft:acacia_leaves", + "id": 161 + }, + { + "name": "minecraft:acacia_log", + "id": 162 + }, + { + "name": "minecraft:acacia_planks", + "id": -742 + }, + { + "name": "minecraft:acacia_pressure_plate", + "id": -150 + }, + { + "name": "minecraft:acacia_sapling", + "id": -828 + }, + { + "name": "minecraft:acacia_sign", + "id": 596 + }, + { + "name": "minecraft:acacia_slab", + "id": -807 + }, + { + "name": "minecraft:acacia_stairs", + "id": 163 + }, + { + "name": "minecraft:acacia_standing_sign", + "id": -190 + }, + { + "name": "minecraft:acacia_trapdoor", + "id": -145 + }, + { + "name": "minecraft:acacia_wall_sign", + "id": -191 + }, + { + "name": "minecraft:acacia_wood", + "id": -817 + }, + { + "name": "minecraft:activator_rail", + "id": 126 + }, + { + "name": "minecraft:agent_spawn_egg", + "id": 498 + }, + { + "name": "minecraft:air", + "id": -158 + }, + { + "name": "minecraft:allay_spawn_egg", + "id": 651 + }, + { + "name": "minecraft:allium", + "id": -831 + }, + { + "name": "minecraft:allow", + "id": 210 + }, + { + "name": "minecraft:amethyst_block", + "id": -327 + }, + { + "name": "minecraft:amethyst_cluster", + "id": -329 + }, + { + "name": "minecraft:amethyst_shard", + "id": 644 + }, + { + "name": "minecraft:ancient_debris", + "id": -271 + }, + { + "name": "minecraft:andesite", + "id": -594 + }, + { + "name": "minecraft:andesite_double_slab", + "id": -920 + }, + { + "name": "minecraft:andesite_slab", + "id": -893 + }, + { + "name": "minecraft:andesite_stairs", + "id": -171 + }, + { + "name": "minecraft:angler_pottery_sherd", + "id": 676 + }, + { + "name": "minecraft:anvil", + "id": 145 + }, + { + "name": "minecraft:apple", + "id": 257 + }, + { + "name": "minecraft:archer_pottery_sherd", + "id": 677 + }, + { + "name": "minecraft:armadillo_scute", + "id": 722 + }, + { + "name": "minecraft:armadillo_spawn_egg", + "id": 721 + }, + { + "name": "minecraft:armor_stand", + "id": 569 + }, + { + "name": "minecraft:arms_up_pottery_sherd", + "id": 678 + }, + { + "name": "minecraft:arrow", + "id": 308 + }, + { + "name": "minecraft:axolotl_bucket", + "id": 377 + }, + { + "name": "minecraft:axolotl_spawn_egg", + "id": 513 + }, + { + "name": "minecraft:azalea", + "id": -337 + }, + { + "name": "minecraft:azalea_leaves", + "id": -324 + }, + { + "name": "minecraft:azalea_leaves_flowered", + "id": -325 + }, + { + "name": "minecraft:azure_bluet", + "id": -832 + }, + { + "name": "minecraft:baked_potato", + "id": 286 + }, + { + "name": "minecraft:balloon", + "id": 618 + }, + { + "name": "minecraft:bamboo", + "id": -163 + }, + { + "name": "minecraft:bamboo_block", + "id": -527 + }, + { + "name": "minecraft:bamboo_button", + "id": -511 + }, + { + "name": "minecraft:bamboo_chest_raft", + "id": 674 + }, + { + "name": "minecraft:bamboo_door", + "id": -517 + }, + { + "name": "minecraft:bamboo_double_slab", + "id": -521 + }, + { + "name": "minecraft:bamboo_fence", + "id": -515 + }, + { + "name": "minecraft:bamboo_fence_gate", + "id": -516 + }, + { + "name": "minecraft:bamboo_hanging_sign", + "id": -522 + }, + { + "name": "minecraft:bamboo_mosaic", + "id": -509 + }, + { + "name": "minecraft:bamboo_mosaic_double_slab", + "id": -525 + }, + { + "name": "minecraft:bamboo_mosaic_slab", + "id": -524 + }, + { + "name": "minecraft:bamboo_mosaic_stairs", + "id": -523 + }, + { + "name": "minecraft:bamboo_planks", + "id": -510 + }, + { + "name": "minecraft:bamboo_pressure_plate", + "id": -514 + }, + { + "name": "minecraft:bamboo_raft", + "id": 673 + }, + { + "name": "minecraft:bamboo_sapling", + "id": -164 + }, + { + "name": "minecraft:bamboo_sign", + "id": 672 + }, + { + "name": "minecraft:bamboo_slab", + "id": -513 + }, + { + "name": "minecraft:bamboo_stairs", + "id": -512 + }, + { + "name": "minecraft:bamboo_standing_sign", + "id": -518 + }, + { + "name": "minecraft:bamboo_trapdoor", + "id": -520 + }, + { + "name": "minecraft:bamboo_wall_sign", + "id": -519 + }, + { + "name": "minecraft:banner", + "id": 584 + }, + { + "name": "minecraft:banner_pattern", + "id": 767 + }, + { + "name": "minecraft:barrel", + "id": -203 + }, + { + "name": "minecraft:barrier", + "id": -161 + }, + { + "name": "minecraft:basalt", + "id": -234 + }, + { + "name": "minecraft:bat_spawn_egg", + "id": 463 + }, + { + "name": "minecraft:beacon", + "id": 138 + }, + { + "name": "minecraft:bed", + "id": 427 + }, + { + "name": "minecraft:bedrock", + "id": 7 + }, + { + "name": "minecraft:bee_nest", + "id": -218 + }, + { + "name": "minecraft:bee_spawn_egg", + "id": 505 + }, + { + "name": "minecraft:beef", + "id": 278 + }, + { + "name": "minecraft:beehive", + "id": -219 + }, + { + "name": "minecraft:beetroot", + "id": 290 + }, + { + "name": "minecraft:beetroot_seeds", + "id": 300 + }, + { + "name": "minecraft:beetroot_soup", + "id": 291 + }, + { + "name": "minecraft:bell", + "id": -206 + }, + { + "name": "minecraft:big_dripleaf", + "id": -323 + }, + { + "name": "minecraft:birch_boat", + "id": 385 + }, + { + "name": "minecraft:birch_button", + "id": -141 + }, + { + "name": "minecraft:birch_chest_boat", + "id": 659 + }, + { + "name": "minecraft:birch_door", + "id": 571 + }, + { + "name": "minecraft:birch_double_slab", + "id": -810 + }, + { + "name": "minecraft:birch_fence", + "id": -576 + }, + { + "name": "minecraft:birch_fence_gate", + "id": 184 + }, + { + "name": "minecraft:birch_hanging_sign", + "id": -502 + }, + { + "name": "minecraft:birch_leaves", + "id": -801 + }, + { + "name": "minecraft:birch_log", + "id": -570 + }, + { + "name": "minecraft:birch_planks", + "id": -740 + }, + { + "name": "minecraft:birch_pressure_plate", + "id": -151 + }, + { + "name": "minecraft:birch_sapling", + "id": -826 + }, + { + "name": "minecraft:birch_sign", + "id": 594 + }, + { + "name": "minecraft:birch_slab", + "id": -805 + }, + { + "name": "minecraft:birch_stairs", + "id": 135 + }, + { + "name": "minecraft:birch_standing_sign", + "id": -186 + }, + { + "name": "minecraft:birch_trapdoor", + "id": -146 + }, + { + "name": "minecraft:birch_wall_sign", + "id": -187 + }, + { + "name": "minecraft:birch_wood", + "id": -815 + }, + { + "name": "minecraft:black_candle", + "id": -428 + }, + { + "name": "minecraft:black_candle_cake", + "id": -445 + }, + { + "name": "minecraft:black_carpet", + "id": -611 + }, + { + "name": "minecraft:black_concrete", + "id": -642 + }, + { + "name": "minecraft:black_concrete_powder", + "id": -723 + }, + { + "name": "minecraft:black_dye", + "id": 404 + }, + { + "name": "minecraft:black_glazed_terracotta", + "id": 235 + }, + { + "name": "minecraft:black_shulker_box", + "id": -627 + }, + { + "name": "minecraft:black_stained_glass", + "id": -687 + }, + { + "name": "minecraft:black_stained_glass_pane", + "id": -657 + }, + { + "name": "minecraft:black_terracotta", + "id": -738 + }, + { + "name": "minecraft:black_wool", + "id": -554 + }, + { + "name": "minecraft:blackstone", + "id": -273 + }, + { + "name": "minecraft:blackstone_double_slab", + "id": -283 + }, + { + "name": "minecraft:blackstone_slab", + "id": -282 + }, + { + "name": "minecraft:blackstone_stairs", + "id": -276 + }, + { + "name": "minecraft:blackstone_wall", + "id": -277 + }, + { + "name": "minecraft:blade_pottery_sherd", + "id": 679 + }, + { + "name": "minecraft:blast_furnace", + "id": -196 + }, + { + "name": "minecraft:blaze_powder", + "id": 439 + }, + { + "name": "minecraft:blaze_rod", + "id": 432 + }, + { + "name": "minecraft:blaze_spawn_egg", + "id": 466 + }, + { + "name": "minecraft:bleach", + "id": 616 + }, + { + "name": "minecraft:blue_candle", + "id": -424 + }, + { + "name": "minecraft:blue_candle_cake", + "id": -441 + }, + { + "name": "minecraft:blue_carpet", + "id": -607 + }, + { + "name": "minecraft:blue_concrete", + "id": -638 + }, + { + "name": "minecraft:blue_concrete_powder", + "id": -719 + }, + { + "name": "minecraft:blue_dye", + "id": 408 + }, + { + "name": "minecraft:blue_glazed_terracotta", + "id": 231 + }, + { + "name": "minecraft:blue_ice", + "id": -11 + }, + { + "name": "minecraft:blue_orchid", + "id": -830 + }, + { + "name": "minecraft:blue_shulker_box", + "id": -623 + }, + { + "name": "minecraft:blue_stained_glass", + "id": -683 + }, + { + "name": "minecraft:blue_stained_glass_pane", + "id": -653 + }, + { + "name": "minecraft:blue_terracotta", + "id": -734 + }, + { + "name": "minecraft:blue_wool", + "id": -563 + }, + { + "name": "minecraft:boat", + "id": 765 + }, + { + "name": "minecraft:bogged_spawn_egg", + "id": 473 + }, + { + "name": "minecraft:bolt_armor_trim_smithing_template", + "id": 718 + }, + { + "name": "minecraft:bone", + "id": 424 + }, + { + "name": "minecraft:bone_block", + "id": 216 + }, + { + "name": "minecraft:bone_meal", + "id": 420 + }, + { + "name": "minecraft:book", + "id": 396 + }, + { + "name": "minecraft:bookshelf", + "id": 47 + }, + { + "name": "minecraft:border_block", + "id": 212 + }, + { + "name": "minecraft:bordure_indented_banner_pattern", + "id": 603 + }, + { + "name": "minecraft:bow", + "id": 307 + }, + { + "name": "minecraft:bowl", + "id": 329 + }, + { + "name": "minecraft:brain_coral", + "id": -581 + }, + { + "name": "minecraft:brain_coral_block", + "id": -849 + }, + { + "name": "minecraft:brain_coral_fan", + "id": -840 + }, + { + "name": "minecraft:brain_coral_wall_fan", + "id": -904 + }, + { + "name": "minecraft:bread", + "id": 266 + }, + { + "name": "minecraft:breeze_rod", + "id": 261 + }, + { + "name": "minecraft:breeze_spawn_egg", + "id": 512 + }, + { + "name": "minecraft:brewer_pottery_sherd", + "id": 680 + }, + { + "name": "minecraft:brewing_stand", + "id": 441 + }, + { + "name": "minecraft:brick", + "id": 392 + }, + { + "name": "minecraft:brick_block", + "id": 45 + }, + { + "name": "minecraft:brick_double_slab", + "id": -880 + }, + { + "name": "minecraft:brick_slab", + "id": -874 + }, + { + "name": "minecraft:brick_stairs", + "id": 108 + }, + { + "name": "minecraft:brown_candle", + "id": -425 + }, + { + "name": "minecraft:brown_candle_cake", + "id": -442 + }, + { + "name": "minecraft:brown_carpet", + "id": -608 + }, + { + "name": "minecraft:brown_concrete", + "id": -639 + }, + { + "name": "minecraft:brown_concrete_powder", + "id": -720 + }, + { + "name": "minecraft:brown_dye", + "id": 407 + }, + { + "name": "minecraft:brown_glazed_terracotta", + "id": 232 + }, + { + "name": "minecraft:brown_mushroom", + "id": 39 + }, + { + "name": "minecraft:brown_mushroom_block", + "id": 99 + }, + { + "name": "minecraft:brown_shulker_box", + "id": -624 + }, + { + "name": "minecraft:brown_stained_glass", + "id": -684 + }, + { + "name": "minecraft:brown_stained_glass_pane", + "id": -654 + }, + { + "name": "minecraft:brown_terracotta", + "id": -735 + }, + { + "name": "minecraft:brown_wool", + "id": -555 + }, + { + "name": "minecraft:brush", + "id": 699 + }, + { + "name": "minecraft:bubble_column", + "id": -160 + }, + { + "name": "minecraft:bubble_coral", + "id": -582 + }, + { + "name": "minecraft:bubble_coral_block", + "id": -850 + }, + { + "name": "minecraft:bubble_coral_fan", + "id": -841 + }, + { + "name": "minecraft:bubble_coral_wall_fan", + "id": -136 + }, + { + "name": "minecraft:bucket", + "id": 368 + }, + { + "name": "minecraft:budding_amethyst", + "id": -328 + }, + { + "name": "minecraft:burn_pottery_sherd", + "id": 681 + }, + { + "name": "minecraft:cactus", + "id": 81 + }, + { + "name": "minecraft:cake", + "id": 426 + }, + { + "name": "minecraft:calcite", + "id": -326 + }, + { + "name": "minecraft:calibrated_sculk_sensor", + "id": -580 + }, + { + "name": "minecraft:camel_spawn_egg", + "id": 675 + }, + { + "name": "minecraft:camera", + "id": 613 + }, + { + "name": "minecraft:campfire", + "id": 608 + }, + { + "name": "minecraft:candle", + "id": -412 + }, + { + "name": "minecraft:candle_cake", + "id": -429 + }, + { + "name": "minecraft:carpet", + "id": 727 + }, + { + "name": "minecraft:carrot", + "id": 284 + }, + { + "name": "minecraft:carrot_on_a_stick", + "id": 534 + }, + { + "name": "minecraft:carrots", + "id": 141 + }, + { + "name": "minecraft:cartography_table", + "id": -200 + }, + { + "name": "minecraft:carved_pumpkin", + "id": -155 + }, + { + "name": "minecraft:cat_spawn_egg", + "id": 499 + }, + { + "name": "minecraft:cauldron", + "id": 442 + }, + { + "name": "minecraft:cave_spider_spawn_egg", + "id": 467 + }, + { + "name": "minecraft:cave_vines", + "id": -322 + }, + { + "name": "minecraft:cave_vines_body_with_berries", + "id": -375 + }, + { + "name": "minecraft:cave_vines_head_with_berries", + "id": -376 + }, + { + "name": "minecraft:chain", + "id": 639 + }, + { + "name": "minecraft:chain_command_block", + "id": 189 + }, + { + "name": "minecraft:chainmail_boots", + "id": 350 + }, + { + "name": "minecraft:chainmail_chestplate", + "id": 348 + }, + { + "name": "minecraft:chainmail_helmet", + "id": 347 + }, + { + "name": "minecraft:chainmail_leggings", + "id": 349 + }, + { + "name": "minecraft:charcoal", + "id": 310 + }, + { + "name": "minecraft:chemical_heat", + "id": 192 + }, + { + "name": "minecraft:chemistry_table", + "id": 238 + }, + { + "name": "minecraft:cherry_boat", + "id": 669 + }, + { + "name": "minecraft:cherry_button", + "id": -530 + }, + { + "name": "minecraft:cherry_chest_boat", + "id": 670 + }, + { + "name": "minecraft:cherry_door", + "id": -531 + }, + { + "name": "minecraft:cherry_double_slab", + "id": -540 + }, + { + "name": "minecraft:cherry_fence", + "id": -532 + }, + { + "name": "minecraft:cherry_fence_gate", + "id": -533 + }, + { + "name": "minecraft:cherry_hanging_sign", + "id": -534 + }, + { + "name": "minecraft:cherry_leaves", + "id": -548 + }, + { + "name": "minecraft:cherry_log", + "id": -536 + }, + { + "name": "minecraft:cherry_planks", + "id": -537 + }, + { + "name": "minecraft:cherry_pressure_plate", + "id": -538 + }, + { + "name": "minecraft:cherry_sapling", + "id": -547 + }, + { + "name": "minecraft:cherry_sign", + "id": 671 + }, + { + "name": "minecraft:cherry_slab", + "id": -539 + }, + { + "name": "minecraft:cherry_stairs", + "id": -541 + }, + { + "name": "minecraft:cherry_standing_sign", + "id": -542 + }, + { + "name": "minecraft:cherry_trapdoor", + "id": -543 + }, + { + "name": "minecraft:cherry_wall_sign", + "id": -544 + }, + { + "name": "minecraft:cherry_wood", + "id": -546 + }, + { + "name": "minecraft:chest", + "id": 54 + }, + { + "name": "minecraft:chest_boat", + "id": 665 + }, + { + "name": "minecraft:chest_minecart", + "id": 398 + }, + { + "name": "minecraft:chicken", + "id": 280 + }, + { + "name": "minecraft:chicken_spawn_egg", + "id": 445 + }, + { + "name": "minecraft:chipped_anvil", + "id": -959 + }, + { + "name": "minecraft:chiseled_bookshelf", + "id": -526 + }, + { + "name": "minecraft:chiseled_copper", + "id": -760 + }, + { + "name": "minecraft:chiseled_deepslate", + "id": -395 + }, + { + "name": "minecraft:chiseled_nether_bricks", + "id": -302 + }, + { + "name": "minecraft:chiseled_polished_blackstone", + "id": -279 + }, + { + "name": "minecraft:chiseled_quartz_block", + "id": -953 + }, + { + "name": "minecraft:chiseled_red_sandstone", + "id": -956 + }, + { + "name": "minecraft:chiseled_sandstone", + "id": -944 + }, + { + "name": "minecraft:chiseled_stone_bricks", + "id": -870 + }, + { + "name": "minecraft:chiseled_tuff", + "id": -753 + }, + { + "name": "minecraft:chiseled_tuff_bricks", + "id": -759 + }, + { + "name": "minecraft:chorus_flower", + "id": 200 + }, + { + "name": "minecraft:chorus_fruit", + "id": 575 + }, + { + "name": "minecraft:chorus_plant", + "id": 240 + }, + { + "name": "minecraft:clay", + "id": 82 + }, + { + "name": "minecraft:clay_ball", + "id": 393 + }, + { + "name": "minecraft:client_request_placeholder_block", + "id": -465 + }, + { + "name": "minecraft:clock", + "id": 402 + }, + { + "name": "minecraft:coal", + "id": 309 + }, + { + "name": "minecraft:coal_block", + "id": 173 + }, + { + "name": "minecraft:coal_ore", + "id": 16 + }, + { + "name": "minecraft:coarse_dirt", + "id": -962 + }, + { + "name": "minecraft:coast_armor_trim_smithing_template", + "id": 703 + }, + { + "name": "minecraft:cobbled_deepslate", + "id": -379 + }, + { + "name": "minecraft:cobbled_deepslate_double_slab", + "id": -396 + }, + { + "name": "minecraft:cobbled_deepslate_slab", + "id": -380 + }, + { + "name": "minecraft:cobbled_deepslate_stairs", + "id": -381 + }, + { + "name": "minecraft:cobbled_deepslate_wall", + "id": -382 + }, + { + "name": "minecraft:cobblestone", + "id": 4 + }, + { + "name": "minecraft:cobblestone_double_slab", + "id": -879 + }, + { + "name": "minecraft:cobblestone_slab", + "id": -873 + }, + { + "name": "minecraft:cobblestone_wall", + "id": 139 + }, + { + "name": "minecraft:cocoa", + "id": 127 + }, + { + "name": "minecraft:cocoa_beans", + "id": 421 + }, + { + "name": "minecraft:cod", + "id": 269 + }, + { + "name": "minecraft:cod_bucket", + "id": 372 + }, + { + "name": "minecraft:cod_spawn_egg", + "id": 491 + }, + { + "name": "minecraft:colored_torch_bp", + "id": 204 + }, + { + "name": "minecraft:colored_torch_rg", + "id": 202 + }, + { + "name": "minecraft:command_block", + "id": 137 + }, + { + "name": "minecraft:command_block_minecart", + "id": 580 + }, + { + "name": "minecraft:comparator", + "id": 539 + }, + { + "name": "minecraft:compass", + "id": 400 + }, + { + "name": "minecraft:composter", + "id": -213 + }, + { + "name": "minecraft:compound", + "id": 614 + }, + { + "name": "minecraft:concrete", + "id": 753 + }, + { + "name": "minecraft:concrete_powder", + "id": 754 + }, + { + "name": "minecraft:conduit", + "id": -157 + }, + { + "name": "minecraft:cooked_beef", + "id": 279 + }, + { + "name": "minecraft:cooked_chicken", + "id": 281 + }, + { + "name": "minecraft:cooked_cod", + "id": 273 + }, + { + "name": "minecraft:cooked_mutton", + "id": 568 + }, + { + "name": "minecraft:cooked_porkchop", + "id": 268 + }, + { + "name": "minecraft:cooked_rabbit", + "id": 294 + }, + { + "name": "minecraft:cooked_salmon", + "id": 274 + }, + { + "name": "minecraft:cookie", + "id": 276 + }, + { + "name": "minecraft:copper_block", + "id": -340 + }, + { + "name": "minecraft:copper_bulb", + "id": -776 + }, + { + "name": "minecraft:copper_door", + "id": -784 + }, + { + "name": "minecraft:copper_grate", + "id": -768 + }, + { + "name": "minecraft:copper_ingot", + "id": 521 + }, + { + "name": "minecraft:copper_ore", + "id": -311 + }, + { + "name": "minecraft:copper_trapdoor", + "id": -792 + }, + { + "name": "minecraft:coral", + "id": 749 + }, + { + "name": "minecraft:coral_block", + "id": 731 + }, + { + "name": "minecraft:coral_fan", + "id": 740 + }, + { + "name": "minecraft:coral_fan_dead", + "id": 741 + }, + { + "name": "minecraft:cornflower", + "id": -838 + }, + { + "name": "minecraft:cow_spawn_egg", + "id": 446 + }, + { + "name": "minecraft:cracked_deepslate_bricks", + "id": -410 + }, + { + "name": "minecraft:cracked_deepslate_tiles", + "id": -409 + }, + { + "name": "minecraft:cracked_nether_bricks", + "id": -303 + }, + { + "name": "minecraft:cracked_polished_blackstone_bricks", + "id": -280 + }, + { + "name": "minecraft:cracked_stone_bricks", + "id": -869 + }, + { + "name": "minecraft:crafter", + "id": -313 + }, + { + "name": "minecraft:crafting_table", + "id": 58 + }, + { + "name": "minecraft:creeper_banner_pattern", + "id": 599 + }, + { + "name": "minecraft:creeper_spawn_egg", + "id": 451 + }, + { + "name": "minecraft:crimson_button", + "id": -260 + }, + { + "name": "minecraft:crimson_door", + "id": 636 + }, + { + "name": "minecraft:crimson_double_slab", + "id": -266 + }, + { + "name": "minecraft:crimson_fence", + "id": -256 + }, + { + "name": "minecraft:crimson_fence_gate", + "id": -258 + }, + { + "name": "minecraft:crimson_fungus", + "id": -228 + }, + { + "name": "minecraft:crimson_hanging_sign", + "id": -506 + }, + { + "name": "minecraft:crimson_hyphae", + "id": -299 + }, + { + "name": "minecraft:crimson_nylium", + "id": -232 + }, + { + "name": "minecraft:crimson_planks", + "id": -242 + }, + { + "name": "minecraft:crimson_pressure_plate", + "id": -262 + }, + { + "name": "minecraft:crimson_roots", + "id": -223 + }, + { + "name": "minecraft:crimson_sign", + "id": 634 + }, + { + "name": "minecraft:crimson_slab", + "id": -264 + }, + { + "name": "minecraft:crimson_stairs", + "id": -254 + }, + { + "name": "minecraft:crimson_standing_sign", + "id": -250 + }, + { + "name": "minecraft:crimson_stem", + "id": -225 + }, + { + "name": "minecraft:crimson_trapdoor", + "id": -246 + }, + { + "name": "minecraft:crimson_wall_sign", + "id": -252 + }, + { + "name": "minecraft:crossbow", + "id": 592 + }, + { + "name": "minecraft:crying_obsidian", + "id": -289 + }, + { + "name": "minecraft:cut_copper", + "id": -347 + }, + { + "name": "minecraft:cut_copper_slab", + "id": -361 + }, + { + "name": "minecraft:cut_copper_stairs", + "id": -354 + }, + { + "name": "minecraft:cut_red_sandstone", + "id": -957 + }, + { + "name": "minecraft:cut_red_sandstone_double_slab", + "id": -928 + }, + { + "name": "minecraft:cut_red_sandstone_slab", + "id": -901 + }, + { + "name": "minecraft:cut_sandstone", + "id": -945 + }, + { + "name": "minecraft:cut_sandstone_double_slab", + "id": -927 + }, + { + "name": "minecraft:cut_sandstone_slab", + "id": -900 + }, + { + "name": "minecraft:cyan_candle", + "id": -422 + }, + { + "name": "minecraft:cyan_candle_cake", + "id": -439 + }, + { + "name": "minecraft:cyan_carpet", + "id": -605 + }, + { + "name": "minecraft:cyan_concrete", + "id": -636 + }, + { + "name": "minecraft:cyan_concrete_powder", + "id": -717 + }, + { + "name": "minecraft:cyan_dye", + "id": 410 + }, + { + "name": "minecraft:cyan_glazed_terracotta", + "id": 229 + }, + { + "name": "minecraft:cyan_shulker_box", + "id": -621 + }, + { + "name": "minecraft:cyan_stained_glass", + "id": -681 + }, + { + "name": "minecraft:cyan_stained_glass_pane", + "id": -651 + }, + { + "name": "minecraft:cyan_terracotta", + "id": -732 + }, + { + "name": "minecraft:cyan_wool", + "id": -561 + }, + { + "name": "minecraft:damaged_anvil", + "id": -960 + }, + { + "name": "minecraft:dandelion", + "id": 37 + }, + { + "name": "minecraft:danger_pottery_sherd", + "id": 682 + }, + { + "name": "minecraft:dark_oak_boat", + "id": 389 + }, + { + "name": "minecraft:dark_oak_button", + "id": -142 + }, + { + "name": "minecraft:dark_oak_chest_boat", + "id": 663 + }, + { + "name": "minecraft:dark_oak_door", + "id": 574 + }, + { + "name": "minecraft:dark_oak_double_slab", + "id": -813 + }, + { + "name": "minecraft:dark_oak_fence", + "id": -577 + }, + { + "name": "minecraft:dark_oak_fence_gate", + "id": 186 + }, + { + "name": "minecraft:dark_oak_hanging_sign", + "id": -505 + }, + { + "name": "minecraft:dark_oak_leaves", + "id": -803 + }, + { + "name": "minecraft:dark_oak_log", + "id": -572 + }, + { + "name": "minecraft:dark_oak_planks", + "id": -743 + }, + { + "name": "minecraft:dark_oak_pressure_plate", + "id": -152 + }, + { + "name": "minecraft:dark_oak_sapling", + "id": -829 + }, + { + "name": "minecraft:dark_oak_sign", + "id": 597 + }, + { + "name": "minecraft:dark_oak_slab", + "id": -808 + }, + { + "name": "minecraft:dark_oak_stairs", + "id": 164 + }, + { + "name": "minecraft:dark_oak_trapdoor", + "id": -147 + }, + { + "name": "minecraft:dark_oak_wood", + "id": -818 + }, + { + "name": "minecraft:dark_prismarine", + "id": -947 + }, + { + "name": "minecraft:dark_prismarine_double_slab", + "id": -913 + }, + { + "name": "minecraft:dark_prismarine_slab", + "id": -886 + }, + { + "name": "minecraft:dark_prismarine_stairs", + "id": -3 + }, + { + "name": "minecraft:darkoak_standing_sign", + "id": -192 + }, + { + "name": "minecraft:darkoak_wall_sign", + "id": -193 + }, + { + "name": "minecraft:daylight_detector", + "id": 151 + }, + { + "name": "minecraft:daylight_detector_inverted", + "id": 178 + }, + { + "name": "minecraft:dead_brain_coral", + "id": -586 + }, + { + "name": "minecraft:dead_brain_coral_block", + "id": -854 + }, + { + "name": "minecraft:dead_brain_coral_fan", + "id": -844 + }, + { + "name": "minecraft:dead_brain_coral_wall_fan", + "id": -906 + }, + { + "name": "minecraft:dead_bubble_coral", + "id": -587 + }, + { + "name": "minecraft:dead_bubble_coral_block", + "id": -855 + }, + { + "name": "minecraft:dead_bubble_coral_fan", + "id": -845 + }, + { + "name": "minecraft:dead_bubble_coral_wall_fan", + "id": -908 + }, + { + "name": "minecraft:dead_fire_coral", + "id": -588 + }, + { + "name": "minecraft:dead_fire_coral_block", + "id": -856 + }, + { + "name": "minecraft:dead_fire_coral_fan", + "id": -846 + }, + { + "name": "minecraft:dead_fire_coral_wall_fan", + "id": -909 + }, + { + "name": "minecraft:dead_horn_coral", + "id": -589 + }, + { + "name": "minecraft:dead_horn_coral_block", + "id": -857 + }, + { + "name": "minecraft:dead_horn_coral_fan", + "id": -847 + }, + { + "name": "minecraft:dead_horn_coral_wall_fan", + "id": -910 + }, + { + "name": "minecraft:dead_tube_coral", + "id": -585 + }, + { + "name": "minecraft:dead_tube_coral_block", + "id": -853 + }, + { + "name": "minecraft:dead_tube_coral_fan", + "id": -134 + }, + { + "name": "minecraft:dead_tube_coral_wall_fan", + "id": -905 + }, + { + "name": "minecraft:deadbush", + "id": 32 + }, + { + "name": "minecraft:decorated_pot", + "id": -551 + }, + { + "name": "minecraft:deepslate", + "id": -378 + }, + { + "name": "minecraft:deepslate_brick_double_slab", + "id": -399 + }, + { + "name": "minecraft:deepslate_brick_slab", + "id": -392 + }, + { + "name": "minecraft:deepslate_brick_stairs", + "id": -393 + }, + { + "name": "minecraft:deepslate_brick_wall", + "id": -394 + }, + { + "name": "minecraft:deepslate_bricks", + "id": -391 + }, + { + "name": "minecraft:deepslate_coal_ore", + "id": -406 + }, + { + "name": "minecraft:deepslate_copper_ore", + "id": -408 + }, + { + "name": "minecraft:deepslate_diamond_ore", + "id": -405 + }, + { + "name": "minecraft:deepslate_emerald_ore", + "id": -407 + }, + { + "name": "minecraft:deepslate_gold_ore", + "id": -402 + }, + { + "name": "minecraft:deepslate_iron_ore", + "id": -401 + }, + { + "name": "minecraft:deepslate_lapis_ore", + "id": -400 + }, + { + "name": "minecraft:deepslate_redstone_ore", + "id": -403 + }, + { + "name": "minecraft:deepslate_tile_double_slab", + "id": -398 + }, + { + "name": "minecraft:deepslate_tile_slab", + "id": -388 + }, + { + "name": "minecraft:deepslate_tile_stairs", + "id": -389 + }, + { + "name": "minecraft:deepslate_tile_wall", + "id": -390 + }, + { + "name": "minecraft:deepslate_tiles", + "id": -387 + }, + { + "name": "minecraft:deny", + "id": 211 + }, + { + "name": "minecraft:deprecated_anvil", + "id": -961 + }, + { + "name": "minecraft:detector_rail", + "id": 28 + }, + { + "name": "minecraft:diamond", + "id": 311 + }, + { + "name": "minecraft:diamond_axe", + "id": 326 + }, + { + "name": "minecraft:diamond_block", + "id": 57 + }, + { + "name": "minecraft:diamond_boots", + "id": 358 + }, + { + "name": "minecraft:diamond_chestplate", + "id": 356 + }, + { + "name": "minecraft:diamond_helmet", + "id": 355 + }, + { + "name": "minecraft:diamond_hoe", + "id": 340 + }, + { + "name": "minecraft:diamond_horse_armor", + "id": 550 + }, + { + "name": "minecraft:diamond_leggings", + "id": 357 + }, + { + "name": "minecraft:diamond_ore", + "id": 56 + }, + { + "name": "minecraft:diamond_pickaxe", + "id": 325 + }, + { + "name": "minecraft:diamond_shovel", + "id": 324 + }, + { + "name": "minecraft:diamond_sword", + "id": 323 + }, + { + "name": "minecraft:diorite", + "id": -592 + }, + { + "name": "minecraft:diorite_double_slab", + "id": -921 + }, + { + "name": "minecraft:diorite_slab", + "id": -894 + }, + { + "name": "minecraft:diorite_stairs", + "id": -170 + }, + { + "name": "minecraft:dirt", + "id": 3 + }, + { + "name": "minecraft:dirt_with_roots", + "id": -318 + }, + { + "name": "minecraft:disc_fragment_5", + "id": 657 + }, + { + "name": "minecraft:dispenser", + "id": 23 + }, + { + "name": "minecraft:dolphin_spawn_egg", + "id": 495 + }, + { + "name": "minecraft:donkey_spawn_egg", + "id": 476 + }, + { + "name": "minecraft:double_cut_copper_slab", + "id": -368 + }, + { + "name": "minecraft:double_plant", + "id": 747 + }, + { + "name": "minecraft:double_stone_block_slab", + "id": 736 + }, + { + "name": "minecraft:double_stone_block_slab2", + "id": 737 + }, + { + "name": "minecraft:double_stone_block_slab3", + "id": 738 + }, + { + "name": "minecraft:double_stone_block_slab4", + "id": 739 + }, + { + "name": "minecraft:dragon_breath", + "id": 577 + }, + { + "name": "minecraft:dragon_egg", + "id": 122 + }, + { + "name": "minecraft:dried_kelp", + "id": 275 + }, + { + "name": "minecraft:dried_kelp_block", + "id": -139 + }, + { + "name": "minecraft:dripstone_block", + "id": -317 + }, + { + "name": "minecraft:dropper", + "id": 125 + }, + { + "name": "minecraft:drowned_spawn_egg", + "id": 494 + }, + { + "name": "minecraft:dune_armor_trim_smithing_template", + "id": 702 + }, + { + "name": "minecraft:dye", + "id": 766 + }, + { + "name": "minecraft:echo_shard", + "id": 667 + }, + { + "name": "minecraft:egg", + "id": 399 + }, + { + "name": "minecraft:elder_guardian_spawn_egg", + "id": 482 + }, + { + "name": "minecraft:element_0", + "id": 36 + }, + { + "name": "minecraft:element_1", + "id": -12 + }, + { + "name": "minecraft:element_10", + "id": -21 + }, + { + "name": "minecraft:element_100", + "id": -111 + }, + { + "name": "minecraft:element_101", + "id": -112 + }, + { + "name": "minecraft:element_102", + "id": -113 + }, + { + "name": "minecraft:element_103", + "id": -114 + }, + { + "name": "minecraft:element_104", + "id": -115 + }, + { + "name": "minecraft:element_105", + "id": -116 + }, + { + "name": "minecraft:element_106", + "id": -117 + }, + { + "name": "minecraft:element_107", + "id": -118 + }, + { + "name": "minecraft:element_108", + "id": -119 + }, + { + "name": "minecraft:element_109", + "id": -120 + }, + { + "name": "minecraft:element_11", + "id": -22 + }, + { + "name": "minecraft:element_110", + "id": -121 + }, + { + "name": "minecraft:element_111", + "id": -122 + }, + { + "name": "minecraft:element_112", + "id": -123 + }, + { + "name": "minecraft:element_113", + "id": -124 + }, + { + "name": "minecraft:element_114", + "id": -125 + }, + { + "name": "minecraft:element_115", + "id": -126 + }, + { + "name": "minecraft:element_116", + "id": -127 + }, + { + "name": "minecraft:element_117", + "id": -128 + }, + { + "name": "minecraft:element_118", + "id": -129 + }, + { + "name": "minecraft:element_12", + "id": -23 + }, + { + "name": "minecraft:element_13", + "id": -24 + }, + { + "name": "minecraft:element_14", + "id": -25 + }, + { + "name": "minecraft:element_15", + "id": -26 + }, + { + "name": "minecraft:element_16", + "id": -27 + }, + { + "name": "minecraft:element_17", + "id": -28 + }, + { + "name": "minecraft:element_18", + "id": -29 + }, + { + "name": "minecraft:element_19", + "id": -30 + }, + { + "name": "minecraft:element_2", + "id": -13 + }, + { + "name": "minecraft:element_20", + "id": -31 + }, + { + "name": "minecraft:element_21", + "id": -32 + }, + { + "name": "minecraft:element_22", + "id": -33 + }, + { + "name": "minecraft:element_23", + "id": -34 + }, + { + "name": "minecraft:element_24", + "id": -35 + }, + { + "name": "minecraft:element_25", + "id": -36 + }, + { + "name": "minecraft:element_26", + "id": -37 + }, + { + "name": "minecraft:element_27", + "id": -38 + }, + { + "name": "minecraft:element_28", + "id": -39 + }, + { + "name": "minecraft:element_29", + "id": -40 + }, + { + "name": "minecraft:element_3", + "id": -14 + }, + { + "name": "minecraft:element_30", + "id": -41 + }, + { + "name": "minecraft:element_31", + "id": -42 + }, + { + "name": "minecraft:element_32", + "id": -43 + }, + { + "name": "minecraft:element_33", + "id": -44 + }, + { + "name": "minecraft:element_34", + "id": -45 + }, + { + "name": "minecraft:element_35", + "id": -46 + }, + { + "name": "minecraft:element_36", + "id": -47 + }, + { + "name": "minecraft:element_37", + "id": -48 + }, + { + "name": "minecraft:element_38", + "id": -49 + }, + { + "name": "minecraft:element_39", + "id": -50 + }, + { + "name": "minecraft:element_4", + "id": -15 + }, + { + "name": "minecraft:element_40", + "id": -51 + }, + { + "name": "minecraft:element_41", + "id": -52 + }, + { + "name": "minecraft:element_42", + "id": -53 + }, + { + "name": "minecraft:element_43", + "id": -54 + }, + { + "name": "minecraft:element_44", + "id": -55 + }, + { + "name": "minecraft:element_45", + "id": -56 + }, + { + "name": "minecraft:element_46", + "id": -57 + }, + { + "name": "minecraft:element_47", + "id": -58 + }, + { + "name": "minecraft:element_48", + "id": -59 + }, + { + "name": "minecraft:element_49", + "id": -60 + }, + { + "name": "minecraft:element_5", + "id": -16 + }, + { + "name": "minecraft:element_50", + "id": -61 + }, + { + "name": "minecraft:element_51", + "id": -62 + }, + { + "name": "minecraft:element_52", + "id": -63 + }, + { + "name": "minecraft:element_53", + "id": -64 + }, + { + "name": "minecraft:element_54", + "id": -65 + }, + { + "name": "minecraft:element_55", + "id": -66 + }, + { + "name": "minecraft:element_56", + "id": -67 + }, + { + "name": "minecraft:element_57", + "id": -68 + }, + { + "name": "minecraft:element_58", + "id": -69 + }, + { + "name": "minecraft:element_59", + "id": -70 + }, + { + "name": "minecraft:element_6", + "id": -17 + }, + { + "name": "minecraft:element_60", + "id": -71 + }, + { + "name": "minecraft:element_61", + "id": -72 + }, + { + "name": "minecraft:element_62", + "id": -73 + }, + { + "name": "minecraft:element_63", + "id": -74 + }, + { + "name": "minecraft:element_64", + "id": -75 + }, + { + "name": "minecraft:element_65", + "id": -76 + }, + { + "name": "minecraft:element_66", + "id": -77 + }, + { + "name": "minecraft:element_67", + "id": -78 + }, + { + "name": "minecraft:element_68", + "id": -79 + }, + { + "name": "minecraft:element_69", + "id": -80 + }, + { + "name": "minecraft:element_7", + "id": -18 + }, + { + "name": "minecraft:element_70", + "id": -81 + }, + { + "name": "minecraft:element_71", + "id": -82 + }, + { + "name": "minecraft:element_72", + "id": -83 + }, + { + "name": "minecraft:element_73", + "id": -84 + }, + { + "name": "minecraft:element_74", + "id": -85 + }, + { + "name": "minecraft:element_75", + "id": -86 + }, + { + "name": "minecraft:element_76", + "id": -87 + }, + { + "name": "minecraft:element_77", + "id": -88 + }, + { + "name": "minecraft:element_78", + "id": -89 + }, + { + "name": "minecraft:element_79", + "id": -90 + }, + { + "name": "minecraft:element_8", + "id": -19 + }, + { + "name": "minecraft:element_80", + "id": -91 + }, + { + "name": "minecraft:element_81", + "id": -92 + }, + { + "name": "minecraft:element_82", + "id": -93 + }, + { + "name": "minecraft:element_83", + "id": -94 + }, + { + "name": "minecraft:element_84", + "id": -95 + }, + { + "name": "minecraft:element_85", + "id": -96 + }, + { + "name": "minecraft:element_86", + "id": -97 + }, + { + "name": "minecraft:element_87", + "id": -98 + }, + { + "name": "minecraft:element_88", + "id": -99 + }, + { + "name": "minecraft:element_89", + "id": -100 + }, + { + "name": "minecraft:element_9", + "id": -20 + }, + { + "name": "minecraft:element_90", + "id": -101 + }, + { + "name": "minecraft:element_91", + "id": -102 + }, + { + "name": "minecraft:element_92", + "id": -103 + }, + { + "name": "minecraft:element_93", + "id": -104 + }, + { + "name": "minecraft:element_94", + "id": -105 + }, + { + "name": "minecraft:element_95", + "id": -106 + }, + { + "name": "minecraft:element_96", + "id": -107 + }, + { + "name": "minecraft:element_97", + "id": -108 + }, + { + "name": "minecraft:element_98", + "id": -109 + }, + { + "name": "minecraft:element_99", + "id": -110 + }, + { + "name": "minecraft:elytra", + "id": 581 + }, + { + "name": "minecraft:emerald", + "id": 529 + }, + { + "name": "minecraft:emerald_block", + "id": 133 + }, + { + "name": "minecraft:emerald_ore", + "id": 129 + }, + { + "name": "minecraft:empty_map", + "id": 532 + }, + { + "name": "minecraft:enchanted_book", + "id": 538 + }, + { + "name": "minecraft:enchanted_golden_apple", + "id": 264 + }, + { + "name": "minecraft:enchanting_table", + "id": 116 + }, + { + "name": "minecraft:end_brick_stairs", + "id": -178 + }, + { + "name": "minecraft:end_bricks", + "id": 206 + }, + { + "name": "minecraft:end_crystal", + "id": 769 + }, + { + "name": "minecraft:end_gateway", + "id": 209 + }, + { + "name": "minecraft:end_portal", + "id": 119 + }, + { + "name": "minecraft:end_portal_frame", + "id": 120 + }, + { + "name": "minecraft:end_rod", + "id": 208 + }, + { + "name": "minecraft:end_stone", + "id": 121 + }, + { + "name": "minecraft:end_stone_brick_double_slab", + "id": -167 + }, + { + "name": "minecraft:end_stone_brick_slab", + "id": -162 + }, + { + "name": "minecraft:ender_chest", + "id": 130 + }, + { + "name": "minecraft:ender_dragon_spawn_egg", + "id": 518 + }, + { + "name": "minecraft:ender_eye", + "id": 443 + }, + { + "name": "minecraft:ender_pearl", + "id": 431 + }, + { + "name": "minecraft:enderman_spawn_egg", + "id": 452 + }, + { + "name": "minecraft:endermite_spawn_egg", + "id": 470 + }, + { + "name": "minecraft:evoker_spawn_egg", + "id": 486 + }, + { + "name": "minecraft:experience_bottle", + "id": 525 + }, + { + "name": "minecraft:explorer_pottery_sherd", + "id": 683 + }, + { + "name": "minecraft:exposed_chiseled_copper", + "id": -761 + }, + { + "name": "minecraft:exposed_copper", + "id": -341 + }, + { + "name": "minecraft:exposed_copper_bulb", + "id": -777 + }, + { + "name": "minecraft:exposed_copper_door", + "id": -785 + }, + { + "name": "minecraft:exposed_copper_grate", + "id": -769 + }, + { + "name": "minecraft:exposed_copper_trapdoor", + "id": -793 + }, + { + "name": "minecraft:exposed_cut_copper", + "id": -348 + }, + { + "name": "minecraft:exposed_cut_copper_slab", + "id": -362 + }, + { + "name": "minecraft:exposed_cut_copper_stairs", + "id": -355 + }, + { + "name": "minecraft:exposed_double_cut_copper_slab", + "id": -369 + }, + { + "name": "minecraft:eye_armor_trim_smithing_template", + "id": 706 + }, + { + "name": "minecraft:farmland", + "id": 60 + }, + { + "name": "minecraft:feather", + "id": 335 + }, + { + "name": "minecraft:fence", + "id": 729 + }, + { + "name": "minecraft:fence_gate", + "id": 107 + }, + { + "name": "minecraft:fermented_spider_eye", + "id": 438 + }, + { + "name": "minecraft:fern", + "id": -848 + }, + { + "name": "minecraft:field_masoned_banner_pattern", + "id": 602 + }, + { + "name": "minecraft:filled_map", + "id": 429 + }, + { + "name": "minecraft:fire", + "id": 51 + }, + { + "name": "minecraft:fire_charge", + "id": 526 + }, + { + "name": "minecraft:fire_coral", + "id": -583 + }, + { + "name": "minecraft:fire_coral_block", + "id": -851 + }, + { + "name": "minecraft:fire_coral_fan", + "id": -842 + }, + { + "name": "minecraft:fire_coral_wall_fan", + "id": -907 + }, + { + "name": "minecraft:firework_rocket", + "id": 536 + }, + { + "name": "minecraft:firework_star", + "id": 537 + }, + { + "name": "minecraft:fishing_rod", + "id": 401 + }, + { + "name": "minecraft:fletching_table", + "id": -201 + }, + { + "name": "minecraft:flint", + "id": 364 + }, + { + "name": "minecraft:flint_and_steel", + "id": 306 + }, + { + "name": "minecraft:flow_armor_trim_smithing_template", + "id": 717 + }, + { + "name": "minecraft:flow_banner_pattern", + "id": 606 + }, + { + "name": "minecraft:flow_pottery_sherd", + "id": 684 + }, + { + "name": "minecraft:flower_banner_pattern", + "id": 598 + }, + { + "name": "minecraft:flower_pot", + "id": 531 + }, + { + "name": "minecraft:flowering_azalea", + "id": -338 + }, + { + "name": "minecraft:flowing_lava", + "id": 10 + }, + { + "name": "minecraft:flowing_water", + "id": 8 + }, + { + "name": "minecraft:fox_spawn_egg", + "id": 501 + }, + { + "name": "minecraft:frame", + "id": 530 + }, + { + "name": "minecraft:friend_pottery_sherd", + "id": 685 + }, + { + "name": "minecraft:frog_spawn", + "id": -468 + }, + { + "name": "minecraft:frog_spawn_egg", + "id": 648 + }, + { + "name": "minecraft:frosted_ice", + "id": 207 + }, + { + "name": "minecraft:furnace", + "id": 61 + }, + { + "name": "minecraft:ghast_spawn_egg", + "id": 464 + }, + { + "name": "minecraft:ghast_tear", + "id": 434 + }, + { + "name": "minecraft:gilded_blackstone", + "id": -281 + }, + { + "name": "minecraft:glass", + "id": 20 + }, + { + "name": "minecraft:glass_bottle", + "id": 437 + }, + { + "name": "minecraft:glass_pane", + "id": 102 + }, + { + "name": "minecraft:glistering_melon_slice", + "id": 444 + }, + { + "name": "minecraft:globe_banner_pattern", + "id": 605 + }, + { + "name": "minecraft:glow_berries", + "id": 770 + }, + { + "name": "minecraft:glow_frame", + "id": 643 + }, + { + "name": "minecraft:glow_ink_sac", + "id": 520 + }, + { + "name": "minecraft:glow_lichen", + "id": -411 + }, + { + "name": "minecraft:glow_squid_spawn_egg", + "id": 515 + }, + { + "name": "minecraft:glow_stick", + "id": 621 + }, + { + "name": "minecraft:glowingobsidian", + "id": 246 + }, + { + "name": "minecraft:glowstone", + "id": 89 + }, + { + "name": "minecraft:glowstone_dust", + "id": 403 + }, + { + "name": "minecraft:goat_horn", + "id": 647 + }, + { + "name": "minecraft:goat_spawn_egg", + "id": 514 + }, + { + "name": "minecraft:gold_block", + "id": 41 + }, + { + "name": "minecraft:gold_ingot", + "id": 313 + }, + { + "name": "minecraft:gold_nugget", + "id": 435 + }, + { + "name": "minecraft:gold_ore", + "id": 14 + }, + { + "name": "minecraft:golden_apple", + "id": 263 + }, + { + "name": "minecraft:golden_axe", + "id": 333 + }, + { + "name": "minecraft:golden_boots", + "id": 362 + }, + { + "name": "minecraft:golden_carrot", + "id": 288 + }, + { + "name": "minecraft:golden_chestplate", + "id": 360 + }, + { + "name": "minecraft:golden_helmet", + "id": 359 + }, + { + "name": "minecraft:golden_hoe", + "id": 341 + }, + { + "name": "minecraft:golden_horse_armor", + "id": 549 + }, + { + "name": "minecraft:golden_leggings", + "id": 361 + }, + { + "name": "minecraft:golden_pickaxe", + "id": 332 + }, + { + "name": "minecraft:golden_rail", + "id": 27 + }, + { + "name": "minecraft:golden_shovel", + "id": 331 + }, + { + "name": "minecraft:golden_sword", + "id": 330 + }, + { + "name": "minecraft:granite", + "id": -590 + }, + { + "name": "minecraft:granite_double_slab", + "id": -923 + }, + { + "name": "minecraft:granite_slab", + "id": -896 + }, + { + "name": "minecraft:granite_stairs", + "id": -169 + }, + { + "name": "minecraft:grass_block", + "id": 2 + }, + { + "name": "minecraft:grass_path", + "id": 198 + }, + { + "name": "minecraft:gravel", + "id": 13 + }, + { + "name": "minecraft:gray_candle", + "id": -420 + }, + { + "name": "minecraft:gray_candle_cake", + "id": -437 + }, + { + "name": "minecraft:gray_carpet", + "id": -603 + }, + { + "name": "minecraft:gray_concrete", + "id": -634 + }, + { + "name": "minecraft:gray_concrete_powder", + "id": -715 + }, + { + "name": "minecraft:gray_dye", + "id": 412 + }, + { + "name": "minecraft:gray_glazed_terracotta", + "id": 227 + }, + { + "name": "minecraft:gray_shulker_box", + "id": -619 + }, + { + "name": "minecraft:gray_stained_glass", + "id": -679 + }, + { + "name": "minecraft:gray_stained_glass_pane", + "id": -649 + }, + { + "name": "minecraft:gray_terracotta", + "id": -730 + }, + { + "name": "minecraft:gray_wool", + "id": -553 + }, + { + "name": "minecraft:green_candle", + "id": -426 + }, + { + "name": "minecraft:green_candle_cake", + "id": -443 + }, + { + "name": "minecraft:green_carpet", + "id": -609 + }, + { + "name": "minecraft:green_concrete", + "id": -640 + }, + { + "name": "minecraft:green_concrete_powder", + "id": -721 + }, + { + "name": "minecraft:green_dye", + "id": 406 + }, + { + "name": "minecraft:green_glazed_terracotta", + "id": 233 + }, + { + "name": "minecraft:green_shulker_box", + "id": -625 + }, + { + "name": "minecraft:green_stained_glass", + "id": -685 + }, + { + "name": "minecraft:green_stained_glass_pane", + "id": -655 + }, + { + "name": "minecraft:green_terracotta", + "id": -736 + }, + { + "name": "minecraft:green_wool", + "id": -560 + }, + { + "name": "minecraft:grindstone", + "id": -195 + }, + { + "name": "minecraft:guardian_spawn_egg", + "id": 471 + }, + { + "name": "minecraft:gunpowder", + "id": 336 + }, + { + "name": "minecraft:guster_banner_pattern", + "id": 607 + }, + { + "name": "minecraft:guster_pottery_sherd", + "id": 686 + }, + { + "name": "minecraft:hanging_roots", + "id": -319 + }, + { + "name": "minecraft:hard_black_stained_glass", + "id": -702 + }, + { + "name": "minecraft:hard_black_stained_glass_pane", + "id": -672 + }, + { + "name": "minecraft:hard_blue_stained_glass", + "id": -698 + }, + { + "name": "minecraft:hard_blue_stained_glass_pane", + "id": -668 + }, + { + "name": "minecraft:hard_brown_stained_glass", + "id": -699 + }, + { + "name": "minecraft:hard_brown_stained_glass_pane", + "id": -669 + }, + { + "name": "minecraft:hard_cyan_stained_glass", + "id": -696 + }, + { + "name": "minecraft:hard_cyan_stained_glass_pane", + "id": -666 + }, + { + "name": "minecraft:hard_glass", + "id": 253 + }, + { + "name": "minecraft:hard_glass_pane", + "id": 190 + }, + { + "name": "minecraft:hard_gray_stained_glass", + "id": -694 + }, + { + "name": "minecraft:hard_gray_stained_glass_pane", + "id": -664 + }, + { + "name": "minecraft:hard_green_stained_glass", + "id": -700 + }, + { + "name": "minecraft:hard_green_stained_glass_pane", + "id": -670 + }, + { + "name": "minecraft:hard_light_blue_stained_glass", + "id": -690 + }, + { + "name": "minecraft:hard_light_blue_stained_glass_pane", + "id": -660 + }, + { + "name": "minecraft:hard_light_gray_stained_glass", + "id": -695 + }, + { + "name": "minecraft:hard_light_gray_stained_glass_pane", + "id": -665 + }, + { + "name": "minecraft:hard_lime_stained_glass", + "id": -692 + }, + { + "name": "minecraft:hard_lime_stained_glass_pane", + "id": -662 + }, + { + "name": "minecraft:hard_magenta_stained_glass", + "id": -689 + }, + { + "name": "minecraft:hard_magenta_stained_glass_pane", + "id": -659 + }, + { + "name": "minecraft:hard_orange_stained_glass", + "id": -688 + }, + { + "name": "minecraft:hard_orange_stained_glass_pane", + "id": -658 + }, + { + "name": "minecraft:hard_pink_stained_glass", + "id": -693 + }, + { + "name": "minecraft:hard_pink_stained_glass_pane", + "id": -663 + }, + { + "name": "minecraft:hard_purple_stained_glass", + "id": -697 + }, + { + "name": "minecraft:hard_purple_stained_glass_pane", + "id": -667 + }, + { + "name": "minecraft:hard_red_stained_glass", + "id": -701 + }, + { + "name": "minecraft:hard_red_stained_glass_pane", + "id": -671 + }, + { + "name": "minecraft:hard_stained_glass", + "id": 762 + }, + { + "name": "minecraft:hard_stained_glass_pane", + "id": 763 + }, + { + "name": "minecraft:hard_white_stained_glass", + "id": 254 + }, + { + "name": "minecraft:hard_white_stained_glass_pane", + "id": 191 + }, + { + "name": "minecraft:hard_yellow_stained_glass", + "id": -691 + }, + { + "name": "minecraft:hard_yellow_stained_glass_pane", + "id": -661 + }, + { + "name": "minecraft:hardened_clay", + "id": 172 + }, + { + "name": "minecraft:hay_block", + "id": 170 + }, + { + "name": "minecraft:heart_of_the_sea", + "id": 588 + }, + { + "name": "minecraft:heart_pottery_sherd", + "id": 687 + }, + { + "name": "minecraft:heartbreak_pottery_sherd", + "id": 688 + }, + { + "name": "minecraft:heavy_core", + "id": -316 + }, + { + "name": "minecraft:heavy_weighted_pressure_plate", + "id": 148 + }, + { + "name": "minecraft:hoglin_spawn_egg", + "id": 507 + }, + { + "name": "minecraft:honey_block", + "id": -220 + }, + { + "name": "minecraft:honey_bottle", + "id": 611 + }, + { + "name": "minecraft:honeycomb", + "id": 610 + }, + { + "name": "minecraft:honeycomb_block", + "id": -221 + }, + { + "name": "minecraft:hopper", + "id": 544 + }, + { + "name": "minecraft:hopper_minecart", + "id": 543 + }, + { + "name": "minecraft:horn_coral", + "id": -584 + }, + { + "name": "minecraft:horn_coral_block", + "id": -852 + }, + { + "name": "minecraft:horn_coral_fan", + "id": -843 + }, + { + "name": "minecraft:horn_coral_wall_fan", + "id": -137 + }, + { + "name": "minecraft:horse_spawn_egg", + "id": 468 + }, + { + "name": "minecraft:host_armor_trim_smithing_template", + "id": 716 + }, + { + "name": "minecraft:howl_pottery_sherd", + "id": 689 + }, + { + "name": "minecraft:husk_spawn_egg", + "id": 474 + }, + { + "name": "minecraft:ice", + "id": 79 + }, + { + "name": "minecraft:ice_bomb", + "id": 615 + }, + { + "name": "minecraft:infested_chiseled_stone_bricks", + "id": -862 + }, + { + "name": "minecraft:infested_cobblestone", + "id": -858 + }, + { + "name": "minecraft:infested_cracked_stone_bricks", + "id": -861 + }, + { + "name": "minecraft:infested_deepslate", + "id": -454 + }, + { + "name": "minecraft:infested_mossy_stone_bricks", + "id": -860 + }, + { + "name": "minecraft:infested_stone", + "id": 97 + }, + { + "name": "minecraft:infested_stone_bricks", + "id": -859 + }, + { + "name": "minecraft:info_update", + "id": 248 + }, + { + "name": "minecraft:info_update2", + "id": 249 + }, + { + "name": "minecraft:ink_sac", + "id": 422 + }, + { + "name": "minecraft:invisible_bedrock", + "id": 95 + }, + { + "name": "minecraft:iron_axe", + "id": 305 + }, + { + "name": "minecraft:iron_bars", + "id": 101 + }, + { + "name": "minecraft:iron_block", + "id": 42 + }, + { + "name": "minecraft:iron_boots", + "id": 354 + }, + { + "name": "minecraft:iron_chestplate", + "id": 352 + }, + { + "name": "minecraft:iron_door", + "id": 380 + }, + { + "name": "minecraft:iron_golem_spawn_egg", + "id": 516 + }, + { + "name": "minecraft:iron_helmet", + "id": 351 + }, + { + "name": "minecraft:iron_hoe", + "id": 339 + }, + { + "name": "minecraft:iron_horse_armor", + "id": 548 + }, + { + "name": "minecraft:iron_ingot", + "id": 312 + }, + { + "name": "minecraft:iron_leggings", + "id": 353 + }, + { + "name": "minecraft:iron_nugget", + "id": 586 + }, + { + "name": "minecraft:iron_ore", + "id": 15 + }, + { + "name": "minecraft:iron_pickaxe", + "id": 304 + }, + { + "name": "minecraft:iron_shovel", + "id": 303 + }, + { + "name": "minecraft:iron_sword", + "id": 314 + }, + { + "name": "minecraft:iron_trapdoor", + "id": 167 + }, + { + "name": "minecraft:item.acacia_door", + "id": 196 + }, + { + "name": "minecraft:item.bed", + "id": 26 + }, + { + "name": "minecraft:item.beetroot", + "id": 244 + }, + { + "name": "minecraft:item.birch_door", + "id": 194 + }, + { + "name": "minecraft:item.brewing_stand", + "id": 117 + }, + { + "name": "minecraft:item.cake", + "id": 92 + }, + { + "name": "minecraft:item.camera", + "id": 242 + }, + { + "name": "minecraft:item.campfire", + "id": -209 + }, + { + "name": "minecraft:item.cauldron", + "id": 118 + }, + { + "name": "minecraft:item.chain", + "id": -286 + }, + { + "name": "minecraft:item.crimson_door", + "id": -244 + }, + { + "name": "minecraft:item.dark_oak_door", + "id": 197 + }, + { + "name": "minecraft:item.flower_pot", + "id": 140 + }, + { + "name": "minecraft:item.frame", + "id": 199 + }, + { + "name": "minecraft:item.glow_frame", + "id": -339 + }, + { + "name": "minecraft:item.hopper", + "id": 154 + }, + { + "name": "minecraft:item.iron_door", + "id": 71 + }, + { + "name": "minecraft:item.jungle_door", + "id": 195 + }, + { + "name": "minecraft:item.kelp", + "id": -138 + }, + { + "name": "minecraft:item.mangrove_door", + "id": -493 + }, + { + "name": "minecraft:item.nether_sprouts", + "id": -238 + }, + { + "name": "minecraft:item.nether_wart", + "id": 115 + }, + { + "name": "minecraft:item.reeds", + "id": 83 + }, + { + "name": "minecraft:item.skull", + "id": 144 + }, + { + "name": "minecraft:item.soul_campfire", + "id": -290 + }, + { + "name": "minecraft:item.spruce_door", + "id": 193 + }, + { + "name": "minecraft:item.warped_door", + "id": -245 + }, + { + "name": "minecraft:item.wheat", + "id": 59 + }, + { + "name": "minecraft:item.wooden_door", + "id": 64 + }, + { + "name": "minecraft:jigsaw", + "id": -211 + }, + { + "name": "minecraft:jukebox", + "id": 84 + }, + { + "name": "minecraft:jungle_boat", + "id": 386 + }, + { + "name": "minecraft:jungle_button", + "id": -143 + }, + { + "name": "minecraft:jungle_chest_boat", + "id": 660 + }, + { + "name": "minecraft:jungle_door", + "id": 572 + }, + { + "name": "minecraft:jungle_double_slab", + "id": -811 + }, + { + "name": "minecraft:jungle_fence", + "id": -578 + }, + { + "name": "minecraft:jungle_fence_gate", + "id": 185 + }, + { + "name": "minecraft:jungle_hanging_sign", + "id": -503 + }, + { + "name": "minecraft:jungle_leaves", + "id": -802 + }, + { + "name": "minecraft:jungle_log", + "id": -571 + }, + { + "name": "minecraft:jungle_planks", + "id": -741 + }, + { + "name": "minecraft:jungle_pressure_plate", + "id": -153 + }, + { + "name": "minecraft:jungle_sapling", + "id": -827 + }, + { + "name": "minecraft:jungle_sign", + "id": 595 + }, + { + "name": "minecraft:jungle_slab", + "id": -806 + }, + { + "name": "minecraft:jungle_stairs", + "id": 136 + }, + { + "name": "minecraft:jungle_standing_sign", + "id": -188 + }, + { + "name": "minecraft:jungle_trapdoor", + "id": -148 + }, + { + "name": "minecraft:jungle_wall_sign", + "id": -189 + }, + { + "name": "minecraft:jungle_wood", + "id": -816 + }, + { + "name": "minecraft:kelp", + "id": 391 + }, + { + "name": "minecraft:ladder", + "id": 65 + }, + { + "name": "minecraft:lantern", + "id": -208 + }, + { + "name": "minecraft:lapis_block", + "id": 22 + }, + { + "name": "minecraft:lapis_lazuli", + "id": 423 + }, + { + "name": "minecraft:lapis_ore", + "id": 21 + }, + { + "name": "minecraft:large_amethyst_bud", + "id": -330 + }, + { + "name": "minecraft:large_fern", + "id": -865 + }, + { + "name": "minecraft:lava", + "id": 11 + }, + { + "name": "minecraft:lava_bucket", + "id": 371 + }, + { + "name": "minecraft:lead", + "id": 564 + }, + { + "name": "minecraft:leather", + "id": 390 + }, + { + "name": "minecraft:leather_boots", + "id": 346 + }, + { + "name": "minecraft:leather_chestplate", + "id": 344 + }, + { + "name": "minecraft:leather_helmet", + "id": 343 + }, + { + "name": "minecraft:leather_horse_armor", + "id": 547 + }, + { + "name": "minecraft:leather_leggings", + "id": 345 + }, + { + "name": "minecraft:leaves", + "id": 743 + }, + { + "name": "minecraft:leaves2", + "id": 744 + }, + { + "name": "minecraft:lectern", + "id": -194 + }, + { + "name": "minecraft:lever", + "id": 69 + }, + { + "name": "minecraft:light_block", + "id": 764 + }, + { + "name": "minecraft:light_block_0", + "id": -215 + }, + { + "name": "minecraft:light_block_1", + "id": -929 + }, + { + "name": "minecraft:light_block_10", + "id": -938 + }, + { + "name": "minecraft:light_block_11", + "id": -939 + }, + { + "name": "minecraft:light_block_12", + "id": -940 + }, + { + "name": "minecraft:light_block_13", + "id": -941 + }, + { + "name": "minecraft:light_block_14", + "id": -942 + }, + { + "name": "minecraft:light_block_15", + "id": -943 + }, + { + "name": "minecraft:light_block_2", + "id": -930 + }, + { + "name": "minecraft:light_block_3", + "id": -931 + }, + { + "name": "minecraft:light_block_4", + "id": -932 + }, + { + "name": "minecraft:light_block_5", + "id": -933 + }, + { + "name": "minecraft:light_block_6", + "id": -934 + }, + { + "name": "minecraft:light_block_7", + "id": -935 + }, + { + "name": "minecraft:light_block_8", + "id": -936 + }, + { + "name": "minecraft:light_block_9", + "id": -937 + }, + { + "name": "minecraft:light_blue_candle", + "id": -416 + }, + { + "name": "minecraft:light_blue_candle_cake", + "id": -433 + }, + { + "name": "minecraft:light_blue_carpet", + "id": -599 + }, + { + "name": "minecraft:light_blue_concrete", + "id": -630 + }, + { + "name": "minecraft:light_blue_concrete_powder", + "id": -711 + }, + { + "name": "minecraft:light_blue_dye", + "id": 416 + }, + { + "name": "minecraft:light_blue_glazed_terracotta", + "id": 223 + }, + { + "name": "minecraft:light_blue_shulker_box", + "id": -615 + }, + { + "name": "minecraft:light_blue_stained_glass", + "id": -675 + }, + { + "name": "minecraft:light_blue_stained_glass_pane", + "id": -645 + }, + { + "name": "minecraft:light_blue_terracotta", + "id": -726 + }, + { + "name": "minecraft:light_blue_wool", + "id": -562 + }, + { + "name": "minecraft:light_gray_candle", + "id": -421 + }, + { + "name": "minecraft:light_gray_candle_cake", + "id": -438 + }, + { + "name": "minecraft:light_gray_carpet", + "id": -604 + }, + { + "name": "minecraft:light_gray_concrete", + "id": -635 + }, + { + "name": "minecraft:light_gray_concrete_powder", + "id": -716 + }, + { + "name": "minecraft:light_gray_dye", + "id": 411 + }, + { + "name": "minecraft:light_gray_shulker_box", + "id": -620 + }, + { + "name": "minecraft:light_gray_stained_glass", + "id": -680 + }, + { + "name": "minecraft:light_gray_stained_glass_pane", + "id": -650 + }, + { + "name": "minecraft:light_gray_terracotta", + "id": -731 + }, + { + "name": "minecraft:light_gray_wool", + "id": -552 + }, + { + "name": "minecraft:light_weighted_pressure_plate", + "id": 147 + }, + { + "name": "minecraft:lightning_rod", + "id": -312 + }, + { + "name": "minecraft:lilac", + "id": -863 + }, + { + "name": "minecraft:lily_of_the_valley", + "id": -839 + }, + { + "name": "minecraft:lime_candle", + "id": -418 + }, + { + "name": "minecraft:lime_candle_cake", + "id": -435 + }, + { + "name": "minecraft:lime_carpet", + "id": -601 + }, + { + "name": "minecraft:lime_concrete", + "id": -632 + }, + { + "name": "minecraft:lime_concrete_powder", + "id": -713 + }, + { + "name": "minecraft:lime_dye", + "id": 414 + }, + { + "name": "minecraft:lime_glazed_terracotta", + "id": 225 + }, + { + "name": "minecraft:lime_shulker_box", + "id": -617 + }, + { + "name": "minecraft:lime_stained_glass", + "id": -677 + }, + { + "name": "minecraft:lime_stained_glass_pane", + "id": -647 + }, + { + "name": "minecraft:lime_terracotta", + "id": -728 + }, + { + "name": "minecraft:lime_wool", + "id": -559 + }, + { + "name": "minecraft:lingering_potion", + "id": 579 + }, + { + "name": "minecraft:lit_blast_furnace", + "id": -214 + }, + { + "name": "minecraft:lit_deepslate_redstone_ore", + "id": -404 + }, + { + "name": "minecraft:lit_furnace", + "id": 62 + }, + { + "name": "minecraft:lit_pumpkin", + "id": 91 + }, + { + "name": "minecraft:lit_redstone_lamp", + "id": 124 + }, + { + "name": "minecraft:lit_redstone_ore", + "id": 74 + }, + { + "name": "minecraft:lit_smoker", + "id": -199 + }, + { + "name": "minecraft:llama_spawn_egg", + "id": 484 + }, + { + "name": "minecraft:lodestone", + "id": -222 + }, + { + "name": "minecraft:lodestone_compass", + "id": 622 + }, + { + "name": "minecraft:log", + "id": 728 + }, + { + "name": "minecraft:log2", + "id": 751 + }, + { + "name": "minecraft:loom", + "id": -204 + }, + { + "name": "minecraft:mace", + "id": 327 + }, + { + "name": "minecraft:magenta_candle", + "id": -415 + }, + { + "name": "minecraft:magenta_candle_cake", + "id": -432 + }, + { + "name": "minecraft:magenta_carpet", + "id": -598 + }, + { + "name": "minecraft:magenta_concrete", + "id": -629 + }, + { + "name": "minecraft:magenta_concrete_powder", + "id": -710 + }, + { + "name": "minecraft:magenta_dye", + "id": 417 + }, + { + "name": "minecraft:magenta_glazed_terracotta", + "id": 222 + }, + { + "name": "minecraft:magenta_shulker_box", + "id": -614 + }, + { + "name": "minecraft:magenta_stained_glass", + "id": -674 + }, + { + "name": "minecraft:magenta_stained_glass_pane", + "id": -644 + }, + { + "name": "minecraft:magenta_terracotta", + "id": -725 + }, + { + "name": "minecraft:magenta_wool", + "id": -565 + }, + { + "name": "minecraft:magma", + "id": 213 + }, + { + "name": "minecraft:magma_cream", + "id": 440 + }, + { + "name": "minecraft:magma_cube_spawn_egg", + "id": 465 + }, + { + "name": "minecraft:mangrove_boat", + "id": 655 + }, + { + "name": "minecraft:mangrove_button", + "id": -487 + }, + { + "name": "minecraft:mangrove_chest_boat", + "id": 664 + }, + { + "name": "minecraft:mangrove_door", + "id": 653 + }, + { + "name": "minecraft:mangrove_double_slab", + "id": -499 + }, + { + "name": "minecraft:mangrove_fence", + "id": -491 + }, + { + "name": "minecraft:mangrove_fence_gate", + "id": -492 + }, + { + "name": "minecraft:mangrove_hanging_sign", + "id": -508 + }, + { + "name": "minecraft:mangrove_leaves", + "id": -472 + }, + { + "name": "minecraft:mangrove_log", + "id": -484 + }, + { + "name": "minecraft:mangrove_planks", + "id": -486 + }, + { + "name": "minecraft:mangrove_pressure_plate", + "id": -490 + }, + { + "name": "minecraft:mangrove_propagule", + "id": -474 + }, + { + "name": "minecraft:mangrove_roots", + "id": -482 + }, + { + "name": "minecraft:mangrove_sign", + "id": 654 + }, + { + "name": "minecraft:mangrove_slab", + "id": -489 + }, + { + "name": "minecraft:mangrove_stairs", + "id": -488 + }, + { + "name": "minecraft:mangrove_standing_sign", + "id": -494 + }, + { + "name": "minecraft:mangrove_trapdoor", + "id": -496 + }, + { + "name": "minecraft:mangrove_wall_sign", + "id": -495 + }, + { + "name": "minecraft:mangrove_wood", + "id": -497 + }, + { + "name": "minecraft:medicine", + "id": 619 + }, + { + "name": "minecraft:medium_amethyst_bud", + "id": -331 + }, + { + "name": "minecraft:melon_block", + "id": 103 + }, + { + "name": "minecraft:melon_seeds", + "id": 298 + }, + { + "name": "minecraft:melon_slice", + "id": 277 + }, + { + "name": "minecraft:melon_stem", + "id": 105 + }, + { + "name": "minecraft:milk_bucket", + "id": 369 + }, + { + "name": "minecraft:minecart", + "id": 378 + }, + { + "name": "minecraft:miner_pottery_sherd", + "id": 690 + }, + { + "name": "minecraft:mob_spawner", + "id": 52 + }, + { + "name": "minecraft:mojang_banner_pattern", + "id": 601 + }, + { + "name": "minecraft:monster_egg", + "id": 752 + }, + { + "name": "minecraft:mooshroom_spawn_egg", + "id": 450 + }, + { + "name": "minecraft:moss_block", + "id": -320 + }, + { + "name": "minecraft:moss_carpet", + "id": -335 + }, + { + "name": "minecraft:mossy_cobblestone", + "id": 48 + }, + { + "name": "minecraft:mossy_cobblestone_double_slab", + "id": -915 + }, + { + "name": "minecraft:mossy_cobblestone_slab", + "id": -888 + }, + { + "name": "minecraft:mossy_cobblestone_stairs", + "id": -179 + }, + { + "name": "minecraft:mossy_stone_brick_double_slab", + "id": -168 + }, + { + "name": "minecraft:mossy_stone_brick_slab", + "id": -166 + }, + { + "name": "minecraft:mossy_stone_brick_stairs", + "id": -175 + }, + { + "name": "minecraft:mossy_stone_bricks", + "id": -868 + }, + { + "name": "minecraft:mourner_pottery_sherd", + "id": 691 + }, + { + "name": "minecraft:moving_block", + "id": 250 + }, + { + "name": "minecraft:mud", + "id": -473 + }, + { + "name": "minecraft:mud_brick_double_slab", + "id": -479 + }, + { + "name": "minecraft:mud_brick_slab", + "id": -478 + }, + { + "name": "minecraft:mud_brick_stairs", + "id": -480 + }, + { + "name": "minecraft:mud_brick_wall", + "id": -481 + }, + { + "name": "minecraft:mud_bricks", + "id": -475 + }, + { + "name": "minecraft:muddy_mangrove_roots", + "id": -483 + }, + { + "name": "minecraft:mule_spawn_egg", + "id": 477 + }, + { + "name": "minecraft:mushroom_stew", + "id": 265 + }, + { + "name": "minecraft:music_disc_11", + "id": 561 + }, + { + "name": "minecraft:music_disc_13", + "id": 551 + }, + { + "name": "minecraft:music_disc_5", + "id": 656 + }, + { + "name": "minecraft:music_disc_blocks", + "id": 553 + }, + { + "name": "minecraft:music_disc_cat", + "id": 552 + }, + { + "name": "minecraft:music_disc_chirp", + "id": 554 + }, + { + "name": "minecraft:music_disc_creator", + "id": 759 + }, + { + "name": "minecraft:music_disc_creator_music_box", + "id": 760 + }, + { + "name": "minecraft:music_disc_far", + "id": 555 + }, + { + "name": "minecraft:music_disc_mall", + "id": 556 + }, + { + "name": "minecraft:music_disc_mellohi", + "id": 557 + }, + { + "name": "minecraft:music_disc_otherside", + "id": 646 + }, + { + "name": "minecraft:music_disc_pigstep", + "id": 640 + }, + { + "name": "minecraft:music_disc_precipice", + "id": 761 + }, + { + "name": "minecraft:music_disc_relic", + "id": 719 + }, + { + "name": "minecraft:music_disc_stal", + "id": 558 + }, + { + "name": "minecraft:music_disc_strad", + "id": 559 + }, + { + "name": "minecraft:music_disc_wait", + "id": 562 + }, + { + "name": "minecraft:music_disc_ward", + "id": 560 + }, + { + "name": "minecraft:mutton", + "id": 567 + }, + { + "name": "minecraft:mycelium", + "id": 110 + }, + { + "name": "minecraft:name_tag", + "id": 565 + }, + { + "name": "minecraft:nautilus_shell", + "id": 587 + }, + { + "name": "minecraft:nether_brick", + "id": 112 + }, + { + "name": "minecraft:nether_brick_double_slab", + "id": -883 + }, + { + "name": "minecraft:nether_brick_fence", + "id": 113 + }, + { + "name": "minecraft:nether_brick_slab", + "id": -877 + }, + { + "name": "minecraft:nether_brick_stairs", + "id": 114 + }, + { + "name": "minecraft:nether_gold_ore", + "id": -288 + }, + { + "name": "minecraft:nether_sprouts", + "id": 641 + }, + { + "name": "minecraft:nether_star", + "id": 535 + }, + { + "name": "minecraft:nether_wart", + "id": 299 + }, + { + "name": "minecraft:nether_wart_block", + "id": 214 + }, + { + "name": "minecraft:netherbrick", + "id": 540 + }, + { + "name": "minecraft:netherite_axe", + "id": 626 + }, + { + "name": "minecraft:netherite_block", + "id": -270 + }, + { + "name": "minecraft:netherite_boots", + "id": 632 + }, + { + "name": "minecraft:netherite_chestplate", + "id": 630 + }, + { + "name": "minecraft:netherite_helmet", + "id": 629 + }, + { + "name": "minecraft:netherite_hoe", + "id": 627 + }, + { + "name": "minecraft:netherite_ingot", + "id": 628 + }, + { + "name": "minecraft:netherite_leggings", + "id": 631 + }, + { + "name": "minecraft:netherite_pickaxe", + "id": 625 + }, + { + "name": "minecraft:netherite_scrap", + "id": 633 + }, + { + "name": "minecraft:netherite_shovel", + "id": 624 + }, + { + "name": "minecraft:netherite_sword", + "id": 623 + }, + { + "name": "minecraft:netherite_upgrade_smithing_template", + "id": 700 + }, + { + "name": "minecraft:netherrack", + "id": 87 + }, + { + "name": "minecraft:netherreactor", + "id": 247 + }, + { + "name": "minecraft:normal_stone_double_slab", + "id": -926 + }, + { + "name": "minecraft:normal_stone_slab", + "id": -899 + }, + { + "name": "minecraft:normal_stone_stairs", + "id": -180 + }, + { + "name": "minecraft:noteblock", + "id": 25 + }, + { + "name": "minecraft:npc_spawn_egg", + "id": 481 + }, + { + "name": "minecraft:oak_boat", + "id": 384 + }, + { + "name": "minecraft:oak_chest_boat", + "id": 658 + }, + { + "name": "minecraft:oak_double_slab", + "id": 157 + }, + { + "name": "minecraft:oak_fence", + "id": 85 + }, + { + "name": "minecraft:oak_hanging_sign", + "id": -500 + }, + { + "name": "minecraft:oak_leaves", + "id": 18 + }, + { + "name": "minecraft:oak_log", + "id": 17 + }, + { + "name": "minecraft:oak_planks", + "id": 5 + }, + { + "name": "minecraft:oak_sapling", + "id": 6 + }, + { + "name": "minecraft:oak_sign", + "id": 366 + }, + { + "name": "minecraft:oak_slab", + "id": 158 + }, + { + "name": "minecraft:oak_stairs", + "id": 53 + }, + { + "name": "minecraft:oak_wood", + "id": -212 + }, + { + "name": "minecraft:observer", + "id": 251 + }, + { + "name": "minecraft:obsidian", + "id": 49 + }, + { + "name": "minecraft:ocelot_spawn_egg", + "id": 461 + }, + { + "name": "minecraft:ochre_froglight", + "id": -471 + }, + { + "name": "minecraft:ominous_bottle", + "id": 612 + }, + { + "name": "minecraft:ominous_trial_key", + "id": 258 + }, + { + "name": "minecraft:orange_candle", + "id": -414 + }, + { + "name": "minecraft:orange_candle_cake", + "id": -431 + }, + { + "name": "minecraft:orange_carpet", + "id": -597 + }, + { + "name": "minecraft:orange_concrete", + "id": -628 + }, + { + "name": "minecraft:orange_concrete_powder", + "id": -709 + }, + { + "name": "minecraft:orange_dye", + "id": 418 + }, + { + "name": "minecraft:orange_glazed_terracotta", + "id": 221 + }, + { + "name": "minecraft:orange_shulker_box", + "id": -613 + }, + { + "name": "minecraft:orange_stained_glass", + "id": -673 + }, + { + "name": "minecraft:orange_stained_glass_pane", + "id": -643 + }, + { + "name": "minecraft:orange_terracotta", + "id": -724 + }, + { + "name": "minecraft:orange_tulip", + "id": -834 + }, + { + "name": "minecraft:orange_wool", + "id": -557 + }, + { + "name": "minecraft:oxeye_daisy", + "id": -837 + }, + { + "name": "minecraft:oxidized_chiseled_copper", + "id": -763 + }, + { + "name": "minecraft:oxidized_copper", + "id": -343 + }, + { + "name": "minecraft:oxidized_copper_bulb", + "id": -779 + }, + { + "name": "minecraft:oxidized_copper_door", + "id": -787 + }, + { + "name": "minecraft:oxidized_copper_grate", + "id": -771 + }, + { + "name": "minecraft:oxidized_copper_trapdoor", + "id": -795 + }, + { + "name": "minecraft:oxidized_cut_copper", + "id": -350 + }, + { + "name": "minecraft:oxidized_cut_copper_slab", + "id": -364 + }, + { + "name": "minecraft:oxidized_cut_copper_stairs", + "id": -357 + }, + { + "name": "minecraft:oxidized_double_cut_copper_slab", + "id": -371 + }, + { + "name": "minecraft:packed_ice", + "id": 174 + }, + { + "name": "minecraft:packed_mud", + "id": -477 + }, + { + "name": "minecraft:painting", + "id": 365 + }, + { + "name": "minecraft:panda_spawn_egg", + "id": 500 + }, + { + "name": "minecraft:paper", + "id": 395 + }, + { + "name": "minecraft:parrot_spawn_egg", + "id": 489 + }, + { + "name": "minecraft:pearlescent_froglight", + "id": -469 + }, + { + "name": "minecraft:peony", + "id": -867 + }, + { + "name": "minecraft:petrified_oak_double_slab", + "id": -903 + }, + { + "name": "minecraft:petrified_oak_slab", + "id": -902 + }, + { + "name": "minecraft:phantom_membrane", + "id": 591 + }, + { + "name": "minecraft:phantom_spawn_egg", + "id": 497 + }, + { + "name": "minecraft:pig_spawn_egg", + "id": 447 + }, + { + "name": "minecraft:piglin_banner_pattern", + "id": 604 + }, + { + "name": "minecraft:piglin_brute_spawn_egg", + "id": 510 + }, + { + "name": "minecraft:piglin_spawn_egg", + "id": 508 + }, + { + "name": "minecraft:pillager_spawn_egg", + "id": 502 + }, + { + "name": "minecraft:pink_candle", + "id": -419 + }, + { + "name": "minecraft:pink_candle_cake", + "id": -436 + }, + { + "name": "minecraft:pink_carpet", + "id": -602 + }, + { + "name": "minecraft:pink_concrete", + "id": -633 + }, + { + "name": "minecraft:pink_concrete_powder", + "id": -714 + }, + { + "name": "minecraft:pink_dye", + "id": 413 + }, + { + "name": "minecraft:pink_glazed_terracotta", + "id": 226 + }, + { + "name": "minecraft:pink_petals", + "id": -549 + }, + { + "name": "minecraft:pink_shulker_box", + "id": -618 + }, + { + "name": "minecraft:pink_stained_glass", + "id": -678 + }, + { + "name": "minecraft:pink_stained_glass_pane", + "id": -648 + }, + { + "name": "minecraft:pink_terracotta", + "id": -729 + }, + { + "name": "minecraft:pink_tulip", + "id": -836 + }, + { + "name": "minecraft:pink_wool", + "id": -566 + }, + { + "name": "minecraft:piston", + "id": 33 + }, + { + "name": "minecraft:piston_arm_collision", + "id": 34 + }, + { + "name": "minecraft:pitcher_crop", + "id": -574 + }, + { + "name": "minecraft:pitcher_plant", + "id": -612 + }, + { + "name": "minecraft:pitcher_pod", + "id": 302 + }, + { + "name": "minecraft:planks", + "id": 748 + }, + { + "name": "minecraft:plenty_pottery_sherd", + "id": 692 + }, + { + "name": "minecraft:podzol", + "id": 243 + }, + { + "name": "minecraft:pointed_dripstone", + "id": -308 + }, + { + "name": "minecraft:poisonous_potato", + "id": 287 + }, + { + "name": "minecraft:polar_bear_spawn_egg", + "id": 483 + }, + { + "name": "minecraft:polished_andesite", + "id": -595 + }, + { + "name": "minecraft:polished_andesite_double_slab", + "id": -919 + }, + { + "name": "minecraft:polished_andesite_slab", + "id": -892 + }, + { + "name": "minecraft:polished_andesite_stairs", + "id": -174 + }, + { + "name": "minecraft:polished_basalt", + "id": -235 + }, + { + "name": "minecraft:polished_blackstone", + "id": -291 + }, + { + "name": "minecraft:polished_blackstone_brick_double_slab", + "id": -285 + }, + { + "name": "minecraft:polished_blackstone_brick_slab", + "id": -284 + }, + { + "name": "minecraft:polished_blackstone_brick_stairs", + "id": -275 + }, + { + "name": "minecraft:polished_blackstone_brick_wall", + "id": -278 + }, + { + "name": "minecraft:polished_blackstone_bricks", + "id": -274 + }, + { + "name": "minecraft:polished_blackstone_button", + "id": -296 + }, + { + "name": "minecraft:polished_blackstone_double_slab", + "id": -294 + }, + { + "name": "minecraft:polished_blackstone_pressure_plate", + "id": -295 + }, + { + "name": "minecraft:polished_blackstone_slab", + "id": -293 + }, + { + "name": "minecraft:polished_blackstone_stairs", + "id": -292 + }, + { + "name": "minecraft:polished_blackstone_wall", + "id": -297 + }, + { + "name": "minecraft:polished_deepslate", + "id": -383 + }, + { + "name": "minecraft:polished_deepslate_double_slab", + "id": -397 + }, + { + "name": "minecraft:polished_deepslate_slab", + "id": -384 + }, + { + "name": "minecraft:polished_deepslate_stairs", + "id": -385 + }, + { + "name": "minecraft:polished_deepslate_wall", + "id": -386 + }, + { + "name": "minecraft:polished_diorite", + "id": -593 + }, + { + "name": "minecraft:polished_diorite_double_slab", + "id": -922 + }, + { + "name": "minecraft:polished_diorite_slab", + "id": -895 + }, + { + "name": "minecraft:polished_diorite_stairs", + "id": -173 + }, + { + "name": "minecraft:polished_granite", + "id": -591 + }, + { + "name": "minecraft:polished_granite_double_slab", + "id": -924 + }, + { + "name": "minecraft:polished_granite_slab", + "id": -897 + }, + { + "name": "minecraft:polished_granite_stairs", + "id": -172 + }, + { + "name": "minecraft:polished_tuff", + "id": -748 + }, + { + "name": "minecraft:polished_tuff_double_slab", + "id": -750 + }, + { + "name": "minecraft:polished_tuff_slab", + "id": -749 + }, + { + "name": "minecraft:polished_tuff_stairs", + "id": -751 + }, + { + "name": "minecraft:polished_tuff_wall", + "id": -752 + }, + { + "name": "minecraft:popped_chorus_fruit", + "id": 576 + }, + { + "name": "minecraft:poppy", + "id": 38 + }, + { + "name": "minecraft:porkchop", + "id": 267 + }, + { + "name": "minecraft:portal", + "id": 90 + }, + { + "name": "minecraft:potato", + "id": 285 + }, + { + "name": "minecraft:potatoes", + "id": 142 + }, + { + "name": "minecraft:potion", + "id": 436 + }, + { + "name": "minecraft:powder_snow", + "id": -306 + }, + { + "name": "minecraft:powder_snow_bucket", + "id": 376 + }, + { + "name": "minecraft:powered_comparator", + "id": 150 + }, + { + "name": "minecraft:powered_repeater", + "id": 94 + }, + { + "name": "minecraft:prismarine", + "id": 168 + }, + { + "name": "minecraft:prismarine_brick_double_slab", + "id": -914 + }, + { + "name": "minecraft:prismarine_brick_slab", + "id": -887 + }, + { + "name": "minecraft:prismarine_bricks", + "id": -948 + }, + { + "name": "minecraft:prismarine_bricks_stairs", + "id": -4 + }, + { + "name": "minecraft:prismarine_crystals", + "id": 566 + }, + { + "name": "minecraft:prismarine_double_slab", + "id": -912 + }, + { + "name": "minecraft:prismarine_shard", + "id": 582 + }, + { + "name": "minecraft:prismarine_slab", + "id": -885 + }, + { + "name": "minecraft:prismarine_stairs", + "id": -2 + }, + { + "name": "minecraft:prize_pottery_sherd", + "id": 693 + }, + { + "name": "minecraft:pufferfish", + "id": 272 + }, + { + "name": "minecraft:pufferfish_bucket", + "id": 375 + }, + { + "name": "minecraft:pufferfish_spawn_egg", + "id": 492 + }, + { + "name": "minecraft:pumpkin", + "id": 86 + }, + { + "name": "minecraft:pumpkin_pie", + "id": 289 + }, + { + "name": "minecraft:pumpkin_seeds", + "id": 297 + }, + { + "name": "minecraft:pumpkin_stem", + "id": 104 + }, + { + "name": "minecraft:purple_candle", + "id": -423 + }, + { + "name": "minecraft:purple_candle_cake", + "id": -440 + }, + { + "name": "minecraft:purple_carpet", + "id": -606 + }, + { + "name": "minecraft:purple_concrete", + "id": -637 + }, + { + "name": "minecraft:purple_concrete_powder", + "id": -718 + }, + { + "name": "minecraft:purple_dye", + "id": 409 + }, + { + "name": "minecraft:purple_glazed_terracotta", + "id": 219 + }, + { + "name": "minecraft:purple_shulker_box", + "id": -622 + }, + { + "name": "minecraft:purple_stained_glass", + "id": -682 + }, + { + "name": "minecraft:purple_stained_glass_pane", + "id": -652 + }, + { + "name": "minecraft:purple_terracotta", + "id": -733 + }, + { + "name": "minecraft:purple_wool", + "id": -564 + }, + { + "name": "minecraft:purpur_block", + "id": 201 + }, + { + "name": "minecraft:purpur_double_slab", + "id": -911 + }, + { + "name": "minecraft:purpur_slab", + "id": -884 + }, + { + "name": "minecraft:purpur_stairs", + "id": 203 + }, + { + "name": "minecraft:quartz", + "id": 541 + }, + { + "name": "minecraft:quartz_block", + "id": 155 + }, + { + "name": "minecraft:quartz_bricks", + "id": -304 + }, + { + "name": "minecraft:quartz_double_slab", + "id": -882 + }, + { + "name": "minecraft:quartz_ore", + "id": 153 + }, + { + "name": "minecraft:quartz_pillar", + "id": -954 + }, + { + "name": "minecraft:quartz_slab", + "id": -876 + }, + { + "name": "minecraft:quartz_stairs", + "id": 156 + }, + { + "name": "minecraft:rabbit", + "id": 293 + }, + { + "name": "minecraft:rabbit_foot", + "id": 545 + }, + { + "name": "minecraft:rabbit_hide", + "id": 546 + }, + { + "name": "minecraft:rabbit_spawn_egg", + "id": 469 + }, + { + "name": "minecraft:rabbit_stew", + "id": 295 + }, + { + "name": "minecraft:rail", + "id": 66 + }, + { + "name": "minecraft:raiser_armor_trim_smithing_template", + "id": 714 + }, + { + "name": "minecraft:rapid_fertilizer", + "id": 617 + }, + { + "name": "minecraft:ravager_spawn_egg", + "id": 504 + }, + { + "name": "minecraft:raw_copper", + "id": 524 + }, + { + "name": "minecraft:raw_copper_block", + "id": -452 + }, + { + "name": "minecraft:raw_gold", + "id": 523 + }, + { + "name": "minecraft:raw_gold_block", + "id": -453 + }, + { + "name": "minecraft:raw_iron", + "id": 522 + }, + { + "name": "minecraft:raw_iron_block", + "id": -451 + }, + { + "name": "minecraft:recovery_compass", + "id": 666 + }, + { + "name": "minecraft:red_candle", + "id": -427 + }, + { + "name": "minecraft:red_candle_cake", + "id": -444 + }, + { + "name": "minecraft:red_carpet", + "id": -610 + }, + { + "name": "minecraft:red_concrete", + "id": -641 + }, + { + "name": "minecraft:red_concrete_powder", + "id": -722 + }, + { + "name": "minecraft:red_dye", + "id": 405 + }, + { + "name": "minecraft:red_flower", + "id": 746 + }, + { + "name": "minecraft:red_glazed_terracotta", + "id": 234 + }, + { + "name": "minecraft:red_mushroom", + "id": 40 + }, + { + "name": "minecraft:red_mushroom_block", + "id": 100 + }, + { + "name": "minecraft:red_nether_brick", + "id": 215 + }, + { + "name": "minecraft:red_nether_brick_double_slab", + "id": -917 + }, + { + "name": "minecraft:red_nether_brick_slab", + "id": -890 + }, + { + "name": "minecraft:red_nether_brick_stairs", + "id": -184 + }, + { + "name": "minecraft:red_sand", + "id": -949 + }, + { + "name": "minecraft:red_sandstone", + "id": 179 + }, + { + "name": "minecraft:red_sandstone_double_slab", + "id": 181 + }, + { + "name": "minecraft:red_sandstone_slab", + "id": 182 + }, + { + "name": "minecraft:red_sandstone_stairs", + "id": 180 + }, + { + "name": "minecraft:red_shulker_box", + "id": -626 + }, + { + "name": "minecraft:red_stained_glass", + "id": -686 + }, + { + "name": "minecraft:red_stained_glass_pane", + "id": -656 + }, + { + "name": "minecraft:red_terracotta", + "id": -737 + }, + { + "name": "minecraft:red_tulip", + "id": -833 + }, + { + "name": "minecraft:red_wool", + "id": -556 + }, + { + "name": "minecraft:redstone", + "id": 381 + }, + { + "name": "minecraft:redstone_block", + "id": 152 + }, + { + "name": "minecraft:redstone_lamp", + "id": 123 + }, + { + "name": "minecraft:redstone_ore", + "id": 73 + }, + { + "name": "minecraft:redstone_torch", + "id": 76 + }, + { + "name": "minecraft:redstone_wire", + "id": 55 + }, + { + "name": "minecraft:reinforced_deepslate", + "id": -466 + }, + { + "name": "minecraft:repeater", + "id": 428 + }, + { + "name": "minecraft:repeating_command_block", + "id": 188 + }, + { + "name": "minecraft:reserved6", + "id": 255 + }, + { + "name": "minecraft:respawn_anchor", + "id": -272 + }, + { + "name": "minecraft:rib_armor_trim_smithing_template", + "id": 710 + }, + { + "name": "minecraft:rose_bush", + "id": -866 + }, + { + "name": "minecraft:rotten_flesh", + "id": 282 + }, + { + "name": "minecraft:saddle", + "id": 379 + }, + { + "name": "minecraft:salmon", + "id": 270 + }, + { + "name": "minecraft:salmon_bucket", + "id": 373 + }, + { + "name": "minecraft:salmon_spawn_egg", + "id": 493 + }, + { + "name": "minecraft:sand", + "id": 12 + }, + { + "name": "minecraft:sandstone", + "id": 24 + }, + { + "name": "minecraft:sandstone_double_slab", + "id": -878 + }, + { + "name": "minecraft:sandstone_slab", + "id": -872 + }, + { + "name": "minecraft:sandstone_stairs", + "id": 128 + }, + { + "name": "minecraft:sapling", + "id": 742 + }, + { + "name": "minecraft:scaffolding", + "id": -165 + }, + { + "name": "minecraft:scrape_pottery_sherd", + "id": 694 + }, + { + "name": "minecraft:sculk", + "id": -458 + }, + { + "name": "minecraft:sculk_catalyst", + "id": -460 + }, + { + "name": "minecraft:sculk_sensor", + "id": -307 + }, + { + "name": "minecraft:sculk_shrieker", + "id": -461 + }, + { + "name": "minecraft:sculk_vein", + "id": -459 + }, + { + "name": "minecraft:sea_lantern", + "id": 169 + }, + { + "name": "minecraft:sea_pickle", + "id": -156 + }, + { + "name": "minecraft:seagrass", + "id": -130 + }, + { + "name": "minecraft:sentry_armor_trim_smithing_template", + "id": 701 + }, + { + "name": "minecraft:shaper_armor_trim_smithing_template", + "id": 715 + }, + { + "name": "minecraft:sheaf_pottery_sherd", + "id": 695 + }, + { + "name": "minecraft:shears", + "id": 430 + }, + { + "name": "minecraft:sheep_spawn_egg", + "id": 448 + }, + { + "name": "minecraft:shelter_pottery_sherd", + "id": 696 + }, + { + "name": "minecraft:shield", + "id": 363 + }, + { + "name": "minecraft:short_grass", + "id": 31 + }, + { + "name": "minecraft:shroomlight", + "id": -230 + }, + { + "name": "minecraft:shulker_box", + "id": 757 + }, + { + "name": "minecraft:shulker_shell", + "id": 583 + }, + { + "name": "minecraft:shulker_spawn_egg", + "id": 480 + }, + { + "name": "minecraft:silence_armor_trim_smithing_template", + "id": 712 + }, + { + "name": "minecraft:silver_glazed_terracotta", + "id": 228 + }, + { + "name": "minecraft:silverfish_spawn_egg", + "id": 453 + }, + { + "name": "minecraft:skeleton_horse_spawn_egg", + "id": 478 + }, + { + "name": "minecraft:skeleton_spawn_egg", + "id": 454 + }, + { + "name": "minecraft:skull", + "id": 533 + }, + { + "name": "minecraft:skull_banner_pattern", + "id": 600 + }, + { + "name": "minecraft:skull_pottery_sherd", + "id": 697 + }, + { + "name": "minecraft:slime", + "id": 165 + }, + { + "name": "minecraft:slime_ball", + "id": 397 + }, + { + "name": "minecraft:slime_spawn_egg", + "id": 455 + }, + { + "name": "minecraft:small_amethyst_bud", + "id": -332 + }, + { + "name": "minecraft:small_dripleaf_block", + "id": -336 + }, + { + "name": "minecraft:smithing_table", + "id": -202 + }, + { + "name": "minecraft:smoker", + "id": -198 + }, + { + "name": "minecraft:smooth_basalt", + "id": -377 + }, + { + "name": "minecraft:smooth_quartz", + "id": -955 + }, + { + "name": "minecraft:smooth_quartz_double_slab", + "id": -925 + }, + { + "name": "minecraft:smooth_quartz_slab", + "id": -898 + }, + { + "name": "minecraft:smooth_quartz_stairs", + "id": -185 + }, + { + "name": "minecraft:smooth_red_sandstone", + "id": -958 + }, + { + "name": "minecraft:smooth_red_sandstone_double_slab", + "id": -918 + }, + { + "name": "minecraft:smooth_red_sandstone_slab", + "id": -891 + }, + { + "name": "minecraft:smooth_red_sandstone_stairs", + "id": -176 + }, + { + "name": "minecraft:smooth_sandstone", + "id": -946 + }, + { + "name": "minecraft:smooth_sandstone_double_slab", + "id": -916 + }, + { + "name": "minecraft:smooth_sandstone_slab", + "id": -889 + }, + { + "name": "minecraft:smooth_sandstone_stairs", + "id": -177 + }, + { + "name": "minecraft:smooth_stone", + "id": -183 + }, + { + "name": "minecraft:smooth_stone_double_slab", + "id": 43 + }, + { + "name": "minecraft:smooth_stone_slab", + "id": 44 + }, + { + "name": "minecraft:sniffer_egg", + "id": -596 + }, + { + "name": "minecraft:sniffer_spawn_egg", + "id": 511 + }, + { + "name": "minecraft:snort_pottery_sherd", + "id": 698 + }, + { + "name": "minecraft:snout_armor_trim_smithing_template", + "id": 709 + }, + { + "name": "minecraft:snow", + "id": 80 + }, + { + "name": "minecraft:snow_golem_spawn_egg", + "id": 517 + }, + { + "name": "minecraft:snow_layer", + "id": 78 + }, + { + "name": "minecraft:snowball", + "id": 382 + }, + { + "name": "minecraft:soul_campfire", + "id": 642 + }, + { + "name": "minecraft:soul_fire", + "id": -237 + }, + { + "name": "minecraft:soul_lantern", + "id": -269 + }, + { + "name": "minecraft:soul_sand", + "id": 88 + }, + { + "name": "minecraft:soul_soil", + "id": -236 + }, + { + "name": "minecraft:soul_torch", + "id": -268 + }, + { + "name": "minecraft:sparkler", + "id": 620 + }, + { + "name": "minecraft:spawn_egg", + "id": 768 + }, + { + "name": "minecraft:spider_eye", + "id": 283 + }, + { + "name": "minecraft:spider_spawn_egg", + "id": 456 + }, + { + "name": "minecraft:spire_armor_trim_smithing_template", + "id": 711 + }, + { + "name": "minecraft:splash_potion", + "id": 578 + }, + { + "name": "minecraft:sponge", + "id": 19 + }, + { + "name": "minecraft:spore_blossom", + "id": -321 + }, + { + "name": "minecraft:spruce_boat", + "id": 387 + }, + { + "name": "minecraft:spruce_button", + "id": -144 + }, + { + "name": "minecraft:spruce_chest_boat", + "id": 661 + }, + { + "name": "minecraft:spruce_door", + "id": 570 + }, + { + "name": "minecraft:spruce_double_slab", + "id": -809 + }, + { + "name": "minecraft:spruce_fence", + "id": -579 + }, + { + "name": "minecraft:spruce_fence_gate", + "id": 183 + }, + { + "name": "minecraft:spruce_hanging_sign", + "id": -501 + }, + { + "name": "minecraft:spruce_leaves", + "id": -800 + }, + { + "name": "minecraft:spruce_log", + "id": -569 + }, + { + "name": "minecraft:spruce_planks", + "id": -739 + }, + { + "name": "minecraft:spruce_pressure_plate", + "id": -154 + }, + { + "name": "minecraft:spruce_sapling", + "id": -825 + }, + { + "name": "minecraft:spruce_sign", + "id": 593 + }, + { + "name": "minecraft:spruce_slab", + "id": -804 + }, + { + "name": "minecraft:spruce_stairs", + "id": 134 + }, + { + "name": "minecraft:spruce_standing_sign", + "id": -181 + }, + { + "name": "minecraft:spruce_trapdoor", + "id": -149 + }, + { + "name": "minecraft:spruce_wall_sign", + "id": -182 + }, + { + "name": "minecraft:spruce_wood", + "id": -814 + }, + { + "name": "minecraft:spyglass", + "id": 645 + }, + { + "name": "minecraft:squid_spawn_egg", + "id": 460 + }, + { + "name": "minecraft:stained_glass", + "id": 755 + }, + { + "name": "minecraft:stained_glass_pane", + "id": 756 + }, + { + "name": "minecraft:stained_hardened_clay", + "id": 720 + }, + { + "name": "minecraft:standing_banner", + "id": 176 + }, + { + "name": "minecraft:standing_sign", + "id": 63 + }, + { + "name": "minecraft:stick", + "id": 328 + }, + { + "name": "minecraft:sticky_piston", + "id": 29 + }, + { + "name": "minecraft:sticky_piston_arm_collision", + "id": -217 + }, + { + "name": "minecraft:stone", + "id": 1 + }, + { + "name": "minecraft:stone_axe", + "id": 322 + }, + { + "name": "minecraft:stone_block_slab", + "id": 732 + }, + { + "name": "minecraft:stone_block_slab2", + "id": 733 + }, + { + "name": "minecraft:stone_block_slab3", + "id": 734 + }, + { + "name": "minecraft:stone_block_slab4", + "id": 735 + }, + { + "name": "minecraft:stone_brick_double_slab", + "id": -881 + }, + { + "name": "minecraft:stone_brick_slab", + "id": -875 + }, + { + "name": "minecraft:stone_brick_stairs", + "id": 109 + }, + { + "name": "minecraft:stone_bricks", + "id": 98 + }, + { + "name": "minecraft:stone_button", + "id": 77 + }, + { + "name": "minecraft:stone_hoe", + "id": 338 + }, + { + "name": "minecraft:stone_pickaxe", + "id": 321 + }, + { + "name": "minecraft:stone_pressure_plate", + "id": 70 + }, + { + "name": "minecraft:stone_shovel", + "id": 320 + }, + { + "name": "minecraft:stone_stairs", + "id": 67 + }, + { + "name": "minecraft:stone_sword", + "id": 319 + }, + { + "name": "minecraft:stonebrick", + "id": 730 + }, + { + "name": "minecraft:stonecutter", + "id": 245 + }, + { + "name": "minecraft:stonecutter_block", + "id": -197 + }, + { + "name": "minecraft:stray_spawn_egg", + "id": 472 + }, + { + "name": "minecraft:strider_spawn_egg", + "id": 506 + }, + { + "name": "minecraft:string", + "id": 334 + }, + { + "name": "minecraft:stripped_acacia_log", + "id": -8 + }, + { + "name": "minecraft:stripped_acacia_wood", + "id": -823 + }, + { + "name": "minecraft:stripped_bamboo_block", + "id": -528 + }, + { + "name": "minecraft:stripped_birch_log", + "id": -6 + }, + { + "name": "minecraft:stripped_birch_wood", + "id": -821 + }, + { + "name": "minecraft:stripped_cherry_log", + "id": -535 + }, + { + "name": "minecraft:stripped_cherry_wood", + "id": -545 + }, + { + "name": "minecraft:stripped_crimson_hyphae", + "id": -300 + }, + { + "name": "minecraft:stripped_crimson_stem", + "id": -240 + }, + { + "name": "minecraft:stripped_dark_oak_log", + "id": -9 + }, + { + "name": "minecraft:stripped_dark_oak_wood", + "id": -824 + }, + { + "name": "minecraft:stripped_jungle_log", + "id": -7 + }, + { + "name": "minecraft:stripped_jungle_wood", + "id": -822 + }, + { + "name": "minecraft:stripped_mangrove_log", + "id": -485 + }, + { + "name": "minecraft:stripped_mangrove_wood", + "id": -498 + }, + { + "name": "minecraft:stripped_oak_log", + "id": -10 + }, + { + "name": "minecraft:stripped_oak_wood", + "id": -819 + }, + { + "name": "minecraft:stripped_spruce_log", + "id": -5 + }, + { + "name": "minecraft:stripped_spruce_wood", + "id": -820 + }, + { + "name": "minecraft:stripped_warped_hyphae", + "id": -301 + }, + { + "name": "minecraft:stripped_warped_stem", + "id": -241 + }, + { + "name": "minecraft:structure_block", + "id": 252 + }, + { + "name": "minecraft:structure_void", + "id": 217 + }, + { + "name": "minecraft:sugar", + "id": 425 + }, + { + "name": "minecraft:sugar_cane", + "id": 394 + }, + { + "name": "minecraft:sunflower", + "id": 175 + }, + { + "name": "minecraft:suspicious_gravel", + "id": -573 + }, + { + "name": "minecraft:suspicious_sand", + "id": -529 + }, + { + "name": "minecraft:suspicious_stew", + "id": 609 + }, + { + "name": "minecraft:sweet_berries", + "id": 292 + }, + { + "name": "minecraft:sweet_berry_bush", + "id": -207 + }, + { + "name": "minecraft:tadpole_bucket", + "id": 650 + }, + { + "name": "minecraft:tadpole_spawn_egg", + "id": 649 + }, + { + "name": "minecraft:tall_grass", + "id": -864 + }, + { + "name": "minecraft:tallgrass", + "id": 750 + }, + { + "name": "minecraft:target", + "id": -239 + }, + { + "name": "minecraft:tide_armor_trim_smithing_template", + "id": 708 + }, + { + "name": "minecraft:tinted_glass", + "id": -334 + }, + { + "name": "minecraft:tnt", + "id": 46 + }, + { + "name": "minecraft:tnt_minecart", + "id": 542 + }, + { + "name": "minecraft:torch", + "id": 50 + }, + { + "name": "minecraft:torchflower", + "id": -568 + }, + { + "name": "minecraft:torchflower_crop", + "id": -567 + }, + { + "name": "minecraft:torchflower_seeds", + "id": 301 + }, + { + "name": "minecraft:totem_of_undying", + "id": 585 + }, + { + "name": "minecraft:trader_llama_spawn_egg", + "id": 668 + }, + { + "name": "minecraft:trapdoor", + "id": 96 + }, + { + "name": "minecraft:trapped_chest", + "id": 146 + }, + { + "name": "minecraft:trial_key", + "id": 259 + }, + { + "name": "minecraft:trial_spawner", + "id": -315 + }, + { + "name": "minecraft:trident", + "id": 563 + }, + { + "name": "minecraft:trip_wire", + "id": 132 + }, + { + "name": "minecraft:tripwire_hook", + "id": 131 + }, + { + "name": "minecraft:tropical_fish", + "id": 271 + }, + { + "name": "minecraft:tropical_fish_bucket", + "id": 374 + }, + { + "name": "minecraft:tropical_fish_spawn_egg", + "id": 490 + }, + { + "name": "minecraft:tube_coral", + "id": -131 + }, + { + "name": "minecraft:tube_coral_block", + "id": -132 + }, + { + "name": "minecraft:tube_coral_fan", + "id": -133 + }, + { + "name": "minecraft:tube_coral_wall_fan", + "id": -135 + }, + { + "name": "minecraft:tuff", + "id": -333 + }, + { + "name": "minecraft:tuff_brick_double_slab", + "id": -756 + }, + { + "name": "minecraft:tuff_brick_slab", + "id": -755 + }, + { + "name": "minecraft:tuff_brick_stairs", + "id": -757 + }, + { + "name": "minecraft:tuff_brick_wall", + "id": -758 + }, + { + "name": "minecraft:tuff_bricks", + "id": -754 + }, + { + "name": "minecraft:tuff_double_slab", + "id": -745 + }, + { + "name": "minecraft:tuff_slab", + "id": -744 + }, + { + "name": "minecraft:tuff_stairs", + "id": -746 + }, + { + "name": "minecraft:tuff_wall", + "id": -747 + }, + { + "name": "minecraft:turtle_egg", + "id": -159 + }, + { + "name": "minecraft:turtle_helmet", + "id": 590 + }, + { + "name": "minecraft:turtle_scute", + "id": 589 + }, + { + "name": "minecraft:turtle_spawn_egg", + "id": 496 + }, + { + "name": "minecraft:twisting_vines", + "id": -287 + }, + { + "name": "minecraft:underwater_torch", + "id": 239 + }, + { + "name": "minecraft:undyed_shulker_box", + "id": 205 + }, + { + "name": "minecraft:unknown", + "id": -305 + }, + { + "name": "minecraft:unlit_redstone_torch", + "id": 75 + }, + { + "name": "minecraft:unpowered_comparator", + "id": 149 + }, + { + "name": "minecraft:unpowered_repeater", + "id": 93 + }, + { + "name": "minecraft:vault", + "id": -314 + }, + { + "name": "minecraft:verdant_froglight", + "id": -470 + }, + { + "name": "minecraft:vex_armor_trim_smithing_template", + "id": 707 + }, + { + "name": "minecraft:vex_spawn_egg", + "id": 487 + }, + { + "name": "minecraft:villager_spawn_egg", + "id": 459 + }, + { + "name": "minecraft:vindicator_spawn_egg", + "id": 485 + }, + { + "name": "minecraft:vine", + "id": 106 + }, + { + "name": "minecraft:wall_banner", + "id": 177 + }, + { + "name": "minecraft:wall_sign", + "id": 68 + }, + { + "name": "minecraft:wandering_trader_spawn_egg", + "id": 503 + }, + { + "name": "minecraft:ward_armor_trim_smithing_template", + "id": 705 + }, + { + "name": "minecraft:warden_spawn_egg", + "id": 652 + }, + { + "name": "minecraft:warped_button", + "id": -261 + }, + { + "name": "minecraft:warped_door", + "id": 637 + }, + { + "name": "minecraft:warped_double_slab", + "id": -267 + }, + { + "name": "minecraft:warped_fence", + "id": -257 + }, + { + "name": "minecraft:warped_fence_gate", + "id": -259 + }, + { + "name": "minecraft:warped_fungus", + "id": -229 + }, + { + "name": "minecraft:warped_fungus_on_a_stick", + "id": 638 + }, + { + "name": "minecraft:warped_hanging_sign", + "id": -507 + }, + { + "name": "minecraft:warped_hyphae", + "id": -298 + }, + { + "name": "minecraft:warped_nylium", + "id": -233 + }, + { + "name": "minecraft:warped_planks", + "id": -243 + }, + { + "name": "minecraft:warped_pressure_plate", + "id": -263 + }, + { + "name": "minecraft:warped_roots", + "id": -224 + }, + { + "name": "minecraft:warped_sign", + "id": 635 + }, + { + "name": "minecraft:warped_slab", + "id": -265 + }, + { + "name": "minecraft:warped_stairs", + "id": -255 + }, + { + "name": "minecraft:warped_standing_sign", + "id": -251 + }, + { + "name": "minecraft:warped_stem", + "id": -226 + }, + { + "name": "minecraft:warped_trapdoor", + "id": -247 + }, + { + "name": "minecraft:warped_wall_sign", + "id": -253 + }, + { + "name": "minecraft:warped_wart_block", + "id": -227 + }, + { + "name": "minecraft:water", + "id": 9 + }, + { + "name": "minecraft:water_bucket", + "id": 370 + }, + { + "name": "minecraft:waterlily", + "id": 111 + }, + { + "name": "minecraft:waxed_chiseled_copper", + "id": -764 + }, + { + "name": "minecraft:waxed_copper", + "id": -344 + }, + { + "name": "minecraft:waxed_copper_bulb", + "id": -780 + }, + { + "name": "minecraft:waxed_copper_door", + "id": -788 + }, + { + "name": "minecraft:waxed_copper_grate", + "id": -772 + }, + { + "name": "minecraft:waxed_copper_trapdoor", + "id": -796 + }, + { + "name": "minecraft:waxed_cut_copper", + "id": -351 + }, + { + "name": "minecraft:waxed_cut_copper_slab", + "id": -365 + }, + { + "name": "minecraft:waxed_cut_copper_stairs", + "id": -358 + }, + { + "name": "minecraft:waxed_double_cut_copper_slab", + "id": -372 + }, + { + "name": "minecraft:waxed_exposed_chiseled_copper", + "id": -765 + }, + { + "name": "minecraft:waxed_exposed_copper", + "id": -345 + }, + { + "name": "minecraft:waxed_exposed_copper_bulb", + "id": -781 + }, + { + "name": "minecraft:waxed_exposed_copper_door", + "id": -789 + }, + { + "name": "minecraft:waxed_exposed_copper_grate", + "id": -773 + }, + { + "name": "minecraft:waxed_exposed_copper_trapdoor", + "id": -797 + }, + { + "name": "minecraft:waxed_exposed_cut_copper", + "id": -352 + }, + { + "name": "minecraft:waxed_exposed_cut_copper_slab", + "id": -366 + }, + { + "name": "minecraft:waxed_exposed_cut_copper_stairs", + "id": -359 + }, + { + "name": "minecraft:waxed_exposed_double_cut_copper_slab", + "id": -373 + }, + { + "name": "minecraft:waxed_oxidized_chiseled_copper", + "id": -766 + }, + { + "name": "minecraft:waxed_oxidized_copper", + "id": -446 + }, + { + "name": "minecraft:waxed_oxidized_copper_bulb", + "id": -783 + }, + { + "name": "minecraft:waxed_oxidized_copper_door", + "id": -791 + }, + { + "name": "minecraft:waxed_oxidized_copper_grate", + "id": -775 + }, + { + "name": "minecraft:waxed_oxidized_copper_trapdoor", + "id": -799 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper", + "id": -447 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper_slab", + "id": -449 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper_stairs", + "id": -448 + }, + { + "name": "minecraft:waxed_oxidized_double_cut_copper_slab", + "id": -450 + }, + { + "name": "minecraft:waxed_weathered_chiseled_copper", + "id": -767 + }, + { + "name": "minecraft:waxed_weathered_copper", + "id": -346 + }, + { + "name": "minecraft:waxed_weathered_copper_bulb", + "id": -782 + }, + { + "name": "minecraft:waxed_weathered_copper_door", + "id": -790 + }, + { + "name": "minecraft:waxed_weathered_copper_grate", + "id": -774 + }, + { + "name": "minecraft:waxed_weathered_copper_trapdoor", + "id": -798 + }, + { + "name": "minecraft:waxed_weathered_cut_copper", + "id": -353 + }, + { + "name": "minecraft:waxed_weathered_cut_copper_slab", + "id": -367 + }, + { + "name": "minecraft:waxed_weathered_cut_copper_stairs", + "id": -360 + }, + { + "name": "minecraft:waxed_weathered_double_cut_copper_slab", + "id": -374 + }, + { + "name": "minecraft:wayfinder_armor_trim_smithing_template", + "id": 713 + }, + { + "name": "minecraft:weathered_chiseled_copper", + "id": -762 + }, + { + "name": "minecraft:weathered_copper", + "id": -342 + }, + { + "name": "minecraft:weathered_copper_bulb", + "id": -778 + }, + { + "name": "minecraft:weathered_copper_door", + "id": -786 + }, + { + "name": "minecraft:weathered_copper_grate", + "id": -770 + }, + { + "name": "minecraft:weathered_copper_trapdoor", + "id": -794 + }, + { + "name": "minecraft:weathered_cut_copper", + "id": -349 + }, + { + "name": "minecraft:weathered_cut_copper_slab", + "id": -363 + }, + { + "name": "minecraft:weathered_cut_copper_stairs", + "id": -356 + }, + { + "name": "minecraft:weathered_double_cut_copper_slab", + "id": -370 + }, + { + "name": "minecraft:web", + "id": 30 + }, + { + "name": "minecraft:weeping_vines", + "id": -231 + }, + { + "name": "minecraft:wheat", + "id": 342 + }, + { + "name": "minecraft:wheat_seeds", + "id": 296 + }, + { + "name": "minecraft:white_candle", + "id": -413 + }, + { + "name": "minecraft:white_candle_cake", + "id": -430 + }, + { + "name": "minecraft:white_carpet", + "id": 171 + }, + { + "name": "minecraft:white_concrete", + "id": 236 + }, + { + "name": "minecraft:white_concrete_powder", + "id": 237 + }, + { + "name": "minecraft:white_dye", + "id": 419 + }, + { + "name": "minecraft:white_glazed_terracotta", + "id": 220 + }, + { + "name": "minecraft:white_shulker_box", + "id": 218 + }, + { + "name": "minecraft:white_stained_glass", + "id": 241 + }, + { + "name": "minecraft:white_stained_glass_pane", + "id": 160 + }, + { + "name": "minecraft:white_terracotta", + "id": 159 + }, + { + "name": "minecraft:white_tulip", + "id": -835 + }, + { + "name": "minecraft:white_wool", + "id": 35 + }, + { + "name": "minecraft:wild_armor_trim_smithing_template", + "id": 704 + }, + { + "name": "minecraft:wind_charge", + "id": 260 + }, + { + "name": "minecraft:witch_spawn_egg", + "id": 462 + }, + { + "name": "minecraft:wither_rose", + "id": -216 + }, + { + "name": "minecraft:wither_skeleton_spawn_egg", + "id": 475 + }, + { + "name": "minecraft:wither_spawn_egg", + "id": 519 + }, + { + "name": "minecraft:wolf_armor", + "id": 723 + }, + { + "name": "minecraft:wolf_spawn_egg", + "id": 449 + }, + { + "name": "minecraft:wood", + "id": 758 + }, + { + "name": "minecraft:wooden_axe", + "id": 318 + }, + { + "name": "minecraft:wooden_button", + "id": 143 + }, + { + "name": "minecraft:wooden_door", + "id": 367 + }, + { + "name": "minecraft:wooden_hoe", + "id": 337 + }, + { + "name": "minecraft:wooden_pickaxe", + "id": 317 + }, + { + "name": "minecraft:wooden_pressure_plate", + "id": 72 + }, + { + "name": "minecraft:wooden_shovel", + "id": 316 + }, + { + "name": "minecraft:wooden_slab", + "id": 745 + }, + { + "name": "minecraft:wooden_sword", + "id": 315 + }, + { + "name": "minecraft:wool", + "id": 726 + }, + { + "name": "minecraft:writable_book", + "id": 527 + }, + { + "name": "minecraft:written_book", + "id": 528 + }, + { + "name": "minecraft:yellow_candle", + "id": -417 + }, + { + "name": "minecraft:yellow_candle_cake", + "id": -434 + }, + { + "name": "minecraft:yellow_carpet", + "id": -600 + }, + { + "name": "minecraft:yellow_concrete", + "id": -631 + }, + { + "name": "minecraft:yellow_concrete_powder", + "id": -712 + }, + { + "name": "minecraft:yellow_dye", + "id": 415 + }, + { + "name": "minecraft:yellow_glazed_terracotta", + "id": 224 + }, + { + "name": "minecraft:yellow_shulker_box", + "id": -616 + }, + { + "name": "minecraft:yellow_stained_glass", + "id": -676 + }, + { + "name": "minecraft:yellow_stained_glass_pane", + "id": -646 + }, + { + "name": "minecraft:yellow_terracotta", + "id": -727 + }, + { + "name": "minecraft:yellow_wool", + "id": -558 + }, + { + "name": "minecraft:zoglin_spawn_egg", + "id": 509 + }, + { + "name": "minecraft:zombie_horse_spawn_egg", + "id": 479 + }, + { + "name": "minecraft:zombie_pigman_spawn_egg", + "id": 458 + }, + { + "name": "minecraft:zombie_spawn_egg", + "id": 457 + }, + { + "name": "minecraft:zombie_villager_spawn_egg", + "id": 488 + } +] \ No newline at end of file diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_21_30.json b/core/src/main/resources/bedrock/runtime_item_states.1_21_30.json new file mode 100644 index 000000000..55bbe0a00 --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_21_30.json @@ -0,0 +1,6898 @@ +[ + { + "name": "minecraft:acacia_boat", + "id": 388 + }, + { + "name": "minecraft:acacia_button", + "id": -140 + }, + { + "name": "minecraft:acacia_chest_boat", + "id": 662 + }, + { + "name": "minecraft:acacia_door", + "id": 573 + }, + { + "name": "minecraft:acacia_double_slab", + "id": -812 + }, + { + "name": "minecraft:acacia_fence", + "id": -575 + }, + { + "name": "minecraft:acacia_fence_gate", + "id": 187 + }, + { + "name": "minecraft:acacia_hanging_sign", + "id": -504 + }, + { + "name": "minecraft:acacia_leaves", + "id": 161 + }, + { + "name": "minecraft:acacia_log", + "id": 162 + }, + { + "name": "minecraft:acacia_planks", + "id": -742 + }, + { + "name": "minecraft:acacia_pressure_plate", + "id": -150 + }, + { + "name": "minecraft:acacia_sapling", + "id": -828 + }, + { + "name": "minecraft:acacia_sign", + "id": 596 + }, + { + "name": "minecraft:acacia_slab", + "id": -807 + }, + { + "name": "minecraft:acacia_stairs", + "id": 163 + }, + { + "name": "minecraft:acacia_standing_sign", + "id": -190 + }, + { + "name": "minecraft:acacia_trapdoor", + "id": -145 + }, + { + "name": "minecraft:acacia_wall_sign", + "id": -191 + }, + { + "name": "minecraft:acacia_wood", + "id": -817 + }, + { + "name": "minecraft:activator_rail", + "id": 126 + }, + { + "name": "minecraft:agent_spawn_egg", + "id": 498 + }, + { + "name": "minecraft:air", + "id": -158 + }, + { + "name": "minecraft:allay_spawn_egg", + "id": 651 + }, + { + "name": "minecraft:allium", + "id": -831 + }, + { + "name": "minecraft:allow", + "id": 210 + }, + { + "name": "minecraft:amethyst_block", + "id": -327 + }, + { + "name": "minecraft:amethyst_cluster", + "id": -329 + }, + { + "name": "minecraft:amethyst_shard", + "id": 644 + }, + { + "name": "minecraft:ancient_debris", + "id": -271 + }, + { + "name": "minecraft:andesite", + "id": -594 + }, + { + "name": "minecraft:andesite_double_slab", + "id": -920 + }, + { + "name": "minecraft:andesite_slab", + "id": -893 + }, + { + "name": "minecraft:andesite_stairs", + "id": -171 + }, + { + "name": "minecraft:andesite_wall", + "id": -974 + }, + { + "name": "minecraft:angler_pottery_sherd", + "id": 676 + }, + { + "name": "minecraft:anvil", + "id": 145 + }, + { + "name": "minecraft:apple", + "id": 261 + }, + { + "name": "minecraft:archer_pottery_sherd", + "id": 677 + }, + { + "name": "minecraft:armadillo_scute", + "id": 722 + }, + { + "name": "minecraft:armadillo_spawn_egg", + "id": 721 + }, + { + "name": "minecraft:armor_stand", + "id": 569 + }, + { + "name": "minecraft:arms_up_pottery_sherd", + "id": 678 + }, + { + "name": "minecraft:arrow", + "id": 308 + }, + { + "name": "minecraft:axolotl_bucket", + "id": 377 + }, + { + "name": "minecraft:axolotl_spawn_egg", + "id": 513 + }, + { + "name": "minecraft:azalea", + "id": -337 + }, + { + "name": "minecraft:azalea_leaves", + "id": -324 + }, + { + "name": "minecraft:azalea_leaves_flowered", + "id": -325 + }, + { + "name": "minecraft:azure_bluet", + "id": -832 + }, + { + "name": "minecraft:baked_potato", + "id": 286 + }, + { + "name": "minecraft:balloon", + "id": 618 + }, + { + "name": "minecraft:bamboo", + "id": -163 + }, + { + "name": "minecraft:bamboo_block", + "id": -527 + }, + { + "name": "minecraft:bamboo_button", + "id": -511 + }, + { + "name": "minecraft:bamboo_chest_raft", + "id": 674 + }, + { + "name": "minecraft:bamboo_door", + "id": -517 + }, + { + "name": "minecraft:bamboo_double_slab", + "id": -521 + }, + { + "name": "minecraft:bamboo_fence", + "id": -515 + }, + { + "name": "minecraft:bamboo_fence_gate", + "id": -516 + }, + { + "name": "minecraft:bamboo_hanging_sign", + "id": -522 + }, + { + "name": "minecraft:bamboo_mosaic", + "id": -509 + }, + { + "name": "minecraft:bamboo_mosaic_double_slab", + "id": -525 + }, + { + "name": "minecraft:bamboo_mosaic_slab", + "id": -524 + }, + { + "name": "minecraft:bamboo_mosaic_stairs", + "id": -523 + }, + { + "name": "minecraft:bamboo_planks", + "id": -510 + }, + { + "name": "minecraft:bamboo_pressure_plate", + "id": -514 + }, + { + "name": "minecraft:bamboo_raft", + "id": 673 + }, + { + "name": "minecraft:bamboo_sapling", + "id": -164 + }, + { + "name": "minecraft:bamboo_sign", + "id": 672 + }, + { + "name": "minecraft:bamboo_slab", + "id": -513 + }, + { + "name": "minecraft:bamboo_stairs", + "id": -512 + }, + { + "name": "minecraft:bamboo_standing_sign", + "id": -518 + }, + { + "name": "minecraft:bamboo_trapdoor", + "id": -520 + }, + { + "name": "minecraft:bamboo_wall_sign", + "id": -519 + }, + { + "name": "minecraft:banner", + "id": 584 + }, + { + "name": "minecraft:banner_pattern", + "id": 770 + }, + { + "name": "minecraft:barrel", + "id": -203 + }, + { + "name": "minecraft:barrier", + "id": -161 + }, + { + "name": "minecraft:basalt", + "id": -234 + }, + { + "name": "minecraft:bat_spawn_egg", + "id": 463 + }, + { + "name": "minecraft:beacon", + "id": 138 + }, + { + "name": "minecraft:bed", + "id": 427 + }, + { + "name": "minecraft:bedrock", + "id": 7 + }, + { + "name": "minecraft:bee_nest", + "id": -218 + }, + { + "name": "minecraft:bee_spawn_egg", + "id": 505 + }, + { + "name": "minecraft:beef", + "id": 278 + }, + { + "name": "minecraft:beehive", + "id": -219 + }, + { + "name": "minecraft:beetroot", + "id": 290 + }, + { + "name": "minecraft:beetroot_seeds", + "id": 300 + }, + { + "name": "minecraft:beetroot_soup", + "id": 291 + }, + { + "name": "minecraft:bell", + "id": -206 + }, + { + "name": "minecraft:big_dripleaf", + "id": -323 + }, + { + "name": "minecraft:birch_boat", + "id": 385 + }, + { + "name": "minecraft:birch_button", + "id": -141 + }, + { + "name": "minecraft:birch_chest_boat", + "id": 659 + }, + { + "name": "minecraft:birch_door", + "id": 571 + }, + { + "name": "minecraft:birch_double_slab", + "id": -810 + }, + { + "name": "minecraft:birch_fence", + "id": -576 + }, + { + "name": "minecraft:birch_fence_gate", + "id": 184 + }, + { + "name": "minecraft:birch_hanging_sign", + "id": -502 + }, + { + "name": "minecraft:birch_leaves", + "id": -801 + }, + { + "name": "minecraft:birch_log", + "id": -570 + }, + { + "name": "minecraft:birch_planks", + "id": -740 + }, + { + "name": "minecraft:birch_pressure_plate", + "id": -151 + }, + { + "name": "minecraft:birch_sapling", + "id": -826 + }, + { + "name": "minecraft:birch_sign", + "id": 594 + }, + { + "name": "minecraft:birch_slab", + "id": -805 + }, + { + "name": "minecraft:birch_stairs", + "id": 135 + }, + { + "name": "minecraft:birch_standing_sign", + "id": -186 + }, + { + "name": "minecraft:birch_trapdoor", + "id": -146 + }, + { + "name": "minecraft:birch_wall_sign", + "id": -187 + }, + { + "name": "minecraft:birch_wood", + "id": -815 + }, + { + "name": "minecraft:black_candle", + "id": -428 + }, + { + "name": "minecraft:black_candle_cake", + "id": -445 + }, + { + "name": "minecraft:black_carpet", + "id": -611 + }, + { + "name": "minecraft:black_concrete", + "id": -642 + }, + { + "name": "minecraft:black_concrete_powder", + "id": -723 + }, + { + "name": "minecraft:black_dye", + "id": 404 + }, + { + "name": "minecraft:black_glazed_terracotta", + "id": 235 + }, + { + "name": "minecraft:black_shulker_box", + "id": -627 + }, + { + "name": "minecraft:black_stained_glass", + "id": -687 + }, + { + "name": "minecraft:black_stained_glass_pane", + "id": -657 + }, + { + "name": "minecraft:black_terracotta", + "id": -738 + }, + { + "name": "minecraft:black_wool", + "id": -554 + }, + { + "name": "minecraft:blackstone", + "id": -273 + }, + { + "name": "minecraft:blackstone_double_slab", + "id": -283 + }, + { + "name": "minecraft:blackstone_slab", + "id": -282 + }, + { + "name": "minecraft:blackstone_stairs", + "id": -276 + }, + { + "name": "minecraft:blackstone_wall", + "id": -277 + }, + { + "name": "minecraft:blade_pottery_sherd", + "id": 679 + }, + { + "name": "minecraft:blast_furnace", + "id": -196 + }, + { + "name": "minecraft:blaze_powder", + "id": 439 + }, + { + "name": "minecraft:blaze_rod", + "id": 432 + }, + { + "name": "minecraft:blaze_spawn_egg", + "id": 466 + }, + { + "name": "minecraft:bleach", + "id": 616 + }, + { + "name": "minecraft:blue_candle", + "id": -424 + }, + { + "name": "minecraft:blue_candle_cake", + "id": -441 + }, + { + "name": "minecraft:blue_carpet", + "id": -607 + }, + { + "name": "minecraft:blue_concrete", + "id": -638 + }, + { + "name": "minecraft:blue_concrete_powder", + "id": -719 + }, + { + "name": "minecraft:blue_dye", + "id": 408 + }, + { + "name": "minecraft:blue_glazed_terracotta", + "id": 231 + }, + { + "name": "minecraft:blue_ice", + "id": -11 + }, + { + "name": "minecraft:blue_orchid", + "id": -830 + }, + { + "name": "minecraft:blue_shulker_box", + "id": -623 + }, + { + "name": "minecraft:blue_stained_glass", + "id": -683 + }, + { + "name": "minecraft:blue_stained_glass_pane", + "id": -653 + }, + { + "name": "minecraft:blue_terracotta", + "id": -734 + }, + { + "name": "minecraft:blue_wool", + "id": -563 + }, + { + "name": "minecraft:boat", + "id": 768 + }, + { + "name": "minecraft:bogged_spawn_egg", + "id": 473 + }, + { + "name": "minecraft:bolt_armor_trim_smithing_template", + "id": 718 + }, + { + "name": "minecraft:bone", + "id": 424 + }, + { + "name": "minecraft:bone_block", + "id": 216 + }, + { + "name": "minecraft:bone_meal", + "id": 420 + }, + { + "name": "minecraft:book", + "id": 396 + }, + { + "name": "minecraft:bookshelf", + "id": 47 + }, + { + "name": "minecraft:border_block", + "id": 212 + }, + { + "name": "minecraft:bordure_indented_banner_pattern", + "id": 603 + }, + { + "name": "minecraft:bow", + "id": 307 + }, + { + "name": "minecraft:bowl", + "id": 329 + }, + { + "name": "minecraft:brain_coral", + "id": -581 + }, + { + "name": "minecraft:brain_coral_block", + "id": -849 + }, + { + "name": "minecraft:brain_coral_fan", + "id": -840 + }, + { + "name": "minecraft:brain_coral_wall_fan", + "id": -904 + }, + { + "name": "minecraft:bread", + "id": 266 + }, + { + "name": "minecraft:breeze_rod", + "id": 257 + }, + { + "name": "minecraft:breeze_spawn_egg", + "id": 512 + }, + { + "name": "minecraft:brewer_pottery_sherd", + "id": 680 + }, + { + "name": "minecraft:brewing_stand", + "id": 441 + }, + { + "name": "minecraft:brick", + "id": 392 + }, + { + "name": "minecraft:brick_block", + "id": 45 + }, + { + "name": "minecraft:brick_double_slab", + "id": -880 + }, + { + "name": "minecraft:brick_slab", + "id": -874 + }, + { + "name": "minecraft:brick_stairs", + "id": 108 + }, + { + "name": "minecraft:brick_wall", + "id": -976 + }, + { + "name": "minecraft:brown_candle", + "id": -425 + }, + { + "name": "minecraft:brown_candle_cake", + "id": -442 + }, + { + "name": "minecraft:brown_carpet", + "id": -608 + }, + { + "name": "minecraft:brown_concrete", + "id": -639 + }, + { + "name": "minecraft:brown_concrete_powder", + "id": -720 + }, + { + "name": "minecraft:brown_dye", + "id": 407 + }, + { + "name": "minecraft:brown_glazed_terracotta", + "id": 232 + }, + { + "name": "minecraft:brown_mushroom", + "id": 39 + }, + { + "name": "minecraft:brown_mushroom_block", + "id": 99 + }, + { + "name": "minecraft:brown_shulker_box", + "id": -624 + }, + { + "name": "minecraft:brown_stained_glass", + "id": -684 + }, + { + "name": "minecraft:brown_stained_glass_pane", + "id": -654 + }, + { + "name": "minecraft:brown_terracotta", + "id": -735 + }, + { + "name": "minecraft:brown_wool", + "id": -555 + }, + { + "name": "minecraft:brush", + "id": 699 + }, + { + "name": "minecraft:bubble_column", + "id": -160 + }, + { + "name": "minecraft:bubble_coral", + "id": -582 + }, + { + "name": "minecraft:bubble_coral_block", + "id": -850 + }, + { + "name": "minecraft:bubble_coral_fan", + "id": -841 + }, + { + "name": "minecraft:bubble_coral_wall_fan", + "id": -136 + }, + { + "name": "minecraft:bucket", + "id": 368 + }, + { + "name": "minecraft:budding_amethyst", + "id": -328 + }, + { + "name": "minecraft:burn_pottery_sherd", + "id": 681 + }, + { + "name": "minecraft:cactus", + "id": 81 + }, + { + "name": "minecraft:cake", + "id": 426 + }, + { + "name": "minecraft:calcite", + "id": -326 + }, + { + "name": "minecraft:calibrated_sculk_sensor", + "id": -580 + }, + { + "name": "minecraft:camel_spawn_egg", + "id": 675 + }, + { + "name": "minecraft:camera", + "id": 613 + }, + { + "name": "minecraft:campfire", + "id": 608 + }, + { + "name": "minecraft:candle", + "id": -412 + }, + { + "name": "minecraft:candle_cake", + "id": -429 + }, + { + "name": "minecraft:carpet", + "id": 727 + }, + { + "name": "minecraft:carrot", + "id": 284 + }, + { + "name": "minecraft:carrot_on_a_stick", + "id": 534 + }, + { + "name": "minecraft:carrots", + "id": 141 + }, + { + "name": "minecraft:cartography_table", + "id": -200 + }, + { + "name": "minecraft:carved_pumpkin", + "id": -155 + }, + { + "name": "minecraft:cat_spawn_egg", + "id": 499 + }, + { + "name": "minecraft:cauldron", + "id": 442 + }, + { + "name": "minecraft:cave_spider_spawn_egg", + "id": 467 + }, + { + "name": "minecraft:cave_vines", + "id": -322 + }, + { + "name": "minecraft:cave_vines_body_with_berries", + "id": -375 + }, + { + "name": "minecraft:cave_vines_head_with_berries", + "id": -376 + }, + { + "name": "minecraft:chain", + "id": 639 + }, + { + "name": "minecraft:chain_command_block", + "id": 189 + }, + { + "name": "minecraft:chainmail_boots", + "id": 350 + }, + { + "name": "minecraft:chainmail_chestplate", + "id": 348 + }, + { + "name": "minecraft:chainmail_helmet", + "id": 347 + }, + { + "name": "minecraft:chainmail_leggings", + "id": 349 + }, + { + "name": "minecraft:charcoal", + "id": 310 + }, + { + "name": "minecraft:chemical_heat", + "id": 192 + }, + { + "name": "minecraft:chemistry_table", + "id": 762 + }, + { + "name": "minecraft:cherry_boat", + "id": 669 + }, + { + "name": "minecraft:cherry_button", + "id": -530 + }, + { + "name": "minecraft:cherry_chest_boat", + "id": 670 + }, + { + "name": "minecraft:cherry_door", + "id": -531 + }, + { + "name": "minecraft:cherry_double_slab", + "id": -540 + }, + { + "name": "minecraft:cherry_fence", + "id": -532 + }, + { + "name": "minecraft:cherry_fence_gate", + "id": -533 + }, + { + "name": "minecraft:cherry_hanging_sign", + "id": -534 + }, + { + "name": "minecraft:cherry_leaves", + "id": -548 + }, + { + "name": "minecraft:cherry_log", + "id": -536 + }, + { + "name": "minecraft:cherry_planks", + "id": -537 + }, + { + "name": "minecraft:cherry_pressure_plate", + "id": -538 + }, + { + "name": "minecraft:cherry_sapling", + "id": -547 + }, + { + "name": "minecraft:cherry_sign", + "id": 671 + }, + { + "name": "minecraft:cherry_slab", + "id": -539 + }, + { + "name": "minecraft:cherry_stairs", + "id": -541 + }, + { + "name": "minecraft:cherry_standing_sign", + "id": -542 + }, + { + "name": "minecraft:cherry_trapdoor", + "id": -543 + }, + { + "name": "minecraft:cherry_wall_sign", + "id": -544 + }, + { + "name": "minecraft:cherry_wood", + "id": -546 + }, + { + "name": "minecraft:chest", + "id": 54 + }, + { + "name": "minecraft:chest_boat", + "id": 665 + }, + { + "name": "minecraft:chest_minecart", + "id": 398 + }, + { + "name": "minecraft:chicken", + "id": 280 + }, + { + "name": "minecraft:chicken_spawn_egg", + "id": 445 + }, + { + "name": "minecraft:chipped_anvil", + "id": -959 + }, + { + "name": "minecraft:chiseled_bookshelf", + "id": -526 + }, + { + "name": "minecraft:chiseled_copper", + "id": -760 + }, + { + "name": "minecraft:chiseled_deepslate", + "id": -395 + }, + { + "name": "minecraft:chiseled_nether_bricks", + "id": -302 + }, + { + "name": "minecraft:chiseled_polished_blackstone", + "id": -279 + }, + { + "name": "minecraft:chiseled_quartz_block", + "id": -953 + }, + { + "name": "minecraft:chiseled_red_sandstone", + "id": -956 + }, + { + "name": "minecraft:chiseled_sandstone", + "id": -944 + }, + { + "name": "minecraft:chiseled_stone_bricks", + "id": -870 + }, + { + "name": "minecraft:chiseled_tuff", + "id": -753 + }, + { + "name": "minecraft:chiseled_tuff_bricks", + "id": -759 + }, + { + "name": "minecraft:chorus_flower", + "id": 200 + }, + { + "name": "minecraft:chorus_fruit", + "id": 575 + }, + { + "name": "minecraft:chorus_plant", + "id": 240 + }, + { + "name": "minecraft:clay", + "id": 82 + }, + { + "name": "minecraft:clay_ball", + "id": 393 + }, + { + "name": "minecraft:client_request_placeholder_block", + "id": -465 + }, + { + "name": "minecraft:clock", + "id": 402 + }, + { + "name": "minecraft:coal", + "id": 309 + }, + { + "name": "minecraft:coal_block", + "id": 173 + }, + { + "name": "minecraft:coal_ore", + "id": 16 + }, + { + "name": "minecraft:coarse_dirt", + "id": -962 + }, + { + "name": "minecraft:coast_armor_trim_smithing_template", + "id": 703 + }, + { + "name": "minecraft:cobbled_deepslate", + "id": -379 + }, + { + "name": "minecraft:cobbled_deepslate_double_slab", + "id": -396 + }, + { + "name": "minecraft:cobbled_deepslate_slab", + "id": -380 + }, + { + "name": "minecraft:cobbled_deepslate_stairs", + "id": -381 + }, + { + "name": "minecraft:cobbled_deepslate_wall", + "id": -382 + }, + { + "name": "minecraft:cobblestone", + "id": 4 + }, + { + "name": "minecraft:cobblestone_double_slab", + "id": -879 + }, + { + "name": "minecraft:cobblestone_slab", + "id": -873 + }, + { + "name": "minecraft:cobblestone_wall", + "id": 139 + }, + { + "name": "minecraft:cocoa", + "id": 127 + }, + { + "name": "minecraft:cocoa_beans", + "id": 421 + }, + { + "name": "minecraft:cod", + "id": 269 + }, + { + "name": "minecraft:cod_bucket", + "id": 372 + }, + { + "name": "minecraft:cod_spawn_egg", + "id": 491 + }, + { + "name": "minecraft:colored_torch_blue", + "id": 204 + }, + { + "name": "minecraft:colored_torch_bp", + "id": 766 + }, + { + "name": "minecraft:colored_torch_green", + "id": -963 + }, + { + "name": "minecraft:colored_torch_purple", + "id": -964 + }, + { + "name": "minecraft:colored_torch_red", + "id": 202 + }, + { + "name": "minecraft:colored_torch_rg", + "id": 765 + }, + { + "name": "minecraft:command_block", + "id": 137 + }, + { + "name": "minecraft:command_block_minecart", + "id": 580 + }, + { + "name": "minecraft:comparator", + "id": 539 + }, + { + "name": "minecraft:compass", + "id": 400 + }, + { + "name": "minecraft:composter", + "id": -213 + }, + { + "name": "minecraft:compound", + "id": 614 + }, + { + "name": "minecraft:compound_creator", + "id": 238 + }, + { + "name": "minecraft:concrete", + "id": 753 + }, + { + "name": "minecraft:concrete_powder", + "id": 754 + }, + { + "name": "minecraft:conduit", + "id": -157 + }, + { + "name": "minecraft:cooked_beef", + "id": 279 + }, + { + "name": "minecraft:cooked_chicken", + "id": 281 + }, + { + "name": "minecraft:cooked_cod", + "id": 273 + }, + { + "name": "minecraft:cooked_mutton", + "id": 568 + }, + { + "name": "minecraft:cooked_porkchop", + "id": 268 + }, + { + "name": "minecraft:cooked_rabbit", + "id": 294 + }, + { + "name": "minecraft:cooked_salmon", + "id": 274 + }, + { + "name": "minecraft:cookie", + "id": 276 + }, + { + "name": "minecraft:copper_block", + "id": -340 + }, + { + "name": "minecraft:copper_bulb", + "id": -776 + }, + { + "name": "minecraft:copper_door", + "id": -784 + }, + { + "name": "minecraft:copper_grate", + "id": -768 + }, + { + "name": "minecraft:copper_ingot", + "id": 521 + }, + { + "name": "minecraft:copper_ore", + "id": -311 + }, + { + "name": "minecraft:copper_trapdoor", + "id": -792 + }, + { + "name": "minecraft:coral", + "id": 749 + }, + { + "name": "minecraft:coral_block", + "id": 731 + }, + { + "name": "minecraft:coral_fan", + "id": 740 + }, + { + "name": "minecraft:coral_fan_dead", + "id": 741 + }, + { + "name": "minecraft:cornflower", + "id": -838 + }, + { + "name": "minecraft:cow_spawn_egg", + "id": 446 + }, + { + "name": "minecraft:cracked_deepslate_bricks", + "id": -410 + }, + { + "name": "minecraft:cracked_deepslate_tiles", + "id": -409 + }, + { + "name": "minecraft:cracked_nether_bricks", + "id": -303 + }, + { + "name": "minecraft:cracked_polished_blackstone_bricks", + "id": -280 + }, + { + "name": "minecraft:cracked_stone_bricks", + "id": -869 + }, + { + "name": "minecraft:crafter", + "id": -313 + }, + { + "name": "minecraft:crafting_table", + "id": 58 + }, + { + "name": "minecraft:creeper_banner_pattern", + "id": 599 + }, + { + "name": "minecraft:creeper_spawn_egg", + "id": 451 + }, + { + "name": "minecraft:crimson_button", + "id": -260 + }, + { + "name": "minecraft:crimson_door", + "id": 636 + }, + { + "name": "minecraft:crimson_double_slab", + "id": -266 + }, + { + "name": "minecraft:crimson_fence", + "id": -256 + }, + { + "name": "minecraft:crimson_fence_gate", + "id": -258 + }, + { + "name": "minecraft:crimson_fungus", + "id": -228 + }, + { + "name": "minecraft:crimson_hanging_sign", + "id": -506 + }, + { + "name": "minecraft:crimson_hyphae", + "id": -299 + }, + { + "name": "minecraft:crimson_nylium", + "id": -232 + }, + { + "name": "minecraft:crimson_planks", + "id": -242 + }, + { + "name": "minecraft:crimson_pressure_plate", + "id": -262 + }, + { + "name": "minecraft:crimson_roots", + "id": -223 + }, + { + "name": "minecraft:crimson_sign", + "id": 634 + }, + { + "name": "minecraft:crimson_slab", + "id": -264 + }, + { + "name": "minecraft:crimson_stairs", + "id": -254 + }, + { + "name": "minecraft:crimson_standing_sign", + "id": -250 + }, + { + "name": "minecraft:crimson_stem", + "id": -225 + }, + { + "name": "minecraft:crimson_trapdoor", + "id": -246 + }, + { + "name": "minecraft:crimson_wall_sign", + "id": -252 + }, + { + "name": "minecraft:crossbow", + "id": 592 + }, + { + "name": "minecraft:crying_obsidian", + "id": -289 + }, + { + "name": "minecraft:cut_copper", + "id": -347 + }, + { + "name": "minecraft:cut_copper_slab", + "id": -361 + }, + { + "name": "minecraft:cut_copper_stairs", + "id": -354 + }, + { + "name": "minecraft:cut_red_sandstone", + "id": -957 + }, + { + "name": "minecraft:cut_red_sandstone_double_slab", + "id": -928 + }, + { + "name": "minecraft:cut_red_sandstone_slab", + "id": -901 + }, + { + "name": "minecraft:cut_sandstone", + "id": -945 + }, + { + "name": "minecraft:cut_sandstone_double_slab", + "id": -927 + }, + { + "name": "minecraft:cut_sandstone_slab", + "id": -900 + }, + { + "name": "minecraft:cyan_candle", + "id": -422 + }, + { + "name": "minecraft:cyan_candle_cake", + "id": -439 + }, + { + "name": "minecraft:cyan_carpet", + "id": -605 + }, + { + "name": "minecraft:cyan_concrete", + "id": -636 + }, + { + "name": "minecraft:cyan_concrete_powder", + "id": -717 + }, + { + "name": "minecraft:cyan_dye", + "id": 410 + }, + { + "name": "minecraft:cyan_glazed_terracotta", + "id": 229 + }, + { + "name": "minecraft:cyan_shulker_box", + "id": -621 + }, + { + "name": "minecraft:cyan_stained_glass", + "id": -681 + }, + { + "name": "minecraft:cyan_stained_glass_pane", + "id": -651 + }, + { + "name": "minecraft:cyan_terracotta", + "id": -732 + }, + { + "name": "minecraft:cyan_wool", + "id": -561 + }, + { + "name": "minecraft:damaged_anvil", + "id": -960 + }, + { + "name": "minecraft:dandelion", + "id": 37 + }, + { + "name": "minecraft:danger_pottery_sherd", + "id": 682 + }, + { + "name": "minecraft:dark_oak_boat", + "id": 389 + }, + { + "name": "minecraft:dark_oak_button", + "id": -142 + }, + { + "name": "minecraft:dark_oak_chest_boat", + "id": 663 + }, + { + "name": "minecraft:dark_oak_door", + "id": 574 + }, + { + "name": "minecraft:dark_oak_double_slab", + "id": -813 + }, + { + "name": "minecraft:dark_oak_fence", + "id": -577 + }, + { + "name": "minecraft:dark_oak_fence_gate", + "id": 186 + }, + { + "name": "minecraft:dark_oak_hanging_sign", + "id": -505 + }, + { + "name": "minecraft:dark_oak_leaves", + "id": -803 + }, + { + "name": "minecraft:dark_oak_log", + "id": -572 + }, + { + "name": "minecraft:dark_oak_planks", + "id": -743 + }, + { + "name": "minecraft:dark_oak_pressure_plate", + "id": -152 + }, + { + "name": "minecraft:dark_oak_sapling", + "id": -829 + }, + { + "name": "minecraft:dark_oak_sign", + "id": 597 + }, + { + "name": "minecraft:dark_oak_slab", + "id": -808 + }, + { + "name": "minecraft:dark_oak_stairs", + "id": 164 + }, + { + "name": "minecraft:dark_oak_trapdoor", + "id": -147 + }, + { + "name": "minecraft:dark_oak_wood", + "id": -818 + }, + { + "name": "minecraft:dark_prismarine", + "id": -947 + }, + { + "name": "minecraft:dark_prismarine_double_slab", + "id": -913 + }, + { + "name": "minecraft:dark_prismarine_slab", + "id": -886 + }, + { + "name": "minecraft:dark_prismarine_stairs", + "id": -3 + }, + { + "name": "minecraft:darkoak_standing_sign", + "id": -192 + }, + { + "name": "minecraft:darkoak_wall_sign", + "id": -193 + }, + { + "name": "minecraft:daylight_detector", + "id": 151 + }, + { + "name": "minecraft:daylight_detector_inverted", + "id": 178 + }, + { + "name": "minecraft:dead_brain_coral", + "id": -586 + }, + { + "name": "minecraft:dead_brain_coral_block", + "id": -854 + }, + { + "name": "minecraft:dead_brain_coral_fan", + "id": -844 + }, + { + "name": "minecraft:dead_brain_coral_wall_fan", + "id": -906 + }, + { + "name": "minecraft:dead_bubble_coral", + "id": -587 + }, + { + "name": "minecraft:dead_bubble_coral_block", + "id": -855 + }, + { + "name": "minecraft:dead_bubble_coral_fan", + "id": -845 + }, + { + "name": "minecraft:dead_bubble_coral_wall_fan", + "id": -908 + }, + { + "name": "minecraft:dead_fire_coral", + "id": -588 + }, + { + "name": "minecraft:dead_fire_coral_block", + "id": -856 + }, + { + "name": "minecraft:dead_fire_coral_fan", + "id": -846 + }, + { + "name": "minecraft:dead_fire_coral_wall_fan", + "id": -909 + }, + { + "name": "minecraft:dead_horn_coral", + "id": -589 + }, + { + "name": "minecraft:dead_horn_coral_block", + "id": -857 + }, + { + "name": "minecraft:dead_horn_coral_fan", + "id": -847 + }, + { + "name": "minecraft:dead_horn_coral_wall_fan", + "id": -910 + }, + { + "name": "minecraft:dead_tube_coral", + "id": -585 + }, + { + "name": "minecraft:dead_tube_coral_block", + "id": -853 + }, + { + "name": "minecraft:dead_tube_coral_fan", + "id": -134 + }, + { + "name": "minecraft:dead_tube_coral_wall_fan", + "id": -905 + }, + { + "name": "minecraft:deadbush", + "id": 32 + }, + { + "name": "minecraft:decorated_pot", + "id": -551 + }, + { + "name": "minecraft:deepslate", + "id": -378 + }, + { + "name": "minecraft:deepslate_brick_double_slab", + "id": -399 + }, + { + "name": "minecraft:deepslate_brick_slab", + "id": -392 + }, + { + "name": "minecraft:deepslate_brick_stairs", + "id": -393 + }, + { + "name": "minecraft:deepslate_brick_wall", + "id": -394 + }, + { + "name": "minecraft:deepslate_bricks", + "id": -391 + }, + { + "name": "minecraft:deepslate_coal_ore", + "id": -406 + }, + { + "name": "minecraft:deepslate_copper_ore", + "id": -408 + }, + { + "name": "minecraft:deepslate_diamond_ore", + "id": -405 + }, + { + "name": "minecraft:deepslate_emerald_ore", + "id": -407 + }, + { + "name": "minecraft:deepslate_gold_ore", + "id": -402 + }, + { + "name": "minecraft:deepslate_iron_ore", + "id": -401 + }, + { + "name": "minecraft:deepslate_lapis_ore", + "id": -400 + }, + { + "name": "minecraft:deepslate_redstone_ore", + "id": -403 + }, + { + "name": "minecraft:deepslate_tile_double_slab", + "id": -398 + }, + { + "name": "minecraft:deepslate_tile_slab", + "id": -388 + }, + { + "name": "minecraft:deepslate_tile_stairs", + "id": -389 + }, + { + "name": "minecraft:deepslate_tile_wall", + "id": -390 + }, + { + "name": "minecraft:deepslate_tiles", + "id": -387 + }, + { + "name": "minecraft:deny", + "id": 211 + }, + { + "name": "minecraft:deprecated_anvil", + "id": -961 + }, + { + "name": "minecraft:deprecated_purpur_block_1", + "id": -950 + }, + { + "name": "minecraft:deprecated_purpur_block_2", + "id": -952 + }, + { + "name": "minecraft:detector_rail", + "id": 28 + }, + { + "name": "minecraft:diamond", + "id": 311 + }, + { + "name": "minecraft:diamond_axe", + "id": 326 + }, + { + "name": "minecraft:diamond_block", + "id": 57 + }, + { + "name": "minecraft:diamond_boots", + "id": 358 + }, + { + "name": "minecraft:diamond_chestplate", + "id": 356 + }, + { + "name": "minecraft:diamond_helmet", + "id": 355 + }, + { + "name": "minecraft:diamond_hoe", + "id": 340 + }, + { + "name": "minecraft:diamond_horse_armor", + "id": 550 + }, + { + "name": "minecraft:diamond_leggings", + "id": 357 + }, + { + "name": "minecraft:diamond_ore", + "id": 56 + }, + { + "name": "minecraft:diamond_pickaxe", + "id": 325 + }, + { + "name": "minecraft:diamond_shovel", + "id": 324 + }, + { + "name": "minecraft:diamond_sword", + "id": 323 + }, + { + "name": "minecraft:diorite", + "id": -592 + }, + { + "name": "minecraft:diorite_double_slab", + "id": -921 + }, + { + "name": "minecraft:diorite_slab", + "id": -894 + }, + { + "name": "minecraft:diorite_stairs", + "id": -170 + }, + { + "name": "minecraft:diorite_wall", + "id": -973 + }, + { + "name": "minecraft:dirt", + "id": 3 + }, + { + "name": "minecraft:dirt_with_roots", + "id": -318 + }, + { + "name": "minecraft:disc_fragment_5", + "id": 657 + }, + { + "name": "minecraft:dispenser", + "id": 23 + }, + { + "name": "minecraft:dolphin_spawn_egg", + "id": 495 + }, + { + "name": "minecraft:donkey_spawn_egg", + "id": 476 + }, + { + "name": "minecraft:double_cut_copper_slab", + "id": -368 + }, + { + "name": "minecraft:double_plant", + "id": 747 + }, + { + "name": "minecraft:double_stone_block_slab", + "id": 736 + }, + { + "name": "minecraft:double_stone_block_slab2", + "id": 737 + }, + { + "name": "minecraft:double_stone_block_slab3", + "id": 738 + }, + { + "name": "minecraft:double_stone_block_slab4", + "id": 739 + }, + { + "name": "minecraft:dragon_breath", + "id": 577 + }, + { + "name": "minecraft:dragon_egg", + "id": 122 + }, + { + "name": "minecraft:dried_kelp", + "id": 275 + }, + { + "name": "minecraft:dried_kelp_block", + "id": -139 + }, + { + "name": "minecraft:dripstone_block", + "id": -317 + }, + { + "name": "minecraft:dropper", + "id": 125 + }, + { + "name": "minecraft:drowned_spawn_egg", + "id": 494 + }, + { + "name": "minecraft:dune_armor_trim_smithing_template", + "id": 702 + }, + { + "name": "minecraft:dye", + "id": 769 + }, + { + "name": "minecraft:echo_shard", + "id": 667 + }, + { + "name": "minecraft:egg", + "id": 399 + }, + { + "name": "minecraft:elder_guardian_spawn_egg", + "id": 482 + }, + { + "name": "minecraft:element_0", + "id": 36 + }, + { + "name": "minecraft:element_1", + "id": -12 + }, + { + "name": "minecraft:element_10", + "id": -21 + }, + { + "name": "minecraft:element_100", + "id": -111 + }, + { + "name": "minecraft:element_101", + "id": -112 + }, + { + "name": "minecraft:element_102", + "id": -113 + }, + { + "name": "minecraft:element_103", + "id": -114 + }, + { + "name": "minecraft:element_104", + "id": -115 + }, + { + "name": "minecraft:element_105", + "id": -116 + }, + { + "name": "minecraft:element_106", + "id": -117 + }, + { + "name": "minecraft:element_107", + "id": -118 + }, + { + "name": "minecraft:element_108", + "id": -119 + }, + { + "name": "minecraft:element_109", + "id": -120 + }, + { + "name": "minecraft:element_11", + "id": -22 + }, + { + "name": "minecraft:element_110", + "id": -121 + }, + { + "name": "minecraft:element_111", + "id": -122 + }, + { + "name": "minecraft:element_112", + "id": -123 + }, + { + "name": "minecraft:element_113", + "id": -124 + }, + { + "name": "minecraft:element_114", + "id": -125 + }, + { + "name": "minecraft:element_115", + "id": -126 + }, + { + "name": "minecraft:element_116", + "id": -127 + }, + { + "name": "minecraft:element_117", + "id": -128 + }, + { + "name": "minecraft:element_118", + "id": -129 + }, + { + "name": "minecraft:element_12", + "id": -23 + }, + { + "name": "minecraft:element_13", + "id": -24 + }, + { + "name": "minecraft:element_14", + "id": -25 + }, + { + "name": "minecraft:element_15", + "id": -26 + }, + { + "name": "minecraft:element_16", + "id": -27 + }, + { + "name": "minecraft:element_17", + "id": -28 + }, + { + "name": "minecraft:element_18", + "id": -29 + }, + { + "name": "minecraft:element_19", + "id": -30 + }, + { + "name": "minecraft:element_2", + "id": -13 + }, + { + "name": "minecraft:element_20", + "id": -31 + }, + { + "name": "minecraft:element_21", + "id": -32 + }, + { + "name": "minecraft:element_22", + "id": -33 + }, + { + "name": "minecraft:element_23", + "id": -34 + }, + { + "name": "minecraft:element_24", + "id": -35 + }, + { + "name": "minecraft:element_25", + "id": -36 + }, + { + "name": "minecraft:element_26", + "id": -37 + }, + { + "name": "minecraft:element_27", + "id": -38 + }, + { + "name": "minecraft:element_28", + "id": -39 + }, + { + "name": "minecraft:element_29", + "id": -40 + }, + { + "name": "minecraft:element_3", + "id": -14 + }, + { + "name": "minecraft:element_30", + "id": -41 + }, + { + "name": "minecraft:element_31", + "id": -42 + }, + { + "name": "minecraft:element_32", + "id": -43 + }, + { + "name": "minecraft:element_33", + "id": -44 + }, + { + "name": "minecraft:element_34", + "id": -45 + }, + { + "name": "minecraft:element_35", + "id": -46 + }, + { + "name": "minecraft:element_36", + "id": -47 + }, + { + "name": "minecraft:element_37", + "id": -48 + }, + { + "name": "minecraft:element_38", + "id": -49 + }, + { + "name": "minecraft:element_39", + "id": -50 + }, + { + "name": "minecraft:element_4", + "id": -15 + }, + { + "name": "minecraft:element_40", + "id": -51 + }, + { + "name": "minecraft:element_41", + "id": -52 + }, + { + "name": "minecraft:element_42", + "id": -53 + }, + { + "name": "minecraft:element_43", + "id": -54 + }, + { + "name": "minecraft:element_44", + "id": -55 + }, + { + "name": "minecraft:element_45", + "id": -56 + }, + { + "name": "minecraft:element_46", + "id": -57 + }, + { + "name": "minecraft:element_47", + "id": -58 + }, + { + "name": "minecraft:element_48", + "id": -59 + }, + { + "name": "minecraft:element_49", + "id": -60 + }, + { + "name": "minecraft:element_5", + "id": -16 + }, + { + "name": "minecraft:element_50", + "id": -61 + }, + { + "name": "minecraft:element_51", + "id": -62 + }, + { + "name": "minecraft:element_52", + "id": -63 + }, + { + "name": "minecraft:element_53", + "id": -64 + }, + { + "name": "minecraft:element_54", + "id": -65 + }, + { + "name": "minecraft:element_55", + "id": -66 + }, + { + "name": "minecraft:element_56", + "id": -67 + }, + { + "name": "minecraft:element_57", + "id": -68 + }, + { + "name": "minecraft:element_58", + "id": -69 + }, + { + "name": "minecraft:element_59", + "id": -70 + }, + { + "name": "minecraft:element_6", + "id": -17 + }, + { + "name": "minecraft:element_60", + "id": -71 + }, + { + "name": "minecraft:element_61", + "id": -72 + }, + { + "name": "minecraft:element_62", + "id": -73 + }, + { + "name": "minecraft:element_63", + "id": -74 + }, + { + "name": "minecraft:element_64", + "id": -75 + }, + { + "name": "minecraft:element_65", + "id": -76 + }, + { + "name": "minecraft:element_66", + "id": -77 + }, + { + "name": "minecraft:element_67", + "id": -78 + }, + { + "name": "minecraft:element_68", + "id": -79 + }, + { + "name": "minecraft:element_69", + "id": -80 + }, + { + "name": "minecraft:element_7", + "id": -18 + }, + { + "name": "minecraft:element_70", + "id": -81 + }, + { + "name": "minecraft:element_71", + "id": -82 + }, + { + "name": "minecraft:element_72", + "id": -83 + }, + { + "name": "minecraft:element_73", + "id": -84 + }, + { + "name": "minecraft:element_74", + "id": -85 + }, + { + "name": "minecraft:element_75", + "id": -86 + }, + { + "name": "minecraft:element_76", + "id": -87 + }, + { + "name": "minecraft:element_77", + "id": -88 + }, + { + "name": "minecraft:element_78", + "id": -89 + }, + { + "name": "minecraft:element_79", + "id": -90 + }, + { + "name": "minecraft:element_8", + "id": -19 + }, + { + "name": "minecraft:element_80", + "id": -91 + }, + { + "name": "minecraft:element_81", + "id": -92 + }, + { + "name": "minecraft:element_82", + "id": -93 + }, + { + "name": "minecraft:element_83", + "id": -94 + }, + { + "name": "minecraft:element_84", + "id": -95 + }, + { + "name": "minecraft:element_85", + "id": -96 + }, + { + "name": "minecraft:element_86", + "id": -97 + }, + { + "name": "minecraft:element_87", + "id": -98 + }, + { + "name": "minecraft:element_88", + "id": -99 + }, + { + "name": "minecraft:element_89", + "id": -100 + }, + { + "name": "minecraft:element_9", + "id": -20 + }, + { + "name": "minecraft:element_90", + "id": -101 + }, + { + "name": "minecraft:element_91", + "id": -102 + }, + { + "name": "minecraft:element_92", + "id": -103 + }, + { + "name": "minecraft:element_93", + "id": -104 + }, + { + "name": "minecraft:element_94", + "id": -105 + }, + { + "name": "minecraft:element_95", + "id": -106 + }, + { + "name": "minecraft:element_96", + "id": -107 + }, + { + "name": "minecraft:element_97", + "id": -108 + }, + { + "name": "minecraft:element_98", + "id": -109 + }, + { + "name": "minecraft:element_99", + "id": -110 + }, + { + "name": "minecraft:element_constructor", + "id": -987 + }, + { + "name": "minecraft:elytra", + "id": 581 + }, + { + "name": "minecraft:emerald", + "id": 529 + }, + { + "name": "minecraft:emerald_block", + "id": 133 + }, + { + "name": "minecraft:emerald_ore", + "id": 129 + }, + { + "name": "minecraft:empty_map", + "id": 532 + }, + { + "name": "minecraft:enchanted_book", + "id": 538 + }, + { + "name": "minecraft:enchanted_golden_apple", + "id": 264 + }, + { + "name": "minecraft:enchanting_table", + "id": 116 + }, + { + "name": "minecraft:end_brick_stairs", + "id": -178 + }, + { + "name": "minecraft:end_bricks", + "id": 206 + }, + { + "name": "minecraft:end_crystal", + "id": 772 + }, + { + "name": "minecraft:end_gateway", + "id": 209 + }, + { + "name": "minecraft:end_portal", + "id": 119 + }, + { + "name": "minecraft:end_portal_frame", + "id": 120 + }, + { + "name": "minecraft:end_rod", + "id": 208 + }, + { + "name": "minecraft:end_stone", + "id": 121 + }, + { + "name": "minecraft:end_stone_brick_double_slab", + "id": -167 + }, + { + "name": "minecraft:end_stone_brick_slab", + "id": -162 + }, + { + "name": "minecraft:end_stone_brick_wall", + "id": -980 + }, + { + "name": "minecraft:ender_chest", + "id": 130 + }, + { + "name": "minecraft:ender_dragon_spawn_egg", + "id": 518 + }, + { + "name": "minecraft:ender_eye", + "id": 443 + }, + { + "name": "minecraft:ender_pearl", + "id": 431 + }, + { + "name": "minecraft:enderman_spawn_egg", + "id": 452 + }, + { + "name": "minecraft:endermite_spawn_egg", + "id": 470 + }, + { + "name": "minecraft:evoker_spawn_egg", + "id": 486 + }, + { + "name": "minecraft:experience_bottle", + "id": 525 + }, + { + "name": "minecraft:explorer_pottery_sherd", + "id": 683 + }, + { + "name": "minecraft:exposed_chiseled_copper", + "id": -761 + }, + { + "name": "minecraft:exposed_copper", + "id": -341 + }, + { + "name": "minecraft:exposed_copper_bulb", + "id": -777 + }, + { + "name": "minecraft:exposed_copper_door", + "id": -785 + }, + { + "name": "minecraft:exposed_copper_grate", + "id": -769 + }, + { + "name": "minecraft:exposed_copper_trapdoor", + "id": -793 + }, + { + "name": "minecraft:exposed_cut_copper", + "id": -348 + }, + { + "name": "minecraft:exposed_cut_copper_slab", + "id": -362 + }, + { + "name": "minecraft:exposed_cut_copper_stairs", + "id": -355 + }, + { + "name": "minecraft:exposed_double_cut_copper_slab", + "id": -369 + }, + { + "name": "minecraft:eye_armor_trim_smithing_template", + "id": 706 + }, + { + "name": "minecraft:farmland", + "id": 60 + }, + { + "name": "minecraft:feather", + "id": 335 + }, + { + "name": "minecraft:fence", + "id": 729 + }, + { + "name": "minecraft:fence_gate", + "id": 107 + }, + { + "name": "minecraft:fermented_spider_eye", + "id": 438 + }, + { + "name": "minecraft:fern", + "id": -848 + }, + { + "name": "minecraft:field_masoned_banner_pattern", + "id": 602 + }, + { + "name": "minecraft:filled_map", + "id": 429 + }, + { + "name": "minecraft:fire", + "id": 51 + }, + { + "name": "minecraft:fire_charge", + "id": 526 + }, + { + "name": "minecraft:fire_coral", + "id": -583 + }, + { + "name": "minecraft:fire_coral_block", + "id": -851 + }, + { + "name": "minecraft:fire_coral_fan", + "id": -842 + }, + { + "name": "minecraft:fire_coral_wall_fan", + "id": -907 + }, + { + "name": "minecraft:firework_rocket", + "id": 536 + }, + { + "name": "minecraft:firework_star", + "id": 537 + }, + { + "name": "minecraft:fishing_rod", + "id": 401 + }, + { + "name": "minecraft:fletching_table", + "id": -201 + }, + { + "name": "minecraft:flint", + "id": 364 + }, + { + "name": "minecraft:flint_and_steel", + "id": 306 + }, + { + "name": "minecraft:flow_armor_trim_smithing_template", + "id": 717 + }, + { + "name": "minecraft:flow_banner_pattern", + "id": 606 + }, + { + "name": "minecraft:flow_pottery_sherd", + "id": 684 + }, + { + "name": "minecraft:flower_banner_pattern", + "id": 598 + }, + { + "name": "minecraft:flower_pot", + "id": 531 + }, + { + "name": "minecraft:flowering_azalea", + "id": -338 + }, + { + "name": "minecraft:flowing_lava", + "id": 10 + }, + { + "name": "minecraft:flowing_water", + "id": 8 + }, + { + "name": "minecraft:fox_spawn_egg", + "id": 501 + }, + { + "name": "minecraft:frame", + "id": 530 + }, + { + "name": "minecraft:friend_pottery_sherd", + "id": 685 + }, + { + "name": "minecraft:frog_spawn", + "id": -468 + }, + { + "name": "minecraft:frog_spawn_egg", + "id": 648 + }, + { + "name": "minecraft:frosted_ice", + "id": 207 + }, + { + "name": "minecraft:furnace", + "id": 61 + }, + { + "name": "minecraft:ghast_spawn_egg", + "id": 464 + }, + { + "name": "minecraft:ghast_tear", + "id": 434 + }, + { + "name": "minecraft:gilded_blackstone", + "id": -281 + }, + { + "name": "minecraft:glass", + "id": 20 + }, + { + "name": "minecraft:glass_bottle", + "id": 437 + }, + { + "name": "minecraft:glass_pane", + "id": 102 + }, + { + "name": "minecraft:glistering_melon_slice", + "id": 444 + }, + { + "name": "minecraft:globe_banner_pattern", + "id": 605 + }, + { + "name": "minecraft:glow_berries", + "id": 773 + }, + { + "name": "minecraft:glow_frame", + "id": 643 + }, + { + "name": "minecraft:glow_ink_sac", + "id": 520 + }, + { + "name": "minecraft:glow_lichen", + "id": -411 + }, + { + "name": "minecraft:glow_squid_spawn_egg", + "id": 515 + }, + { + "name": "minecraft:glow_stick", + "id": 621 + }, + { + "name": "minecraft:glowingobsidian", + "id": 246 + }, + { + "name": "minecraft:glowstone", + "id": 89 + }, + { + "name": "minecraft:glowstone_dust", + "id": 403 + }, + { + "name": "minecraft:goat_horn", + "id": 647 + }, + { + "name": "minecraft:goat_spawn_egg", + "id": 514 + }, + { + "name": "minecraft:gold_block", + "id": 41 + }, + { + "name": "minecraft:gold_ingot", + "id": 313 + }, + { + "name": "minecraft:gold_nugget", + "id": 435 + }, + { + "name": "minecraft:gold_ore", + "id": 14 + }, + { + "name": "minecraft:golden_apple", + "id": 263 + }, + { + "name": "minecraft:golden_axe", + "id": 333 + }, + { + "name": "minecraft:golden_boots", + "id": 362 + }, + { + "name": "minecraft:golden_carrot", + "id": 288 + }, + { + "name": "minecraft:golden_chestplate", + "id": 360 + }, + { + "name": "minecraft:golden_helmet", + "id": 359 + }, + { + "name": "minecraft:golden_hoe", + "id": 341 + }, + { + "name": "minecraft:golden_horse_armor", + "id": 549 + }, + { + "name": "minecraft:golden_leggings", + "id": 361 + }, + { + "name": "minecraft:golden_pickaxe", + "id": 332 + }, + { + "name": "minecraft:golden_rail", + "id": 27 + }, + { + "name": "minecraft:golden_shovel", + "id": 331 + }, + { + "name": "minecraft:golden_sword", + "id": 330 + }, + { + "name": "minecraft:granite", + "id": -590 + }, + { + "name": "minecraft:granite_double_slab", + "id": -923 + }, + { + "name": "minecraft:granite_slab", + "id": -896 + }, + { + "name": "minecraft:granite_stairs", + "id": -169 + }, + { + "name": "minecraft:granite_wall", + "id": -972 + }, + { + "name": "minecraft:grass_block", + "id": 2 + }, + { + "name": "minecraft:grass_path", + "id": 198 + }, + { + "name": "minecraft:gravel", + "id": 13 + }, + { + "name": "minecraft:gray_candle", + "id": -420 + }, + { + "name": "minecraft:gray_candle_cake", + "id": -437 + }, + { + "name": "minecraft:gray_carpet", + "id": -603 + }, + { + "name": "minecraft:gray_concrete", + "id": -634 + }, + { + "name": "minecraft:gray_concrete_powder", + "id": -715 + }, + { + "name": "minecraft:gray_dye", + "id": 412 + }, + { + "name": "minecraft:gray_glazed_terracotta", + "id": 227 + }, + { + "name": "minecraft:gray_shulker_box", + "id": -619 + }, + { + "name": "minecraft:gray_stained_glass", + "id": -679 + }, + { + "name": "minecraft:gray_stained_glass_pane", + "id": -649 + }, + { + "name": "minecraft:gray_terracotta", + "id": -730 + }, + { + "name": "minecraft:gray_wool", + "id": -553 + }, + { + "name": "minecraft:green_candle", + "id": -426 + }, + { + "name": "minecraft:green_candle_cake", + "id": -443 + }, + { + "name": "minecraft:green_carpet", + "id": -609 + }, + { + "name": "minecraft:green_concrete", + "id": -640 + }, + { + "name": "minecraft:green_concrete_powder", + "id": -721 + }, + { + "name": "minecraft:green_dye", + "id": 406 + }, + { + "name": "minecraft:green_glazed_terracotta", + "id": 233 + }, + { + "name": "minecraft:green_shulker_box", + "id": -625 + }, + { + "name": "minecraft:green_stained_glass", + "id": -685 + }, + { + "name": "minecraft:green_stained_glass_pane", + "id": -655 + }, + { + "name": "minecraft:green_terracotta", + "id": -736 + }, + { + "name": "minecraft:green_wool", + "id": -560 + }, + { + "name": "minecraft:grindstone", + "id": -195 + }, + { + "name": "minecraft:guardian_spawn_egg", + "id": 471 + }, + { + "name": "minecraft:gunpowder", + "id": 336 + }, + { + "name": "minecraft:guster_banner_pattern", + "id": 607 + }, + { + "name": "minecraft:guster_pottery_sherd", + "id": 686 + }, + { + "name": "minecraft:hanging_roots", + "id": -319 + }, + { + "name": "minecraft:hard_black_stained_glass", + "id": -702 + }, + { + "name": "minecraft:hard_black_stained_glass_pane", + "id": -672 + }, + { + "name": "minecraft:hard_blue_stained_glass", + "id": -698 + }, + { + "name": "minecraft:hard_blue_stained_glass_pane", + "id": -668 + }, + { + "name": "minecraft:hard_brown_stained_glass", + "id": -699 + }, + { + "name": "minecraft:hard_brown_stained_glass_pane", + "id": -669 + }, + { + "name": "minecraft:hard_cyan_stained_glass", + "id": -696 + }, + { + "name": "minecraft:hard_cyan_stained_glass_pane", + "id": -666 + }, + { + "name": "minecraft:hard_glass", + "id": 253 + }, + { + "name": "minecraft:hard_glass_pane", + "id": 190 + }, + { + "name": "minecraft:hard_gray_stained_glass", + "id": -694 + }, + { + "name": "minecraft:hard_gray_stained_glass_pane", + "id": -664 + }, + { + "name": "minecraft:hard_green_stained_glass", + "id": -700 + }, + { + "name": "minecraft:hard_green_stained_glass_pane", + "id": -670 + }, + { + "name": "minecraft:hard_light_blue_stained_glass", + "id": -690 + }, + { + "name": "minecraft:hard_light_blue_stained_glass_pane", + "id": -660 + }, + { + "name": "minecraft:hard_light_gray_stained_glass", + "id": -695 + }, + { + "name": "minecraft:hard_light_gray_stained_glass_pane", + "id": -665 + }, + { + "name": "minecraft:hard_lime_stained_glass", + "id": -692 + }, + { + "name": "minecraft:hard_lime_stained_glass_pane", + "id": -662 + }, + { + "name": "minecraft:hard_magenta_stained_glass", + "id": -689 + }, + { + "name": "minecraft:hard_magenta_stained_glass_pane", + "id": -659 + }, + { + "name": "minecraft:hard_orange_stained_glass", + "id": -688 + }, + { + "name": "minecraft:hard_orange_stained_glass_pane", + "id": -658 + }, + { + "name": "minecraft:hard_pink_stained_glass", + "id": -693 + }, + { + "name": "minecraft:hard_pink_stained_glass_pane", + "id": -663 + }, + { + "name": "minecraft:hard_purple_stained_glass", + "id": -697 + }, + { + "name": "minecraft:hard_purple_stained_glass_pane", + "id": -667 + }, + { + "name": "minecraft:hard_red_stained_glass", + "id": -701 + }, + { + "name": "minecraft:hard_red_stained_glass_pane", + "id": -671 + }, + { + "name": "minecraft:hard_stained_glass", + "id": 763 + }, + { + "name": "minecraft:hard_stained_glass_pane", + "id": 764 + }, + { + "name": "minecraft:hard_white_stained_glass", + "id": 254 + }, + { + "name": "minecraft:hard_white_stained_glass_pane", + "id": 191 + }, + { + "name": "minecraft:hard_yellow_stained_glass", + "id": -691 + }, + { + "name": "minecraft:hard_yellow_stained_glass_pane", + "id": -661 + }, + { + "name": "minecraft:hardened_clay", + "id": 172 + }, + { + "name": "minecraft:hay_block", + "id": 170 + }, + { + "name": "minecraft:heart_of_the_sea", + "id": 588 + }, + { + "name": "minecraft:heart_pottery_sherd", + "id": 687 + }, + { + "name": "minecraft:heartbreak_pottery_sherd", + "id": 688 + }, + { + "name": "minecraft:heavy_core", + "id": -316 + }, + { + "name": "minecraft:heavy_weighted_pressure_plate", + "id": 148 + }, + { + "name": "minecraft:hoglin_spawn_egg", + "id": 507 + }, + { + "name": "minecraft:honey_block", + "id": -220 + }, + { + "name": "minecraft:honey_bottle", + "id": 611 + }, + { + "name": "minecraft:honeycomb", + "id": 610 + }, + { + "name": "minecraft:honeycomb_block", + "id": -221 + }, + { + "name": "minecraft:hopper", + "id": 544 + }, + { + "name": "minecraft:hopper_minecart", + "id": 543 + }, + { + "name": "minecraft:horn_coral", + "id": -584 + }, + { + "name": "minecraft:horn_coral_block", + "id": -852 + }, + { + "name": "minecraft:horn_coral_fan", + "id": -843 + }, + { + "name": "minecraft:horn_coral_wall_fan", + "id": -137 + }, + { + "name": "minecraft:horse_spawn_egg", + "id": 468 + }, + { + "name": "minecraft:host_armor_trim_smithing_template", + "id": 716 + }, + { + "name": "minecraft:howl_pottery_sherd", + "id": 689 + }, + { + "name": "minecraft:husk_spawn_egg", + "id": 474 + }, + { + "name": "minecraft:ice", + "id": 79 + }, + { + "name": "minecraft:ice_bomb", + "id": 615 + }, + { + "name": "minecraft:infested_chiseled_stone_bricks", + "id": -862 + }, + { + "name": "minecraft:infested_cobblestone", + "id": -858 + }, + { + "name": "minecraft:infested_cracked_stone_bricks", + "id": -861 + }, + { + "name": "minecraft:infested_deepslate", + "id": -454 + }, + { + "name": "minecraft:infested_mossy_stone_bricks", + "id": -860 + }, + { + "name": "minecraft:infested_stone", + "id": 97 + }, + { + "name": "minecraft:infested_stone_bricks", + "id": -859 + }, + { + "name": "minecraft:info_update", + "id": 248 + }, + { + "name": "minecraft:info_update2", + "id": 249 + }, + { + "name": "minecraft:ink_sac", + "id": 422 + }, + { + "name": "minecraft:invisible_bedrock", + "id": 95 + }, + { + "name": "minecraft:iron_axe", + "id": 305 + }, + { + "name": "minecraft:iron_bars", + "id": 101 + }, + { + "name": "minecraft:iron_block", + "id": 42 + }, + { + "name": "minecraft:iron_boots", + "id": 354 + }, + { + "name": "minecraft:iron_chestplate", + "id": 352 + }, + { + "name": "minecraft:iron_door", + "id": 380 + }, + { + "name": "minecraft:iron_golem_spawn_egg", + "id": 516 + }, + { + "name": "minecraft:iron_helmet", + "id": 351 + }, + { + "name": "minecraft:iron_hoe", + "id": 339 + }, + { + "name": "minecraft:iron_horse_armor", + "id": 548 + }, + { + "name": "minecraft:iron_ingot", + "id": 312 + }, + { + "name": "minecraft:iron_leggings", + "id": 353 + }, + { + "name": "minecraft:iron_nugget", + "id": 586 + }, + { + "name": "minecraft:iron_ore", + "id": 15 + }, + { + "name": "minecraft:iron_pickaxe", + "id": 304 + }, + { + "name": "minecraft:iron_shovel", + "id": 303 + }, + { + "name": "minecraft:iron_sword", + "id": 314 + }, + { + "name": "minecraft:iron_trapdoor", + "id": 167 + }, + { + "name": "minecraft:item.acacia_door", + "id": 196 + }, + { + "name": "minecraft:item.bed", + "id": 26 + }, + { + "name": "minecraft:item.beetroot", + "id": 244 + }, + { + "name": "minecraft:item.birch_door", + "id": 194 + }, + { + "name": "minecraft:item.brewing_stand", + "id": 117 + }, + { + "name": "minecraft:item.cake", + "id": 92 + }, + { + "name": "minecraft:item.camera", + "id": 242 + }, + { + "name": "minecraft:item.campfire", + "id": -209 + }, + { + "name": "minecraft:item.cauldron", + "id": 118 + }, + { + "name": "minecraft:item.chain", + "id": -286 + }, + { + "name": "minecraft:item.crimson_door", + "id": -244 + }, + { + "name": "minecraft:item.dark_oak_door", + "id": 197 + }, + { + "name": "minecraft:item.flower_pot", + "id": 140 + }, + { + "name": "minecraft:item.frame", + "id": 199 + }, + { + "name": "minecraft:item.glow_frame", + "id": -339 + }, + { + "name": "minecraft:item.hopper", + "id": 154 + }, + { + "name": "minecraft:item.iron_door", + "id": 71 + }, + { + "name": "minecraft:item.jungle_door", + "id": 195 + }, + { + "name": "minecraft:item.kelp", + "id": -138 + }, + { + "name": "minecraft:item.mangrove_door", + "id": -493 + }, + { + "name": "minecraft:item.nether_sprouts", + "id": -238 + }, + { + "name": "minecraft:item.nether_wart", + "id": 115 + }, + { + "name": "minecraft:item.reeds", + "id": 83 + }, + { + "name": "minecraft:item.skull", + "id": 144 + }, + { + "name": "minecraft:item.soul_campfire", + "id": -290 + }, + { + "name": "minecraft:item.spruce_door", + "id": 193 + }, + { + "name": "minecraft:item.warped_door", + "id": -245 + }, + { + "name": "minecraft:item.wheat", + "id": 59 + }, + { + "name": "minecraft:item.wooden_door", + "id": 64 + }, + { + "name": "minecraft:jigsaw", + "id": -211 + }, + { + "name": "minecraft:jukebox", + "id": 84 + }, + { + "name": "minecraft:jungle_boat", + "id": 386 + }, + { + "name": "minecraft:jungle_button", + "id": -143 + }, + { + "name": "minecraft:jungle_chest_boat", + "id": 660 + }, + { + "name": "minecraft:jungle_door", + "id": 572 + }, + { + "name": "minecraft:jungle_double_slab", + "id": -811 + }, + { + "name": "minecraft:jungle_fence", + "id": -578 + }, + { + "name": "minecraft:jungle_fence_gate", + "id": 185 + }, + { + "name": "minecraft:jungle_hanging_sign", + "id": -503 + }, + { + "name": "minecraft:jungle_leaves", + "id": -802 + }, + { + "name": "minecraft:jungle_log", + "id": -571 + }, + { + "name": "minecraft:jungle_planks", + "id": -741 + }, + { + "name": "minecraft:jungle_pressure_plate", + "id": -153 + }, + { + "name": "minecraft:jungle_sapling", + "id": -827 + }, + { + "name": "minecraft:jungle_sign", + "id": 595 + }, + { + "name": "minecraft:jungle_slab", + "id": -806 + }, + { + "name": "minecraft:jungle_stairs", + "id": 136 + }, + { + "name": "minecraft:jungle_standing_sign", + "id": -188 + }, + { + "name": "minecraft:jungle_trapdoor", + "id": -148 + }, + { + "name": "minecraft:jungle_wall_sign", + "id": -189 + }, + { + "name": "minecraft:jungle_wood", + "id": -816 + }, + { + "name": "minecraft:kelp", + "id": 391 + }, + { + "name": "minecraft:lab_table", + "id": -988 + }, + { + "name": "minecraft:ladder", + "id": 65 + }, + { + "name": "minecraft:lantern", + "id": -208 + }, + { + "name": "minecraft:lapis_block", + "id": 22 + }, + { + "name": "minecraft:lapis_lazuli", + "id": 423 + }, + { + "name": "minecraft:lapis_ore", + "id": 21 + }, + { + "name": "minecraft:large_amethyst_bud", + "id": -330 + }, + { + "name": "minecraft:large_fern", + "id": -865 + }, + { + "name": "minecraft:lava", + "id": 11 + }, + { + "name": "minecraft:lava_bucket", + "id": 371 + }, + { + "name": "minecraft:lead", + "id": 564 + }, + { + "name": "minecraft:leather", + "id": 390 + }, + { + "name": "minecraft:leather_boots", + "id": 346 + }, + { + "name": "minecraft:leather_chestplate", + "id": 344 + }, + { + "name": "minecraft:leather_helmet", + "id": 343 + }, + { + "name": "minecraft:leather_horse_armor", + "id": 547 + }, + { + "name": "minecraft:leather_leggings", + "id": 345 + }, + { + "name": "minecraft:leaves", + "id": 743 + }, + { + "name": "minecraft:leaves2", + "id": 744 + }, + { + "name": "minecraft:lectern", + "id": -194 + }, + { + "name": "minecraft:lever", + "id": 69 + }, + { + "name": "minecraft:light_block", + "id": 767 + }, + { + "name": "minecraft:light_block_0", + "id": -215 + }, + { + "name": "minecraft:light_block_1", + "id": -929 + }, + { + "name": "minecraft:light_block_10", + "id": -938 + }, + { + "name": "minecraft:light_block_11", + "id": -939 + }, + { + "name": "minecraft:light_block_12", + "id": -940 + }, + { + "name": "minecraft:light_block_13", + "id": -941 + }, + { + "name": "minecraft:light_block_14", + "id": -942 + }, + { + "name": "minecraft:light_block_15", + "id": -943 + }, + { + "name": "minecraft:light_block_2", + "id": -930 + }, + { + "name": "minecraft:light_block_3", + "id": -931 + }, + { + "name": "minecraft:light_block_4", + "id": -932 + }, + { + "name": "minecraft:light_block_5", + "id": -933 + }, + { + "name": "minecraft:light_block_6", + "id": -934 + }, + { + "name": "minecraft:light_block_7", + "id": -935 + }, + { + "name": "minecraft:light_block_8", + "id": -936 + }, + { + "name": "minecraft:light_block_9", + "id": -937 + }, + { + "name": "minecraft:light_blue_candle", + "id": -416 + }, + { + "name": "minecraft:light_blue_candle_cake", + "id": -433 + }, + { + "name": "minecraft:light_blue_carpet", + "id": -599 + }, + { + "name": "minecraft:light_blue_concrete", + "id": -630 + }, + { + "name": "minecraft:light_blue_concrete_powder", + "id": -711 + }, + { + "name": "minecraft:light_blue_dye", + "id": 416 + }, + { + "name": "minecraft:light_blue_glazed_terracotta", + "id": 223 + }, + { + "name": "minecraft:light_blue_shulker_box", + "id": -615 + }, + { + "name": "minecraft:light_blue_stained_glass", + "id": -675 + }, + { + "name": "minecraft:light_blue_stained_glass_pane", + "id": -645 + }, + { + "name": "minecraft:light_blue_terracotta", + "id": -726 + }, + { + "name": "minecraft:light_blue_wool", + "id": -562 + }, + { + "name": "minecraft:light_gray_candle", + "id": -421 + }, + { + "name": "minecraft:light_gray_candle_cake", + "id": -438 + }, + { + "name": "minecraft:light_gray_carpet", + "id": -604 + }, + { + "name": "minecraft:light_gray_concrete", + "id": -635 + }, + { + "name": "minecraft:light_gray_concrete_powder", + "id": -716 + }, + { + "name": "minecraft:light_gray_dye", + "id": 411 + }, + { + "name": "minecraft:light_gray_shulker_box", + "id": -620 + }, + { + "name": "minecraft:light_gray_stained_glass", + "id": -680 + }, + { + "name": "minecraft:light_gray_stained_glass_pane", + "id": -650 + }, + { + "name": "minecraft:light_gray_terracotta", + "id": -731 + }, + { + "name": "minecraft:light_gray_wool", + "id": -552 + }, + { + "name": "minecraft:light_weighted_pressure_plate", + "id": 147 + }, + { + "name": "minecraft:lightning_rod", + "id": -312 + }, + { + "name": "minecraft:lilac", + "id": -863 + }, + { + "name": "minecraft:lily_of_the_valley", + "id": -839 + }, + { + "name": "minecraft:lime_candle", + "id": -418 + }, + { + "name": "minecraft:lime_candle_cake", + "id": -435 + }, + { + "name": "minecraft:lime_carpet", + "id": -601 + }, + { + "name": "minecraft:lime_concrete", + "id": -632 + }, + { + "name": "minecraft:lime_concrete_powder", + "id": -713 + }, + { + "name": "minecraft:lime_dye", + "id": 414 + }, + { + "name": "minecraft:lime_glazed_terracotta", + "id": 225 + }, + { + "name": "minecraft:lime_shulker_box", + "id": -617 + }, + { + "name": "minecraft:lime_stained_glass", + "id": -677 + }, + { + "name": "minecraft:lime_stained_glass_pane", + "id": -647 + }, + { + "name": "minecraft:lime_terracotta", + "id": -728 + }, + { + "name": "minecraft:lime_wool", + "id": -559 + }, + { + "name": "minecraft:lingering_potion", + "id": 579 + }, + { + "name": "minecraft:lit_blast_furnace", + "id": -214 + }, + { + "name": "minecraft:lit_deepslate_redstone_ore", + "id": -404 + }, + { + "name": "minecraft:lit_furnace", + "id": 62 + }, + { + "name": "minecraft:lit_pumpkin", + "id": 91 + }, + { + "name": "minecraft:lit_redstone_lamp", + "id": 124 + }, + { + "name": "minecraft:lit_redstone_ore", + "id": 74 + }, + { + "name": "minecraft:lit_smoker", + "id": -199 + }, + { + "name": "minecraft:llama_spawn_egg", + "id": 484 + }, + { + "name": "minecraft:lodestone", + "id": -222 + }, + { + "name": "minecraft:lodestone_compass", + "id": 622 + }, + { + "name": "minecraft:log", + "id": 728 + }, + { + "name": "minecraft:log2", + "id": 751 + }, + { + "name": "minecraft:loom", + "id": -204 + }, + { + "name": "minecraft:mace", + "id": 327 + }, + { + "name": "minecraft:magenta_candle", + "id": -415 + }, + { + "name": "minecraft:magenta_candle_cake", + "id": -432 + }, + { + "name": "minecraft:magenta_carpet", + "id": -598 + }, + { + "name": "minecraft:magenta_concrete", + "id": -629 + }, + { + "name": "minecraft:magenta_concrete_powder", + "id": -710 + }, + { + "name": "minecraft:magenta_dye", + "id": 417 + }, + { + "name": "minecraft:magenta_glazed_terracotta", + "id": 222 + }, + { + "name": "minecraft:magenta_shulker_box", + "id": -614 + }, + { + "name": "minecraft:magenta_stained_glass", + "id": -674 + }, + { + "name": "minecraft:magenta_stained_glass_pane", + "id": -644 + }, + { + "name": "minecraft:magenta_terracotta", + "id": -725 + }, + { + "name": "minecraft:magenta_wool", + "id": -565 + }, + { + "name": "minecraft:magma", + "id": 213 + }, + { + "name": "minecraft:magma_cream", + "id": 440 + }, + { + "name": "minecraft:magma_cube_spawn_egg", + "id": 465 + }, + { + "name": "minecraft:mangrove_boat", + "id": 655 + }, + { + "name": "minecraft:mangrove_button", + "id": -487 + }, + { + "name": "minecraft:mangrove_chest_boat", + "id": 664 + }, + { + "name": "minecraft:mangrove_door", + "id": 653 + }, + { + "name": "minecraft:mangrove_double_slab", + "id": -499 + }, + { + "name": "minecraft:mangrove_fence", + "id": -491 + }, + { + "name": "minecraft:mangrove_fence_gate", + "id": -492 + }, + { + "name": "minecraft:mangrove_hanging_sign", + "id": -508 + }, + { + "name": "minecraft:mangrove_leaves", + "id": -472 + }, + { + "name": "minecraft:mangrove_log", + "id": -484 + }, + { + "name": "minecraft:mangrove_planks", + "id": -486 + }, + { + "name": "minecraft:mangrove_pressure_plate", + "id": -490 + }, + { + "name": "minecraft:mangrove_propagule", + "id": -474 + }, + { + "name": "minecraft:mangrove_roots", + "id": -482 + }, + { + "name": "minecraft:mangrove_sign", + "id": 654 + }, + { + "name": "minecraft:mangrove_slab", + "id": -489 + }, + { + "name": "minecraft:mangrove_stairs", + "id": -488 + }, + { + "name": "minecraft:mangrove_standing_sign", + "id": -494 + }, + { + "name": "minecraft:mangrove_trapdoor", + "id": -496 + }, + { + "name": "minecraft:mangrove_wall_sign", + "id": -495 + }, + { + "name": "minecraft:mangrove_wood", + "id": -497 + }, + { + "name": "minecraft:material_reducer", + "id": -986 + }, + { + "name": "minecraft:medicine", + "id": 619 + }, + { + "name": "minecraft:medium_amethyst_bud", + "id": -331 + }, + { + "name": "minecraft:melon_block", + "id": 103 + }, + { + "name": "minecraft:melon_seeds", + "id": 298 + }, + { + "name": "minecraft:melon_slice", + "id": 277 + }, + { + "name": "minecraft:melon_stem", + "id": 105 + }, + { + "name": "minecraft:milk_bucket", + "id": 369 + }, + { + "name": "minecraft:minecart", + "id": 378 + }, + { + "name": "minecraft:miner_pottery_sherd", + "id": 690 + }, + { + "name": "minecraft:mob_spawner", + "id": 52 + }, + { + "name": "minecraft:mojang_banner_pattern", + "id": 601 + }, + { + "name": "minecraft:monster_egg", + "id": 752 + }, + { + "name": "minecraft:mooshroom_spawn_egg", + "id": 450 + }, + { + "name": "minecraft:moss_block", + "id": -320 + }, + { + "name": "minecraft:moss_carpet", + "id": -335 + }, + { + "name": "minecraft:mossy_cobblestone", + "id": 48 + }, + { + "name": "minecraft:mossy_cobblestone_double_slab", + "id": -915 + }, + { + "name": "minecraft:mossy_cobblestone_slab", + "id": -888 + }, + { + "name": "minecraft:mossy_cobblestone_stairs", + "id": -179 + }, + { + "name": "minecraft:mossy_cobblestone_wall", + "id": -971 + }, + { + "name": "minecraft:mossy_stone_brick_double_slab", + "id": -168 + }, + { + "name": "minecraft:mossy_stone_brick_slab", + "id": -166 + }, + { + "name": "minecraft:mossy_stone_brick_stairs", + "id": -175 + }, + { + "name": "minecraft:mossy_stone_brick_wall", + "id": -978 + }, + { + "name": "minecraft:mossy_stone_bricks", + "id": -868 + }, + { + "name": "minecraft:mourner_pottery_sherd", + "id": 691 + }, + { + "name": "minecraft:moving_block", + "id": 250 + }, + { + "name": "minecraft:mud", + "id": -473 + }, + { + "name": "minecraft:mud_brick_double_slab", + "id": -479 + }, + { + "name": "minecraft:mud_brick_slab", + "id": -478 + }, + { + "name": "minecraft:mud_brick_stairs", + "id": -480 + }, + { + "name": "minecraft:mud_brick_wall", + "id": -481 + }, + { + "name": "minecraft:mud_bricks", + "id": -475 + }, + { + "name": "minecraft:muddy_mangrove_roots", + "id": -483 + }, + { + "name": "minecraft:mule_spawn_egg", + "id": 477 + }, + { + "name": "minecraft:mushroom_stew", + "id": 265 + }, + { + "name": "minecraft:music_disc_11", + "id": 561 + }, + { + "name": "minecraft:music_disc_13", + "id": 551 + }, + { + "name": "minecraft:music_disc_5", + "id": 656 + }, + { + "name": "minecraft:music_disc_blocks", + "id": 553 + }, + { + "name": "minecraft:music_disc_cat", + "id": 552 + }, + { + "name": "minecraft:music_disc_chirp", + "id": 554 + }, + { + "name": "minecraft:music_disc_creator", + "id": 759 + }, + { + "name": "minecraft:music_disc_creator_music_box", + "id": 760 + }, + { + "name": "minecraft:music_disc_far", + "id": 555 + }, + { + "name": "minecraft:music_disc_mall", + "id": 556 + }, + { + "name": "minecraft:music_disc_mellohi", + "id": 557 + }, + { + "name": "minecraft:music_disc_otherside", + "id": 646 + }, + { + "name": "minecraft:music_disc_pigstep", + "id": 640 + }, + { + "name": "minecraft:music_disc_precipice", + "id": 761 + }, + { + "name": "minecraft:music_disc_relic", + "id": 719 + }, + { + "name": "minecraft:music_disc_stal", + "id": 558 + }, + { + "name": "minecraft:music_disc_strad", + "id": 559 + }, + { + "name": "minecraft:music_disc_wait", + "id": 562 + }, + { + "name": "minecraft:music_disc_ward", + "id": 560 + }, + { + "name": "minecraft:mutton", + "id": 567 + }, + { + "name": "minecraft:mycelium", + "id": 110 + }, + { + "name": "minecraft:name_tag", + "id": 565 + }, + { + "name": "minecraft:nautilus_shell", + "id": 587 + }, + { + "name": "minecraft:nether_brick", + "id": 112 + }, + { + "name": "minecraft:nether_brick_double_slab", + "id": -883 + }, + { + "name": "minecraft:nether_brick_fence", + "id": 113 + }, + { + "name": "minecraft:nether_brick_slab", + "id": -877 + }, + { + "name": "minecraft:nether_brick_stairs", + "id": 114 + }, + { + "name": "minecraft:nether_brick_wall", + "id": -979 + }, + { + "name": "minecraft:nether_gold_ore", + "id": -288 + }, + { + "name": "minecraft:nether_sprouts", + "id": 641 + }, + { + "name": "minecraft:nether_star", + "id": 535 + }, + { + "name": "minecraft:nether_wart", + "id": 299 + }, + { + "name": "minecraft:nether_wart_block", + "id": 214 + }, + { + "name": "minecraft:netherbrick", + "id": 540 + }, + { + "name": "minecraft:netherite_axe", + "id": 626 + }, + { + "name": "minecraft:netherite_block", + "id": -270 + }, + { + "name": "minecraft:netherite_boots", + "id": 632 + }, + { + "name": "minecraft:netherite_chestplate", + "id": 630 + }, + { + "name": "minecraft:netherite_helmet", + "id": 629 + }, + { + "name": "minecraft:netherite_hoe", + "id": 627 + }, + { + "name": "minecraft:netherite_ingot", + "id": 628 + }, + { + "name": "minecraft:netherite_leggings", + "id": 631 + }, + { + "name": "minecraft:netherite_pickaxe", + "id": 625 + }, + { + "name": "minecraft:netherite_scrap", + "id": 633 + }, + { + "name": "minecraft:netherite_shovel", + "id": 624 + }, + { + "name": "minecraft:netherite_sword", + "id": 623 + }, + { + "name": "minecraft:netherite_upgrade_smithing_template", + "id": 700 + }, + { + "name": "minecraft:netherrack", + "id": 87 + }, + { + "name": "minecraft:netherreactor", + "id": 247 + }, + { + "name": "minecraft:normal_stone_double_slab", + "id": -926 + }, + { + "name": "minecraft:normal_stone_slab", + "id": -899 + }, + { + "name": "minecraft:normal_stone_stairs", + "id": -180 + }, + { + "name": "minecraft:noteblock", + "id": 25 + }, + { + "name": "minecraft:npc_spawn_egg", + "id": 481 + }, + { + "name": "minecraft:oak_boat", + "id": 384 + }, + { + "name": "minecraft:oak_chest_boat", + "id": 658 + }, + { + "name": "minecraft:oak_double_slab", + "id": 157 + }, + { + "name": "minecraft:oak_fence", + "id": 85 + }, + { + "name": "minecraft:oak_hanging_sign", + "id": -500 + }, + { + "name": "minecraft:oak_leaves", + "id": 18 + }, + { + "name": "minecraft:oak_log", + "id": 17 + }, + { + "name": "minecraft:oak_planks", + "id": 5 + }, + { + "name": "minecraft:oak_sapling", + "id": 6 + }, + { + "name": "minecraft:oak_sign", + "id": 366 + }, + { + "name": "minecraft:oak_slab", + "id": 158 + }, + { + "name": "minecraft:oak_stairs", + "id": 53 + }, + { + "name": "minecraft:oak_wood", + "id": -212 + }, + { + "name": "minecraft:observer", + "id": 251 + }, + { + "name": "minecraft:obsidian", + "id": 49 + }, + { + "name": "minecraft:ocelot_spawn_egg", + "id": 461 + }, + { + "name": "minecraft:ochre_froglight", + "id": -471 + }, + { + "name": "minecraft:ominous_bottle", + "id": 612 + }, + { + "name": "minecraft:ominous_trial_key", + "id": 258 + }, + { + "name": "minecraft:orange_candle", + "id": -414 + }, + { + "name": "minecraft:orange_candle_cake", + "id": -431 + }, + { + "name": "minecraft:orange_carpet", + "id": -597 + }, + { + "name": "minecraft:orange_concrete", + "id": -628 + }, + { + "name": "minecraft:orange_concrete_powder", + "id": -709 + }, + { + "name": "minecraft:orange_dye", + "id": 418 + }, + { + "name": "minecraft:orange_glazed_terracotta", + "id": 221 + }, + { + "name": "minecraft:orange_shulker_box", + "id": -613 + }, + { + "name": "minecraft:orange_stained_glass", + "id": -673 + }, + { + "name": "minecraft:orange_stained_glass_pane", + "id": -643 + }, + { + "name": "minecraft:orange_terracotta", + "id": -724 + }, + { + "name": "minecraft:orange_tulip", + "id": -834 + }, + { + "name": "minecraft:orange_wool", + "id": -557 + }, + { + "name": "minecraft:oxeye_daisy", + "id": -837 + }, + { + "name": "minecraft:oxidized_chiseled_copper", + "id": -763 + }, + { + "name": "minecraft:oxidized_copper", + "id": -343 + }, + { + "name": "minecraft:oxidized_copper_bulb", + "id": -779 + }, + { + "name": "minecraft:oxidized_copper_door", + "id": -787 + }, + { + "name": "minecraft:oxidized_copper_grate", + "id": -771 + }, + { + "name": "minecraft:oxidized_copper_trapdoor", + "id": -795 + }, + { + "name": "minecraft:oxidized_cut_copper", + "id": -350 + }, + { + "name": "minecraft:oxidized_cut_copper_slab", + "id": -364 + }, + { + "name": "minecraft:oxidized_cut_copper_stairs", + "id": -357 + }, + { + "name": "minecraft:oxidized_double_cut_copper_slab", + "id": -371 + }, + { + "name": "minecraft:packed_ice", + "id": 174 + }, + { + "name": "minecraft:packed_mud", + "id": -477 + }, + { + "name": "minecraft:painting", + "id": 365 + }, + { + "name": "minecraft:panda_spawn_egg", + "id": 500 + }, + { + "name": "minecraft:paper", + "id": 395 + }, + { + "name": "minecraft:parrot_spawn_egg", + "id": 489 + }, + { + "name": "minecraft:pearlescent_froglight", + "id": -469 + }, + { + "name": "minecraft:peony", + "id": -867 + }, + { + "name": "minecraft:petrified_oak_double_slab", + "id": -903 + }, + { + "name": "minecraft:petrified_oak_slab", + "id": -902 + }, + { + "name": "minecraft:phantom_membrane", + "id": 591 + }, + { + "name": "minecraft:phantom_spawn_egg", + "id": 497 + }, + { + "name": "minecraft:pig_spawn_egg", + "id": 447 + }, + { + "name": "minecraft:piglin_banner_pattern", + "id": 604 + }, + { + "name": "minecraft:piglin_brute_spawn_egg", + "id": 510 + }, + { + "name": "minecraft:piglin_spawn_egg", + "id": 508 + }, + { + "name": "minecraft:pillager_spawn_egg", + "id": 502 + }, + { + "name": "minecraft:pink_candle", + "id": -419 + }, + { + "name": "minecraft:pink_candle_cake", + "id": -436 + }, + { + "name": "minecraft:pink_carpet", + "id": -602 + }, + { + "name": "minecraft:pink_concrete", + "id": -633 + }, + { + "name": "minecraft:pink_concrete_powder", + "id": -714 + }, + { + "name": "minecraft:pink_dye", + "id": 413 + }, + { + "name": "minecraft:pink_glazed_terracotta", + "id": 226 + }, + { + "name": "minecraft:pink_petals", + "id": -549 + }, + { + "name": "minecraft:pink_shulker_box", + "id": -618 + }, + { + "name": "minecraft:pink_stained_glass", + "id": -678 + }, + { + "name": "minecraft:pink_stained_glass_pane", + "id": -648 + }, + { + "name": "minecraft:pink_terracotta", + "id": -729 + }, + { + "name": "minecraft:pink_tulip", + "id": -836 + }, + { + "name": "minecraft:pink_wool", + "id": -566 + }, + { + "name": "minecraft:piston", + "id": 33 + }, + { + "name": "minecraft:piston_arm_collision", + "id": 34 + }, + { + "name": "minecraft:pitcher_crop", + "id": -574 + }, + { + "name": "minecraft:pitcher_plant", + "id": -612 + }, + { + "name": "minecraft:pitcher_pod", + "id": 302 + }, + { + "name": "minecraft:planks", + "id": 748 + }, + { + "name": "minecraft:plenty_pottery_sherd", + "id": 692 + }, + { + "name": "minecraft:podzol", + "id": 243 + }, + { + "name": "minecraft:pointed_dripstone", + "id": -308 + }, + { + "name": "minecraft:poisonous_potato", + "id": 287 + }, + { + "name": "minecraft:polar_bear_spawn_egg", + "id": 483 + }, + { + "name": "minecraft:polished_andesite", + "id": -595 + }, + { + "name": "minecraft:polished_andesite_double_slab", + "id": -919 + }, + { + "name": "minecraft:polished_andesite_slab", + "id": -892 + }, + { + "name": "minecraft:polished_andesite_stairs", + "id": -174 + }, + { + "name": "minecraft:polished_basalt", + "id": -235 + }, + { + "name": "minecraft:polished_blackstone", + "id": -291 + }, + { + "name": "minecraft:polished_blackstone_brick_double_slab", + "id": -285 + }, + { + "name": "minecraft:polished_blackstone_brick_slab", + "id": -284 + }, + { + "name": "minecraft:polished_blackstone_brick_stairs", + "id": -275 + }, + { + "name": "minecraft:polished_blackstone_brick_wall", + "id": -278 + }, + { + "name": "minecraft:polished_blackstone_bricks", + "id": -274 + }, + { + "name": "minecraft:polished_blackstone_button", + "id": -296 + }, + { + "name": "minecraft:polished_blackstone_double_slab", + "id": -294 + }, + { + "name": "minecraft:polished_blackstone_pressure_plate", + "id": -295 + }, + { + "name": "minecraft:polished_blackstone_slab", + "id": -293 + }, + { + "name": "minecraft:polished_blackstone_stairs", + "id": -292 + }, + { + "name": "minecraft:polished_blackstone_wall", + "id": -297 + }, + { + "name": "minecraft:polished_deepslate", + "id": -383 + }, + { + "name": "minecraft:polished_deepslate_double_slab", + "id": -397 + }, + { + "name": "minecraft:polished_deepslate_slab", + "id": -384 + }, + { + "name": "minecraft:polished_deepslate_stairs", + "id": -385 + }, + { + "name": "minecraft:polished_deepslate_wall", + "id": -386 + }, + { + "name": "minecraft:polished_diorite", + "id": -593 + }, + { + "name": "minecraft:polished_diorite_double_slab", + "id": -922 + }, + { + "name": "minecraft:polished_diorite_slab", + "id": -895 + }, + { + "name": "minecraft:polished_diorite_stairs", + "id": -173 + }, + { + "name": "minecraft:polished_granite", + "id": -591 + }, + { + "name": "minecraft:polished_granite_double_slab", + "id": -924 + }, + { + "name": "minecraft:polished_granite_slab", + "id": -897 + }, + { + "name": "minecraft:polished_granite_stairs", + "id": -172 + }, + { + "name": "minecraft:polished_tuff", + "id": -748 + }, + { + "name": "minecraft:polished_tuff_double_slab", + "id": -750 + }, + { + "name": "minecraft:polished_tuff_slab", + "id": -749 + }, + { + "name": "minecraft:polished_tuff_stairs", + "id": -751 + }, + { + "name": "minecraft:polished_tuff_wall", + "id": -752 + }, + { + "name": "minecraft:popped_chorus_fruit", + "id": 576 + }, + { + "name": "minecraft:poppy", + "id": 38 + }, + { + "name": "minecraft:porkchop", + "id": 267 + }, + { + "name": "minecraft:portal", + "id": 90 + }, + { + "name": "minecraft:potato", + "id": 285 + }, + { + "name": "minecraft:potatoes", + "id": 142 + }, + { + "name": "minecraft:potion", + "id": 436 + }, + { + "name": "minecraft:powder_snow", + "id": -306 + }, + { + "name": "minecraft:powder_snow_bucket", + "id": 376 + }, + { + "name": "minecraft:powered_comparator", + "id": 150 + }, + { + "name": "minecraft:powered_repeater", + "id": 94 + }, + { + "name": "minecraft:prismarine", + "id": 168 + }, + { + "name": "minecraft:prismarine_brick_double_slab", + "id": -914 + }, + { + "name": "minecraft:prismarine_brick_slab", + "id": -887 + }, + { + "name": "minecraft:prismarine_bricks", + "id": -948 + }, + { + "name": "minecraft:prismarine_bricks_stairs", + "id": -4 + }, + { + "name": "minecraft:prismarine_crystals", + "id": 566 + }, + { + "name": "minecraft:prismarine_double_slab", + "id": -912 + }, + { + "name": "minecraft:prismarine_shard", + "id": 582 + }, + { + "name": "minecraft:prismarine_slab", + "id": -885 + }, + { + "name": "minecraft:prismarine_stairs", + "id": -2 + }, + { + "name": "minecraft:prismarine_wall", + "id": -981 + }, + { + "name": "minecraft:prize_pottery_sherd", + "id": 693 + }, + { + "name": "minecraft:pufferfish", + "id": 272 + }, + { + "name": "minecraft:pufferfish_bucket", + "id": 375 + }, + { + "name": "minecraft:pufferfish_spawn_egg", + "id": 492 + }, + { + "name": "minecraft:pumpkin", + "id": 86 + }, + { + "name": "minecraft:pumpkin_pie", + "id": 289 + }, + { + "name": "minecraft:pumpkin_seeds", + "id": 297 + }, + { + "name": "minecraft:pumpkin_stem", + "id": 104 + }, + { + "name": "minecraft:purple_candle", + "id": -423 + }, + { + "name": "minecraft:purple_candle_cake", + "id": -440 + }, + { + "name": "minecraft:purple_carpet", + "id": -606 + }, + { + "name": "minecraft:purple_concrete", + "id": -637 + }, + { + "name": "minecraft:purple_concrete_powder", + "id": -718 + }, + { + "name": "minecraft:purple_dye", + "id": 409 + }, + { + "name": "minecraft:purple_glazed_terracotta", + "id": 219 + }, + { + "name": "minecraft:purple_shulker_box", + "id": -622 + }, + { + "name": "minecraft:purple_stained_glass", + "id": -682 + }, + { + "name": "minecraft:purple_stained_glass_pane", + "id": -652 + }, + { + "name": "minecraft:purple_terracotta", + "id": -733 + }, + { + "name": "minecraft:purple_wool", + "id": -564 + }, + { + "name": "minecraft:purpur_block", + "id": 201 + }, + { + "name": "minecraft:purpur_double_slab", + "id": -911 + }, + { + "name": "minecraft:purpur_pillar", + "id": -951 + }, + { + "name": "minecraft:purpur_slab", + "id": -884 + }, + { + "name": "minecraft:purpur_stairs", + "id": 203 + }, + { + "name": "minecraft:quartz", + "id": 541 + }, + { + "name": "minecraft:quartz_block", + "id": 155 + }, + { + "name": "minecraft:quartz_bricks", + "id": -304 + }, + { + "name": "minecraft:quartz_double_slab", + "id": -882 + }, + { + "name": "minecraft:quartz_ore", + "id": 153 + }, + { + "name": "minecraft:quartz_pillar", + "id": -954 + }, + { + "name": "minecraft:quartz_slab", + "id": -876 + }, + { + "name": "minecraft:quartz_stairs", + "id": 156 + }, + { + "name": "minecraft:rabbit", + "id": 293 + }, + { + "name": "minecraft:rabbit_foot", + "id": 545 + }, + { + "name": "minecraft:rabbit_hide", + "id": 546 + }, + { + "name": "minecraft:rabbit_spawn_egg", + "id": 469 + }, + { + "name": "minecraft:rabbit_stew", + "id": 295 + }, + { + "name": "minecraft:rail", + "id": 66 + }, + { + "name": "minecraft:raiser_armor_trim_smithing_template", + "id": 714 + }, + { + "name": "minecraft:rapid_fertilizer", + "id": 617 + }, + { + "name": "minecraft:ravager_spawn_egg", + "id": 504 + }, + { + "name": "minecraft:raw_copper", + "id": 524 + }, + { + "name": "minecraft:raw_copper_block", + "id": -452 + }, + { + "name": "minecraft:raw_gold", + "id": 523 + }, + { + "name": "minecraft:raw_gold_block", + "id": -453 + }, + { + "name": "minecraft:raw_iron", + "id": 522 + }, + { + "name": "minecraft:raw_iron_block", + "id": -451 + }, + { + "name": "minecraft:recovery_compass", + "id": 666 + }, + { + "name": "minecraft:red_candle", + "id": -427 + }, + { + "name": "minecraft:red_candle_cake", + "id": -444 + }, + { + "name": "minecraft:red_carpet", + "id": -610 + }, + { + "name": "minecraft:red_concrete", + "id": -641 + }, + { + "name": "minecraft:red_concrete_powder", + "id": -722 + }, + { + "name": "minecraft:red_dye", + "id": 405 + }, + { + "name": "minecraft:red_flower", + "id": 746 + }, + { + "name": "minecraft:red_glazed_terracotta", + "id": 234 + }, + { + "name": "minecraft:red_mushroom", + "id": 40 + }, + { + "name": "minecraft:red_mushroom_block", + "id": 100 + }, + { + "name": "minecraft:red_nether_brick", + "id": 215 + }, + { + "name": "minecraft:red_nether_brick_double_slab", + "id": -917 + }, + { + "name": "minecraft:red_nether_brick_slab", + "id": -890 + }, + { + "name": "minecraft:red_nether_brick_stairs", + "id": -184 + }, + { + "name": "minecraft:red_nether_brick_wall", + "id": -983 + }, + { + "name": "minecraft:red_sand", + "id": -949 + }, + { + "name": "minecraft:red_sandstone", + "id": 179 + }, + { + "name": "minecraft:red_sandstone_double_slab", + "id": 181 + }, + { + "name": "minecraft:red_sandstone_slab", + "id": 182 + }, + { + "name": "minecraft:red_sandstone_stairs", + "id": 180 + }, + { + "name": "minecraft:red_sandstone_wall", + "id": -982 + }, + { + "name": "minecraft:red_shulker_box", + "id": -626 + }, + { + "name": "minecraft:red_stained_glass", + "id": -686 + }, + { + "name": "minecraft:red_stained_glass_pane", + "id": -656 + }, + { + "name": "minecraft:red_terracotta", + "id": -737 + }, + { + "name": "minecraft:red_tulip", + "id": -833 + }, + { + "name": "minecraft:red_wool", + "id": -556 + }, + { + "name": "minecraft:redstone", + "id": 381 + }, + { + "name": "minecraft:redstone_block", + "id": 152 + }, + { + "name": "minecraft:redstone_lamp", + "id": 123 + }, + { + "name": "minecraft:redstone_ore", + "id": 73 + }, + { + "name": "minecraft:redstone_torch", + "id": 76 + }, + { + "name": "minecraft:redstone_wire", + "id": 55 + }, + { + "name": "minecraft:reinforced_deepslate", + "id": -466 + }, + { + "name": "minecraft:repeater", + "id": 428 + }, + { + "name": "minecraft:repeating_command_block", + "id": 188 + }, + { + "name": "minecraft:reserved6", + "id": 255 + }, + { + "name": "minecraft:respawn_anchor", + "id": -272 + }, + { + "name": "minecraft:rib_armor_trim_smithing_template", + "id": 710 + }, + { + "name": "minecraft:rose_bush", + "id": -866 + }, + { + "name": "minecraft:rotten_flesh", + "id": 282 + }, + { + "name": "minecraft:saddle", + "id": 379 + }, + { + "name": "minecraft:salmon", + "id": 270 + }, + { + "name": "minecraft:salmon_bucket", + "id": 373 + }, + { + "name": "minecraft:salmon_spawn_egg", + "id": 493 + }, + { + "name": "minecraft:sand", + "id": 12 + }, + { + "name": "minecraft:sandstone", + "id": 24 + }, + { + "name": "minecraft:sandstone_double_slab", + "id": -878 + }, + { + "name": "minecraft:sandstone_slab", + "id": -872 + }, + { + "name": "minecraft:sandstone_stairs", + "id": 128 + }, + { + "name": "minecraft:sandstone_wall", + "id": -975 + }, + { + "name": "minecraft:sapling", + "id": 742 + }, + { + "name": "minecraft:scaffolding", + "id": -165 + }, + { + "name": "minecraft:scrape_pottery_sherd", + "id": 694 + }, + { + "name": "minecraft:sculk", + "id": -458 + }, + { + "name": "minecraft:sculk_catalyst", + "id": -460 + }, + { + "name": "minecraft:sculk_sensor", + "id": -307 + }, + { + "name": "minecraft:sculk_shrieker", + "id": -461 + }, + { + "name": "minecraft:sculk_vein", + "id": -459 + }, + { + "name": "minecraft:sea_lantern", + "id": 169 + }, + { + "name": "minecraft:sea_pickle", + "id": -156 + }, + { + "name": "minecraft:seagrass", + "id": -130 + }, + { + "name": "minecraft:sentry_armor_trim_smithing_template", + "id": 701 + }, + { + "name": "minecraft:shaper_armor_trim_smithing_template", + "id": 715 + }, + { + "name": "minecraft:sheaf_pottery_sherd", + "id": 695 + }, + { + "name": "minecraft:shears", + "id": 430 + }, + { + "name": "minecraft:sheep_spawn_egg", + "id": 448 + }, + { + "name": "minecraft:shelter_pottery_sherd", + "id": 696 + }, + { + "name": "minecraft:shield", + "id": 363 + }, + { + "name": "minecraft:short_grass", + "id": 31 + }, + { + "name": "minecraft:shroomlight", + "id": -230 + }, + { + "name": "minecraft:shulker_box", + "id": 757 + }, + { + "name": "minecraft:shulker_shell", + "id": 583 + }, + { + "name": "minecraft:shulker_spawn_egg", + "id": 480 + }, + { + "name": "minecraft:silence_armor_trim_smithing_template", + "id": 712 + }, + { + "name": "minecraft:silver_glazed_terracotta", + "id": 228 + }, + { + "name": "minecraft:silverfish_spawn_egg", + "id": 453 + }, + { + "name": "minecraft:skeleton_horse_spawn_egg", + "id": 478 + }, + { + "name": "minecraft:skeleton_spawn_egg", + "id": 454 + }, + { + "name": "minecraft:skull", + "id": 533 + }, + { + "name": "minecraft:skull_banner_pattern", + "id": 600 + }, + { + "name": "minecraft:skull_pottery_sherd", + "id": 697 + }, + { + "name": "minecraft:slime", + "id": 165 + }, + { + "name": "minecraft:slime_ball", + "id": 397 + }, + { + "name": "minecraft:slime_spawn_egg", + "id": 455 + }, + { + "name": "minecraft:small_amethyst_bud", + "id": -332 + }, + { + "name": "minecraft:small_dripleaf_block", + "id": -336 + }, + { + "name": "minecraft:smithing_table", + "id": -202 + }, + { + "name": "minecraft:smoker", + "id": -198 + }, + { + "name": "minecraft:smooth_basalt", + "id": -377 + }, + { + "name": "minecraft:smooth_quartz", + "id": -955 + }, + { + "name": "minecraft:smooth_quartz_double_slab", + "id": -925 + }, + { + "name": "minecraft:smooth_quartz_slab", + "id": -898 + }, + { + "name": "minecraft:smooth_quartz_stairs", + "id": -185 + }, + { + "name": "minecraft:smooth_red_sandstone", + "id": -958 + }, + { + "name": "minecraft:smooth_red_sandstone_double_slab", + "id": -918 + }, + { + "name": "minecraft:smooth_red_sandstone_slab", + "id": -891 + }, + { + "name": "minecraft:smooth_red_sandstone_stairs", + "id": -176 + }, + { + "name": "minecraft:smooth_sandstone", + "id": -946 + }, + { + "name": "minecraft:smooth_sandstone_double_slab", + "id": -916 + }, + { + "name": "minecraft:smooth_sandstone_slab", + "id": -889 + }, + { + "name": "minecraft:smooth_sandstone_stairs", + "id": -177 + }, + { + "name": "minecraft:smooth_stone", + "id": -183 + }, + { + "name": "minecraft:smooth_stone_double_slab", + "id": 43 + }, + { + "name": "minecraft:smooth_stone_slab", + "id": 44 + }, + { + "name": "minecraft:sniffer_egg", + "id": -596 + }, + { + "name": "minecraft:sniffer_spawn_egg", + "id": 511 + }, + { + "name": "minecraft:snort_pottery_sherd", + "id": 698 + }, + { + "name": "minecraft:snout_armor_trim_smithing_template", + "id": 709 + }, + { + "name": "minecraft:snow", + "id": 80 + }, + { + "name": "minecraft:snow_golem_spawn_egg", + "id": 517 + }, + { + "name": "minecraft:snow_layer", + "id": 78 + }, + { + "name": "minecraft:snowball", + "id": 382 + }, + { + "name": "minecraft:soul_campfire", + "id": 642 + }, + { + "name": "minecraft:soul_fire", + "id": -237 + }, + { + "name": "minecraft:soul_lantern", + "id": -269 + }, + { + "name": "minecraft:soul_sand", + "id": 88 + }, + { + "name": "minecraft:soul_soil", + "id": -236 + }, + { + "name": "minecraft:soul_torch", + "id": -268 + }, + { + "name": "minecraft:sparkler", + "id": 620 + }, + { + "name": "minecraft:spawn_egg", + "id": 771 + }, + { + "name": "minecraft:spider_eye", + "id": 283 + }, + { + "name": "minecraft:spider_spawn_egg", + "id": 456 + }, + { + "name": "minecraft:spire_armor_trim_smithing_template", + "id": 711 + }, + { + "name": "minecraft:splash_potion", + "id": 578 + }, + { + "name": "minecraft:sponge", + "id": 19 + }, + { + "name": "minecraft:spore_blossom", + "id": -321 + }, + { + "name": "minecraft:spruce_boat", + "id": 387 + }, + { + "name": "minecraft:spruce_button", + "id": -144 + }, + { + "name": "minecraft:spruce_chest_boat", + "id": 661 + }, + { + "name": "minecraft:spruce_door", + "id": 570 + }, + { + "name": "minecraft:spruce_double_slab", + "id": -809 + }, + { + "name": "minecraft:spruce_fence", + "id": -579 + }, + { + "name": "minecraft:spruce_fence_gate", + "id": 183 + }, + { + "name": "minecraft:spruce_hanging_sign", + "id": -501 + }, + { + "name": "minecraft:spruce_leaves", + "id": -800 + }, + { + "name": "minecraft:spruce_log", + "id": -569 + }, + { + "name": "minecraft:spruce_planks", + "id": -739 + }, + { + "name": "minecraft:spruce_pressure_plate", + "id": -154 + }, + { + "name": "minecraft:spruce_sapling", + "id": -825 + }, + { + "name": "minecraft:spruce_sign", + "id": 593 + }, + { + "name": "minecraft:spruce_slab", + "id": -804 + }, + { + "name": "minecraft:spruce_stairs", + "id": 134 + }, + { + "name": "minecraft:spruce_standing_sign", + "id": -181 + }, + { + "name": "minecraft:spruce_trapdoor", + "id": -149 + }, + { + "name": "minecraft:spruce_wall_sign", + "id": -182 + }, + { + "name": "minecraft:spruce_wood", + "id": -814 + }, + { + "name": "minecraft:spyglass", + "id": 645 + }, + { + "name": "minecraft:squid_spawn_egg", + "id": 460 + }, + { + "name": "minecraft:stained_glass", + "id": 755 + }, + { + "name": "minecraft:stained_glass_pane", + "id": 756 + }, + { + "name": "minecraft:stained_hardened_clay", + "id": 720 + }, + { + "name": "minecraft:standing_banner", + "id": 176 + }, + { + "name": "minecraft:standing_sign", + "id": 63 + }, + { + "name": "minecraft:stick", + "id": 328 + }, + { + "name": "minecraft:sticky_piston", + "id": 29 + }, + { + "name": "minecraft:sticky_piston_arm_collision", + "id": -217 + }, + { + "name": "minecraft:stone", + "id": 1 + }, + { + "name": "minecraft:stone_axe", + "id": 322 + }, + { + "name": "minecraft:stone_block_slab", + "id": 732 + }, + { + "name": "minecraft:stone_block_slab2", + "id": 733 + }, + { + "name": "minecraft:stone_block_slab3", + "id": 734 + }, + { + "name": "minecraft:stone_block_slab4", + "id": 735 + }, + { + "name": "minecraft:stone_brick_double_slab", + "id": -881 + }, + { + "name": "minecraft:stone_brick_slab", + "id": -875 + }, + { + "name": "minecraft:stone_brick_stairs", + "id": 109 + }, + { + "name": "minecraft:stone_brick_wall", + "id": -977 + }, + { + "name": "minecraft:stone_bricks", + "id": 98 + }, + { + "name": "minecraft:stone_button", + "id": 77 + }, + { + "name": "minecraft:stone_hoe", + "id": 338 + }, + { + "name": "minecraft:stone_pickaxe", + "id": 321 + }, + { + "name": "minecraft:stone_pressure_plate", + "id": 70 + }, + { + "name": "minecraft:stone_shovel", + "id": 320 + }, + { + "name": "minecraft:stone_stairs", + "id": 67 + }, + { + "name": "minecraft:stone_sword", + "id": 319 + }, + { + "name": "minecraft:stonebrick", + "id": 730 + }, + { + "name": "minecraft:stonecutter", + "id": 245 + }, + { + "name": "minecraft:stonecutter_block", + "id": -197 + }, + { + "name": "minecraft:stray_spawn_egg", + "id": 472 + }, + { + "name": "minecraft:strider_spawn_egg", + "id": 506 + }, + { + "name": "minecraft:string", + "id": 334 + }, + { + "name": "minecraft:stripped_acacia_log", + "id": -8 + }, + { + "name": "minecraft:stripped_acacia_wood", + "id": -823 + }, + { + "name": "minecraft:stripped_bamboo_block", + "id": -528 + }, + { + "name": "minecraft:stripped_birch_log", + "id": -6 + }, + { + "name": "minecraft:stripped_birch_wood", + "id": -821 + }, + { + "name": "minecraft:stripped_cherry_log", + "id": -535 + }, + { + "name": "minecraft:stripped_cherry_wood", + "id": -545 + }, + { + "name": "minecraft:stripped_crimson_hyphae", + "id": -300 + }, + { + "name": "minecraft:stripped_crimson_stem", + "id": -240 + }, + { + "name": "minecraft:stripped_dark_oak_log", + "id": -9 + }, + { + "name": "minecraft:stripped_dark_oak_wood", + "id": -824 + }, + { + "name": "minecraft:stripped_jungle_log", + "id": -7 + }, + { + "name": "minecraft:stripped_jungle_wood", + "id": -822 + }, + { + "name": "minecraft:stripped_mangrove_log", + "id": -485 + }, + { + "name": "minecraft:stripped_mangrove_wood", + "id": -498 + }, + { + "name": "minecraft:stripped_oak_log", + "id": -10 + }, + { + "name": "minecraft:stripped_oak_wood", + "id": -819 + }, + { + "name": "minecraft:stripped_spruce_log", + "id": -5 + }, + { + "name": "minecraft:stripped_spruce_wood", + "id": -820 + }, + { + "name": "minecraft:stripped_warped_hyphae", + "id": -301 + }, + { + "name": "minecraft:stripped_warped_stem", + "id": -241 + }, + { + "name": "minecraft:structure_block", + "id": 252 + }, + { + "name": "minecraft:structure_void", + "id": 217 + }, + { + "name": "minecraft:sugar", + "id": 425 + }, + { + "name": "minecraft:sugar_cane", + "id": 394 + }, + { + "name": "minecraft:sunflower", + "id": 175 + }, + { + "name": "minecraft:suspicious_gravel", + "id": -573 + }, + { + "name": "minecraft:suspicious_sand", + "id": -529 + }, + { + "name": "minecraft:suspicious_stew", + "id": 609 + }, + { + "name": "minecraft:sweet_berries", + "id": 292 + }, + { + "name": "minecraft:sweet_berry_bush", + "id": -207 + }, + { + "name": "minecraft:tadpole_bucket", + "id": 650 + }, + { + "name": "minecraft:tadpole_spawn_egg", + "id": 649 + }, + { + "name": "minecraft:tall_grass", + "id": -864 + }, + { + "name": "minecraft:tallgrass", + "id": 750 + }, + { + "name": "minecraft:target", + "id": -239 + }, + { + "name": "minecraft:tide_armor_trim_smithing_template", + "id": 708 + }, + { + "name": "minecraft:tinted_glass", + "id": -334 + }, + { + "name": "minecraft:tnt", + "id": 46 + }, + { + "name": "minecraft:tnt_minecart", + "id": 542 + }, + { + "name": "minecraft:torch", + "id": 50 + }, + { + "name": "minecraft:torchflower", + "id": -568 + }, + { + "name": "minecraft:torchflower_crop", + "id": -567 + }, + { + "name": "minecraft:torchflower_seeds", + "id": 301 + }, + { + "name": "minecraft:totem_of_undying", + "id": 585 + }, + { + "name": "minecraft:trader_llama_spawn_egg", + "id": 668 + }, + { + "name": "minecraft:trapdoor", + "id": 96 + }, + { + "name": "minecraft:trapped_chest", + "id": 146 + }, + { + "name": "minecraft:trial_key", + "id": 259 + }, + { + "name": "minecraft:trial_spawner", + "id": -315 + }, + { + "name": "minecraft:trident", + "id": 563 + }, + { + "name": "minecraft:trip_wire", + "id": 132 + }, + { + "name": "minecraft:tripwire_hook", + "id": 131 + }, + { + "name": "minecraft:tropical_fish", + "id": 271 + }, + { + "name": "minecraft:tropical_fish_bucket", + "id": 374 + }, + { + "name": "minecraft:tropical_fish_spawn_egg", + "id": 490 + }, + { + "name": "minecraft:tube_coral", + "id": -131 + }, + { + "name": "minecraft:tube_coral_block", + "id": -132 + }, + { + "name": "minecraft:tube_coral_fan", + "id": -133 + }, + { + "name": "minecraft:tube_coral_wall_fan", + "id": -135 + }, + { + "name": "minecraft:tuff", + "id": -333 + }, + { + "name": "minecraft:tuff_brick_double_slab", + "id": -756 + }, + { + "name": "minecraft:tuff_brick_slab", + "id": -755 + }, + { + "name": "minecraft:tuff_brick_stairs", + "id": -757 + }, + { + "name": "minecraft:tuff_brick_wall", + "id": -758 + }, + { + "name": "minecraft:tuff_bricks", + "id": -754 + }, + { + "name": "minecraft:tuff_double_slab", + "id": -745 + }, + { + "name": "minecraft:tuff_slab", + "id": -744 + }, + { + "name": "minecraft:tuff_stairs", + "id": -746 + }, + { + "name": "minecraft:tuff_wall", + "id": -747 + }, + { + "name": "minecraft:turtle_egg", + "id": -159 + }, + { + "name": "minecraft:turtle_helmet", + "id": 590 + }, + { + "name": "minecraft:turtle_scute", + "id": 589 + }, + { + "name": "minecraft:turtle_spawn_egg", + "id": 496 + }, + { + "name": "minecraft:twisting_vines", + "id": -287 + }, + { + "name": "minecraft:underwater_tnt", + "id": -985 + }, + { + "name": "minecraft:underwater_torch", + "id": 239 + }, + { + "name": "minecraft:undyed_shulker_box", + "id": 205 + }, + { + "name": "minecraft:unknown", + "id": -305 + }, + { + "name": "minecraft:unlit_redstone_torch", + "id": 75 + }, + { + "name": "minecraft:unpowered_comparator", + "id": 149 + }, + { + "name": "minecraft:unpowered_repeater", + "id": 93 + }, + { + "name": "minecraft:vault", + "id": -314 + }, + { + "name": "minecraft:verdant_froglight", + "id": -470 + }, + { + "name": "minecraft:vex_armor_trim_smithing_template", + "id": 707 + }, + { + "name": "minecraft:vex_spawn_egg", + "id": 487 + }, + { + "name": "minecraft:villager_spawn_egg", + "id": 459 + }, + { + "name": "minecraft:vindicator_spawn_egg", + "id": 485 + }, + { + "name": "minecraft:vine", + "id": 106 + }, + { + "name": "minecraft:wall_banner", + "id": 177 + }, + { + "name": "minecraft:wall_sign", + "id": 68 + }, + { + "name": "minecraft:wandering_trader_spawn_egg", + "id": 503 + }, + { + "name": "minecraft:ward_armor_trim_smithing_template", + "id": 705 + }, + { + "name": "minecraft:warden_spawn_egg", + "id": 652 + }, + { + "name": "minecraft:warped_button", + "id": -261 + }, + { + "name": "minecraft:warped_door", + "id": 637 + }, + { + "name": "minecraft:warped_double_slab", + "id": -267 + }, + { + "name": "minecraft:warped_fence", + "id": -257 + }, + { + "name": "minecraft:warped_fence_gate", + "id": -259 + }, + { + "name": "minecraft:warped_fungus", + "id": -229 + }, + { + "name": "minecraft:warped_fungus_on_a_stick", + "id": 638 + }, + { + "name": "minecraft:warped_hanging_sign", + "id": -507 + }, + { + "name": "minecraft:warped_hyphae", + "id": -298 + }, + { + "name": "minecraft:warped_nylium", + "id": -233 + }, + { + "name": "minecraft:warped_planks", + "id": -243 + }, + { + "name": "minecraft:warped_pressure_plate", + "id": -263 + }, + { + "name": "minecraft:warped_roots", + "id": -224 + }, + { + "name": "minecraft:warped_sign", + "id": 635 + }, + { + "name": "minecraft:warped_slab", + "id": -265 + }, + { + "name": "minecraft:warped_stairs", + "id": -255 + }, + { + "name": "minecraft:warped_standing_sign", + "id": -251 + }, + { + "name": "minecraft:warped_stem", + "id": -226 + }, + { + "name": "minecraft:warped_trapdoor", + "id": -247 + }, + { + "name": "minecraft:warped_wall_sign", + "id": -253 + }, + { + "name": "minecraft:warped_wart_block", + "id": -227 + }, + { + "name": "minecraft:water", + "id": 9 + }, + { + "name": "minecraft:water_bucket", + "id": 370 + }, + { + "name": "minecraft:waterlily", + "id": 111 + }, + { + "name": "minecraft:waxed_chiseled_copper", + "id": -764 + }, + { + "name": "minecraft:waxed_copper", + "id": -344 + }, + { + "name": "minecraft:waxed_copper_bulb", + "id": -780 + }, + { + "name": "minecraft:waxed_copper_door", + "id": -788 + }, + { + "name": "minecraft:waxed_copper_grate", + "id": -772 + }, + { + "name": "minecraft:waxed_copper_trapdoor", + "id": -796 + }, + { + "name": "minecraft:waxed_cut_copper", + "id": -351 + }, + { + "name": "minecraft:waxed_cut_copper_slab", + "id": -365 + }, + { + "name": "minecraft:waxed_cut_copper_stairs", + "id": -358 + }, + { + "name": "minecraft:waxed_double_cut_copper_slab", + "id": -372 + }, + { + "name": "minecraft:waxed_exposed_chiseled_copper", + "id": -765 + }, + { + "name": "minecraft:waxed_exposed_copper", + "id": -345 + }, + { + "name": "minecraft:waxed_exposed_copper_bulb", + "id": -781 + }, + { + "name": "minecraft:waxed_exposed_copper_door", + "id": -789 + }, + { + "name": "minecraft:waxed_exposed_copper_grate", + "id": -773 + }, + { + "name": "minecraft:waxed_exposed_copper_trapdoor", + "id": -797 + }, + { + "name": "minecraft:waxed_exposed_cut_copper", + "id": -352 + }, + { + "name": "minecraft:waxed_exposed_cut_copper_slab", + "id": -366 + }, + { + "name": "minecraft:waxed_exposed_cut_copper_stairs", + "id": -359 + }, + { + "name": "minecraft:waxed_exposed_double_cut_copper_slab", + "id": -373 + }, + { + "name": "minecraft:waxed_oxidized_chiseled_copper", + "id": -766 + }, + { + "name": "minecraft:waxed_oxidized_copper", + "id": -446 + }, + { + "name": "minecraft:waxed_oxidized_copper_bulb", + "id": -783 + }, + { + "name": "minecraft:waxed_oxidized_copper_door", + "id": -791 + }, + { + "name": "minecraft:waxed_oxidized_copper_grate", + "id": -775 + }, + { + "name": "minecraft:waxed_oxidized_copper_trapdoor", + "id": -799 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper", + "id": -447 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper_slab", + "id": -449 + }, + { + "name": "minecraft:waxed_oxidized_cut_copper_stairs", + "id": -448 + }, + { + "name": "minecraft:waxed_oxidized_double_cut_copper_slab", + "id": -450 + }, + { + "name": "minecraft:waxed_weathered_chiseled_copper", + "id": -767 + }, + { + "name": "minecraft:waxed_weathered_copper", + "id": -346 + }, + { + "name": "minecraft:waxed_weathered_copper_bulb", + "id": -782 + }, + { + "name": "minecraft:waxed_weathered_copper_door", + "id": -790 + }, + { + "name": "minecraft:waxed_weathered_copper_grate", + "id": -774 + }, + { + "name": "minecraft:waxed_weathered_copper_trapdoor", + "id": -798 + }, + { + "name": "minecraft:waxed_weathered_cut_copper", + "id": -353 + }, + { + "name": "minecraft:waxed_weathered_cut_copper_slab", + "id": -367 + }, + { + "name": "minecraft:waxed_weathered_cut_copper_stairs", + "id": -360 + }, + { + "name": "minecraft:waxed_weathered_double_cut_copper_slab", + "id": -374 + }, + { + "name": "minecraft:wayfinder_armor_trim_smithing_template", + "id": 713 + }, + { + "name": "minecraft:weathered_chiseled_copper", + "id": -762 + }, + { + "name": "minecraft:weathered_copper", + "id": -342 + }, + { + "name": "minecraft:weathered_copper_bulb", + "id": -778 + }, + { + "name": "minecraft:weathered_copper_door", + "id": -786 + }, + { + "name": "minecraft:weathered_copper_grate", + "id": -770 + }, + { + "name": "minecraft:weathered_copper_trapdoor", + "id": -794 + }, + { + "name": "minecraft:weathered_cut_copper", + "id": -349 + }, + { + "name": "minecraft:weathered_cut_copper_slab", + "id": -363 + }, + { + "name": "minecraft:weathered_cut_copper_stairs", + "id": -356 + }, + { + "name": "minecraft:weathered_double_cut_copper_slab", + "id": -370 + }, + { + "name": "minecraft:web", + "id": 30 + }, + { + "name": "minecraft:weeping_vines", + "id": -231 + }, + { + "name": "minecraft:wet_sponge", + "id": -984 + }, + { + "name": "minecraft:wheat", + "id": 342 + }, + { + "name": "minecraft:wheat_seeds", + "id": 296 + }, + { + "name": "minecraft:white_candle", + "id": -413 + }, + { + "name": "minecraft:white_candle_cake", + "id": -430 + }, + { + "name": "minecraft:white_carpet", + "id": 171 + }, + { + "name": "minecraft:white_concrete", + "id": 236 + }, + { + "name": "minecraft:white_concrete_powder", + "id": 237 + }, + { + "name": "minecraft:white_dye", + "id": 419 + }, + { + "name": "minecraft:white_glazed_terracotta", + "id": 220 + }, + { + "name": "minecraft:white_shulker_box", + "id": 218 + }, + { + "name": "minecraft:white_stained_glass", + "id": 241 + }, + { + "name": "minecraft:white_stained_glass_pane", + "id": 160 + }, + { + "name": "minecraft:white_terracotta", + "id": 159 + }, + { + "name": "minecraft:white_tulip", + "id": -835 + }, + { + "name": "minecraft:white_wool", + "id": 35 + }, + { + "name": "minecraft:wild_armor_trim_smithing_template", + "id": 704 + }, + { + "name": "minecraft:wind_charge", + "id": 260 + }, + { + "name": "minecraft:witch_spawn_egg", + "id": 462 + }, + { + "name": "minecraft:wither_rose", + "id": -216 + }, + { + "name": "minecraft:wither_skeleton_spawn_egg", + "id": 475 + }, + { + "name": "minecraft:wither_spawn_egg", + "id": 519 + }, + { + "name": "minecraft:wolf_armor", + "id": 723 + }, + { + "name": "minecraft:wolf_spawn_egg", + "id": 449 + }, + { + "name": "minecraft:wood", + "id": 758 + }, + { + "name": "minecraft:wooden_axe", + "id": 318 + }, + { + "name": "minecraft:wooden_button", + "id": 143 + }, + { + "name": "minecraft:wooden_door", + "id": 367 + }, + { + "name": "minecraft:wooden_hoe", + "id": 337 + }, + { + "name": "minecraft:wooden_pickaxe", + "id": 317 + }, + { + "name": "minecraft:wooden_pressure_plate", + "id": 72 + }, + { + "name": "minecraft:wooden_shovel", + "id": 316 + }, + { + "name": "minecraft:wooden_slab", + "id": 745 + }, + { + "name": "minecraft:wooden_sword", + "id": 315 + }, + { + "name": "minecraft:wool", + "id": 726 + }, + { + "name": "minecraft:writable_book", + "id": 527 + }, + { + "name": "minecraft:written_book", + "id": 528 + }, + { + "name": "minecraft:yellow_candle", + "id": -417 + }, + { + "name": "minecraft:yellow_candle_cake", + "id": -434 + }, + { + "name": "minecraft:yellow_carpet", + "id": -600 + }, + { + "name": "minecraft:yellow_concrete", + "id": -631 + }, + { + "name": "minecraft:yellow_concrete_powder", + "id": -712 + }, + { + "name": "minecraft:yellow_dye", + "id": 415 + }, + { + "name": "minecraft:yellow_glazed_terracotta", + "id": 224 + }, + { + "name": "minecraft:yellow_shulker_box", + "id": -616 + }, + { + "name": "minecraft:yellow_stained_glass", + "id": -676 + }, + { + "name": "minecraft:yellow_stained_glass_pane", + "id": -646 + }, + { + "name": "minecraft:yellow_terracotta", + "id": -727 + }, + { + "name": "minecraft:yellow_wool", + "id": -558 + }, + { + "name": "minecraft:zoglin_spawn_egg", + "id": 509 + }, + { + "name": "minecraft:zombie_horse_spawn_egg", + "id": 479 + }, + { + "name": "minecraft:zombie_pigman_spawn_egg", + "id": 458 + }, + { + "name": "minecraft:zombie_spawn_egg", + "id": 457 + }, + { + "name": "minecraft:zombie_villager_spawn_egg", + "id": 488 + } +] \ No newline at end of file diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index fc0cd83f1..15d3a20a6 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -54,9 +54,6 @@ remote: # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. auth-type: online - # Allow for password-based authentication methods through Geyser. Only useful in online mode. - # If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop. - allow-password-authentication: true # Whether to enable PROXY protocol or not while connecting to the server. # This is useful only when: # 1) Your server supports PROXY protocol (it probably doesn't) @@ -168,7 +165,6 @@ above-bedrock-nether-building: false force-resource-packs: true # Allows Xbox achievements to be unlocked. -# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating. xbox-achievements-enabled: false # Whether player IP addresses will be logged by the server. diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index afbf78bbe..7499daf71 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit afbf78bbe0b39d0a076a42c228828c12f7f7da90 +Subproject commit 7499daf712ad6de70a07fba471b51b4ad92315c5 diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index aaf53d695..93f207e7e 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit aaf53d6953c927e5ac1b87fd6627ffbfd4aa7cf5 +Subproject commit 93f207e7e9d73f58a7c8902f7deda9dcb0524c8e diff --git a/core/src/main/resources/permissions.yml b/core/src/main/resources/permissions.yml new file mode 100644 index 000000000..4da9251e8 --- /dev/null +++ b/core/src/main/resources/permissions.yml @@ -0,0 +1,9 @@ + +# Add any permissions here that all players should have. +# Permissions for builtin Geyser commands do not have to be listed here. + +# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary. +# If extensions don't register their permissions, permissions that everyone should have must be added here manually. + +default-permissions: + - geyser.command.help # this is unnecessary diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java new file mode 100644 index 000000000..523e4dca2 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java @@ -0,0 +1,270 @@ +/* + * 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.scoreboard.network; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import net.kyori.adventure.text.Component; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; +import org.junit.jupiter.api.Test; + +public class NameVisibilityScoreboardTest { + @Test + void playerVisibilityNever() { + mockContextScoreboard(context -> { + var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team1", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.NEVER, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"player1"} + ) + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, context); + }); + } + + @Test + void playerVisibilityHideForOtherTeam() { + mockContextScoreboard(context -> { + var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team1", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.HIDE_FOR_OTHER_TEAMS, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"player1"} + ) + ); + // only hidden if session player (Tim203) is in a team as well + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix"); + return packet; + }, context); + assertNoNextPacket(context); + + // create another team and add Tim203 to it + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team2", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.NEVER, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"Tim203"} + ) + ); + // Tim203 is now in another team, so it should be hidden + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, context); + assertNoNextPacket(context); + + // add Tim203 to same team as player1, score should be visible again + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"}) + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix"); + return packet; + }, context); + }); + } + + @Test + void playerVisibilityHideForOwnTeam() { + mockContextScoreboard(context -> { + var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team1", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.HIDE_FOR_OWN_TEAM, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"player1"} + ) + ); + // Tim203 is not in a team (let alone the same team), so should be visible + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix"); + return packet; + }, context); + assertNoNextPacket(context); + + // Tim203 is now in the same team as player1, so should be hidden + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"}) + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, context); + assertNoNextPacket(context); + + // create another team and add Tim203 to there, score should be visible again + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team2", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.NEVER, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"Tim203"} + ) + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix"); + return packet; + }, context); + }); + } + + @Test + void playerVisibilityAlways() { + mockContextScoreboard(context -> { + var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team1", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.ALWAYS, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"player1"} + ) + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix"); + return packet; + }, context); + + // adding self to another team shouldn't make a difference + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team2", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.ALWAYS, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{"Tim203"} + ) + ); + assertNoNextPacket(context); + + // adding self to player1 team shouldn't matter + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"}) + ); + assertNoNextPacket(context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java new file mode 100644 index 000000000..1ec245007 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/ScoreboardIssueTests.java @@ -0,0 +1,93 @@ +/* + * 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.scoreboard.network; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketType; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket; +import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; +import org.geysermc.geyser.entity.type.living.monster.EnderDragonPartEntity; +import org.geysermc.geyser.session.cache.EntityCache; +import org.geysermc.geyser.translator.protocol.java.entity.JavaRemoveEntitiesTranslator; +import org.geysermc.geyser.translator.protocol.java.entity.spawn.JavaAddExperienceOrbTranslator; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundRemoveEntitiesPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddExperienceOrbPacket; +import org.junit.jupiter.api.Test; + +/** + * Tests that don't fit in a larger system (e.g. sidebar objective) that were reported on GitHub + */ +public class ScoreboardIssueTests { + /** + * Test for #5075 + */ + @Test + void entityWithoutUuid() { + // experience orbs are the only known entities without an uuid, see Entity#teamIdentifier for more info + mockContextScoreboard(context -> { + var addExperienceOrbTranslator = new JavaAddExperienceOrbTranslator(); + var removeEntitiesTranslator = new JavaRemoveEntitiesTranslator(); + + // Entity#teamIdentifier used to throw because it returned uuid.toString where uuid could be null. + // this would result in both EntityCache#spawnEntity and EntityCache#removeEntity throwing an exception, + // because the entity would be registered and deregistered to the scoreboard. + assertDoesNotThrow(() -> { + context.translate(addExperienceOrbTranslator, new ClientboundAddExperienceOrbPacket(2, 0, 0, 0, 1)); + + String displayName = context.mockOrSpy(EntityCache.class).getEntityByJavaId(2).getDisplayName(); + assertEquals("entity.minecraft.experience_orb", displayName); + + context.translate(removeEntitiesTranslator, new ClientboundRemoveEntitiesPacket(new int[] { 2 })); + }); + + // we know that spawning and removing the entity should be fine + assertNextPacketType(context, AddEntityPacket.class); + assertNextPacketType(context, RemoveEntityPacket.class); + }); + } + + /** + * Test for #5078 + */ + @Test + void entityWithoutType() { + // dragon entity parts are an entity in Geyser, but do not have an entity type + mockContextScoreboard(context -> { + // EntityUtils#translatedEntityName used to not take null EntityType's into account, + // so it used to throw an exception + assertDoesNotThrow(() -> { + // dragon entity parts are not spawned using a packet, so we manually create an instance + var dragonHeadPart = new EnderDragonPartEntity(context.session(), 2, 2, 1, 1); + + String displayName = dragonHeadPart.getDisplayName(); + assertEquals("entity.unregistered_sadface", displayName); + }); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java new file mode 100644 index 000000000..5d8d8309f --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/belowname/BasicBelownameScoreboardTests.java @@ -0,0 +1,227 @@ +/* + * 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.scoreboard.network.belowname; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.junit.jupiter.api.Test; + +public class BasicBelownameScoreboardTests { + @Test + void displayWithNoPlayersAndRemove() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective") + ); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "") + ); + assertNoNextPacket(context); + }); + } + + @Test + void displayColorWithOnePlayer() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective", NamedTextColor.BLUE), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, "0 §r§9objective"); + return packet; + }, context); + }); + } + + @Test + void displayWithOnePlayerAndRemove() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective"); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, ""); + return packet; + }, context); + }); + } + + @Test + void overrideAndRemove() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + mockAndAddPlayerEntity(context, "player1", 2); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective1", + ObjectiveAction.ADD, + Component.text("objective1"), + ScoreType.INTEGER, + null + ) + ); + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective2", + ObjectiveAction.ADD, + Component.text("objective2"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective2") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective2"); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective1") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, ""); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective1"); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "") + ); + assertNextPacket(() -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.SCORE, ""); + return packet; + }, context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java new file mode 100644 index 000000000..a3d4ad671 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/playerlist/BasicPlayerlistScoreboardTests.java @@ -0,0 +1,204 @@ +/* + * 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.scoreboard.network.playerlist; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; +import org.junit.jupiter.api.Test; + +/* +Identical to sidebar + */ +public class BasicPlayerlistScoreboardTests { + @Test + void display() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("list"); + packet.setSortOrder(1); + return packet; + }, context); + }); + } + + @Test + void displayNameColors() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("§b§lobjective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("list"); + packet.setSortOrder(1); + return packet; + }, context); + }); + } + + @Test + void overrideWithOneScore() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective1", + ObjectiveAction.ADD, + Component.text("objective1"), + ScoreType.INTEGER, + null + ) + ); + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective2", + ObjectiveAction.ADD, + Component.text("objective2"), + ScoreType.INTEGER, + null + ) + ); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2)); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective2") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective2"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("list"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + // session player name is Tim203 + packet.setInfos(List.of(new ScoreInfo(1, "0", 2, ScoreInfo.ScorerType.PLAYER, 1))); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective1") + ); + assertNextPacket(() -> { + var packet = new RemoveObjectivePacket(); + packet.setObjectiveId("0"); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("2"); + packet.setDisplayName("objective1"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("list"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + // session player name is Tim203 + packet.setInfos(List.of(new ScoreInfo(3, "2", 1, ScoreInfo.ScorerType.PLAYER, 1))); + return packet; + }, context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java new file mode 100644 index 000000000..dd693022c --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java @@ -0,0 +1,756 @@ +/* + * 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.scoreboard.network.server; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; +import org.junit.jupiter.api.Test; + +public class CubecraftScoreboardTest { + @Test + void test() { + mockContextScoreboard(context -> { + var setTeamTranslator = new JavaSetPlayerTeamTranslator(); + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + // unused + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("SB_NoName", Component.text("SB_NoName"), Component.empty(), Component.empty(), true, true, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.RESET, new String[0])); + assertNoNextPacket(context); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "sidebar", + ObjectiveAction.ADD, + Component.text("sidebar"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "sidebar") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("sidebar"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + + + // Now they're going to create a bunch of teams and add players to those teams in a very inefficient way. + // Presumably this is a leftover from an old system, as these don't seem to do anything but hide their nametags. + // For which you could just use a single team. + + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "A_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "B_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "C_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "D_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "E_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "F_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "G_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "H_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "I_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", TeamAction.ADD_PLAYER, new String[] { "J_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "K_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", TeamAction.ADD_PLAYER, new String[] { "L_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "M_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "N_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", TeamAction.ADD_PLAYER, new String[] { "O_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "P_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "Q_Player" })); + + assertNoNextPacket(context); + + + // Now that those teams are created and people added to it, they set the final sidebar name and add the lines to it. + // They're also not doing this efficiently, because they don't add the players when the team is created. + // Instead, they send an additional packet. + + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "sidebar", + ObjectiveAction.UPDATE, + Component.empty() + .append(Component.text( + "CubeCraft", Style.style(NamedTextColor.WHITE, TextDecoration.BOLD))), + ScoreType.INTEGER, + null)); + assertNextPacket( + () -> { + var packet = new RemoveObjectivePacket(); + packet.setObjectiveId("0"); + return packet; + }, + context); + assertNextPacket( + () -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("§f§lCubeCraft"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-0", + Component.text("SB_l-0"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-0", TeamAction.ADD_PLAYER, new String[] {"§0§0"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-0", + Component.text("SB_l-0"), + Component.empty().append(Component.text("", Style.style(NamedTextColor.BLACK))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§0", "sidebar", 10)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 10, "§r§0§0§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-1", + Component.text("SB_l-1"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-1", TeamAction.ADD_PLAYER, new String[] {"§0§1"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-1", + Component.text("SB_l-1"), + Component.empty() + .append(Component.textOfChildren( + Component.text("User: ", TextColor.color(0x3aa9ff)), + Component.text("Tim203", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§1", "sidebar", 9)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(2, "0", 9, "§bUser: §r§fTim203§r§0§1§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-2", + Component.text("SB_l-2"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-2", TeamAction.ADD_PLAYER, new String[] {"§0§2"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-2", + Component.text("SB_l-2"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Rank: ", TextColor.color(0x3aa9ff)), + Component.text("\uE1AB ", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§2", "sidebar", 8)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(3, "0", 8, "§bRank: §r§f\uE1AB §r§0§2§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-3", + Component.text("SB_l-3"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-3", TeamAction.ADD_PLAYER, new String[] {"§0§3"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-3", + Component.text("SB_l-3"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§3", "sidebar", 7)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(4, "0", 7, "§r§0§3§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-4", + Component.text("SB_l-4"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-4", TeamAction.ADD_PLAYER, new String[] {"§0§4"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-4", + Component.text("SB_l-4"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§4", "sidebar", 6)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(5, "0", 6, "§r§0§4§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-5", + Component.text("SB_l-5"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-5", TeamAction.ADD_PLAYER, new String[] {"§0§5"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-5", + Component.text("SB_l-5"), + Component.empty().append(Component.text("", NamedTextColor.DARK_BLUE)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§5", "sidebar", 5)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(6, "0", 5, "§r§0§5§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-6", + Component.text("SB_l-6"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-6", TeamAction.ADD_PLAYER, new String[] {"§0§6"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-6", + Component.text("SB_l-6"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Lobby: ", TextColor.color(0x3aa9ff)), + Component.text("EU #10", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§6", "sidebar", 4)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(7, "0", 4, "§bLobby: §r§fEU #10§r§0§6§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-7", + Component.text("SB_l-7"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-7", TeamAction.ADD_PLAYER, new String[] {"§0§7"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-7", + Component.text("SB_l-7"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Players: ", TextColor.color(0x3aa9ff)), + Component.text("783", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§7", "sidebar", 3)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(8, "0", 3, "§bPlayers: §r§f783§r§0§7§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-8", + Component.text("SB_l-8"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-8", TeamAction.ADD_PLAYER, new String[] {"§0§8"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-8", + Component.text("SB_l-8"), + Component.empty().append(Component.text("", NamedTextColor.DARK_GREEN)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§8", "sidebar", 2)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(9, "0", 2, "§r§0§8§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-9", + Component.text("SB_l-9"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-9", TeamAction.ADD_PLAYER, new String[] {"§0§9"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-9", + Component.text("SB_l-9"), + Component.empty().append(Component.text("24/09/24 (g2208)", TextColor.color(0x777777))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§9", "sidebar", 1)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(10, "0", 1, "§824/09/24 (g2208)§r§0§9§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-10", + Component.text("SB_l-10"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-10", TeamAction.ADD_PLAYER, new String[] {"§0§a"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-10", + Component.text("SB_l-10"), + Component.empty().append(Component.text("play.cubecraft.net", NamedTextColor.GOLD)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§a", "sidebar", 0)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(11, "0", 0, "§6play.cubecraft.net§r§0§a§r"))); + return packet; + }, + context); + + // after this we get a ClientboundPlayerInfoUpdatePacket with the action UPDATE_DISPLAY_NAME, + // but that one is only shown in the tablist so we don't have to handle that. + // And after that we get each player's ClientboundPlayerInfoUpdatePacket with also a UPDATE_DISPLAY_NAME, + // which is also not interesting for us. + // CubeCraft seems to use two armor stands per player: 1 for the rank badge and 1 for the player name. + // So the only thing we have to verify is that the nametag is hidden + + mockAndAddPlayerEntity(context, "A_Player", 2); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "B_Player", 3); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(3); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "E_Player", 4); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(4); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "H_Player", 5); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(5); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "J_Player", 6); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(6); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "K_Player", 7); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(7); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "L_Player", 8); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(8); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "O_Player", 9); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(9); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java new file mode 100644 index 000000000..b3999303e --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/BasicSidebarScoreboardTests.java @@ -0,0 +1,218 @@ +/* + * 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.scoreboard.network.sidebar; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; +import org.junit.jupiter.api.Test; + +/* +Identical to playerlist + */ +public class BasicSidebarScoreboardTests { + @Test + void displayAndRemove() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("list"); + packet.setSortOrder(1); + return packet; + }, context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "") + ); + assertNextPacket(() -> { + var packet = new RemoveObjectivePacket(); + packet.setObjectiveId("0"); + return packet; + }, context); + }); + } + + @Test + void displayNameColors() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("§b§lobjective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + }); + } + + @Test + void override() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective1", + ObjectiveAction.ADD, + Component.text("objective1"), + ScoreType.INTEGER, + null + ) + ); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective2", + ObjectiveAction.ADD, + Component.text("objective2"), + ScoreType.INTEGER, + null + ) + ); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2)); + assertNoNextPacket(context); + + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective2") + ); + + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective2"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "Tim203"))); + return packet; + }, context); + assertNoNextPacket(context); + + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective1") + ); + + assertNextPacket(() -> { + var packet = new RemoveObjectivePacket(); + packet.setObjectiveId("0"); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("2"); + packet.setDisplayName("objective1"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(3, "2", 1, "Tim203"))); + return packet; + }, context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java new file mode 100644 index 000000000..3e0be1c02 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/OrderAndLimitSidebarScoreboardTests.java @@ -0,0 +1,533 @@ +/* + * 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.scoreboard.network.sidebar; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import java.util.List; +import net.kyori.adventure.text.Component; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaResetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundResetScorePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; +import org.junit.jupiter.api.Test; + +public class OrderAndLimitSidebarScoreboardTests { + @Test + void aboveDisplayLimit() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + var resetScoreTranslator = new JavaResetScorePacket(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + + // some are in an odd order to make sure that there is no bias for which score is send first, + // and to make sure that the score value also doesn't influence the order + context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17)); + assertNoNextPacket(context); + + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(1, "0", 17, "q"), + new ScoreInfo(2, "0", 16, "j"), + new ScoreInfo(3, "0", 15, "l"), + new ScoreInfo(4, "0", 14, "k"), + new ScoreInfo(5, "0", 13, "m"), + new ScoreInfo(6, "0", 12, "n"), + new ScoreInfo(7, "0", 11, "o"), + new ScoreInfo(8, "0", 10, "p"), + new ScoreInfo(9, "0", 9, "g"), + new ScoreInfo(10, "0", 8, "h"), + new ScoreInfo(11, "0", 7, "i"), + new ScoreInfo(12, "0", 6, "f"), + new ScoreInfo(13, "0", 5, "d"), + new ScoreInfo(14, "0", 4, "e"), + new ScoreInfo(15, "0", 3, "c") + )); + return packet; + }, context); + assertNoNextPacket(context); + + // remove a score + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("m", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b"))); + return packet; + }, context); + + // add a score + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("aa", "objective", 13) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa"))); + return packet; + }, context); + + // add score with same score value (after) + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("ga", "objective", 9) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(15, "0", 3, "c"), + new ScoreInfo(9, "0", 9, "§0§rg") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(9, "0", 9, "§0§rg"), + new ScoreInfo(18, "0", 9, "§1§rga") + )); + return packet; + }, context); + + // add another score with same score value (before all) + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("ag", "objective", 9) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(14, "0", 4, "e"), + new ScoreInfo(9, "0", 9, "§1§rg"), + new ScoreInfo(18, "0", 9, "§2§rga") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(19, "0", 9, "§0§rag"), + new ScoreInfo(9, "0", 9, "§1§rg"), + new ScoreInfo(18, "0", 9, "§2§rga") + )); + return packet; + }, context); + + // remove score with same value + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("g", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(9, "0", 9, "§1§rg"), + new ScoreInfo(18, "0", 9, "§1§rga") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(18, "0", 9, "§1§rga"), + new ScoreInfo(20, "0", 4, "e") + )); + return packet; + }, context); + + // remove the other score with the same value + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("ga", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(18, "0", 9, "§1§rga"), + new ScoreInfo(19, "0", 9, "ag") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(19, "0", 9, "ag"), + new ScoreInfo(21, "0", 3, "c") + )); + return packet; + }, context); + }); + } + + @Test + void aboveDisplayLimitWithTeam() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + var resetScoreTranslator = new JavaResetScorePacket(); + var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + + // some are in an odd order to make sure that there is no bias for which score is send first, + // and to make sure that the score value also doesn't influence the order + context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16)); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17)); + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team1", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.ALWAYS, + CollisionRule.NEVER, + TeamColor.DARK_RED, + new String[]{ "f", "o" } + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(1, "0", 17, "q"), + new ScoreInfo(2, "0", 16, "j"), + new ScoreInfo(3, "0", 15, "l"), + new ScoreInfo(4, "0", 14, "k"), + new ScoreInfo(5, "0", 13, "m"), + new ScoreInfo(6, "0", 12, "n"), + new ScoreInfo(7, "0", 11, "§4prefix§r§4o§r§4suffix"), + new ScoreInfo(8, "0", 10, "p"), + new ScoreInfo(9, "0", 9, "g"), + new ScoreInfo(10, "0", 8, "h"), + new ScoreInfo(11, "0", 7, "i"), + new ScoreInfo(12, "0", 6, "§4prefix§r§4f§r§4suffix"), + new ScoreInfo(13, "0", 5, "d"), + new ScoreInfo(14, "0", 4, "e"), + new ScoreInfo(15, "0", 3, "c") + )); + return packet; + }, context); + assertNoNextPacket(context); + + // remove a score + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("m", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b"))); + return packet; + }, context); + + // add a score + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("aa", "objective", 13) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa"))); + return packet; + }, context); + + // add some teams for the upcoming score adds + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team2", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.ALWAYS, + CollisionRule.NEVER, + TeamColor.DARK_AQUA, + new String[]{ "oa" } + ) + ); + context.translate( + setPlayerTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "team3", + Component.text("displayName"), + Component.text("prefix"), + Component.text("suffix"), + false, + false, + NameTagVisibility.ALWAYS, + CollisionRule.NEVER, + TeamColor.DARK_PURPLE, + new String[]{ "ao" } + ) + ); + assertNoNextPacket(context); + + // add a score that on Java should be after 'o', but would be before on Bedrock without manual order + // due to the team color + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("oa", "objective", 11) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(15, "0", 3, "c"), + new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix"), + new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix") + )); + return packet; + }, context); + + // add a score that on Java should be before 'o', but would be after on Bedrock without manual order + // due to the team color + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("ao", "objective", 11) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(14, "0", 4, "e"), + new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"), + new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(19, "0", 11, "§0§r§5prefix§r§5ao§r§5suffix"), + new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"), + new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix") + )); + return packet; + }, context); + + // remove original 'o' score + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("o", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"), + new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"), + new ScoreInfo(20, "0", 4, "e") + )); + return packet; + }, context); + + // remove the other score with the same value as 'o' + context.translate( + resetScoreTranslator, + new ClientboundResetScorePacket("oa", "objective") + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of( + new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"), + new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix") + )); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of( + new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix"), + new ScoreInfo(21, "0", 3, "c") + )); + return packet; + }, context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java new file mode 100644 index 000000000..0a02a58d9 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/sidebar/VanillaSidebarScoreboardTests.java @@ -0,0 +1,265 @@ +/* + * 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.scoreboard.network.sidebar; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; + +import java.util.List; +import net.kyori.adventure.text.Component; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator; +import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; +import org.junit.jupiter.api.Test; + +public class VanillaSidebarScoreboardTests { + @Test + void displayAndAddScore() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1)); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner"))); + return packet; + }, context); + }); + } + + @Test + void displayAndChangeScoreValue() { + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1)); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner"))); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 2)); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "owner"))); + return packet; + }, context); + }); + } + + @Test + void displayAndChangeScoreDisplayName() { + // this ensures that MCPE-143063 is properly handled + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1)); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner"))); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("owner", "objective", 1).withDisplay(Component.text("hi")) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi"))); + return packet; + }, context); + }); + } + + @Test + void displayAndChangeScoreDisplayNameAndValue() { + // this ensures that MCPE-143063 is properly handled + mockContextScoreboard(context -> { + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "objective", + ObjectiveAction.ADD, + Component.text("objective"), + ScoreType.INTEGER, + null + ) + ); + context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1)); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("objective"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner"))); + return packet; + }, context); + assertNoNextPacket(context); + + context.translate( + setScoreTranslator, + new ClientboundSetScorePacket("owner", "objective", 2).withDisplay(Component.text("hi")) + ); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.REMOVE); + packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi"))); + return packet; + }, context); + assertNextPacket(() -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi"))); + return packet; + }, context); + }); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java new file mode 100644 index 000000000..b15994533 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/AssertUtils.java @@ -0,0 +1,60 @@ +/* + * 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.scoreboard.network.util; + +import java.util.Collections; +import java.util.function.Supplier; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.junit.jupiter.api.Assertions; + +public class AssertUtils { + public static void assertContextEquals(Supplier expected, T actual) { + if (actual == null) { + Assertions.fail("Expected another packet! " + expected.get()); + } + Assertions.assertEquals(expected.get(), actual); + } + + public static void assertNextPacket(Supplier expected, GeyserMockContext context) { + assertContextEquals(expected, context.nextPacket()); + } + + public static void assertNextPacketType(GeyserMockContext context, Class type) { + var actual = context.nextPacket(); + if (actual == null) { + Assertions.fail("Expected another packet! " + type); + } + Assertions.assertEquals(type, actual.getClass()); + } + + public static void assertNoNextPacket(GeyserMockContext context) { + Assertions.assertEquals( + Collections.emptyList(), + context.packets(), + "Expected no remaining packets, got " + context.packetCount() + ); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java new file mode 100644 index 000000000..f147e766d --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/EmptyGeyserLogger.java @@ -0,0 +1,75 @@ +/* + * 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.scoreboard.network.util; + +import org.geysermc.geyser.GeyserLogger; + +public class EmptyGeyserLogger implements GeyserLogger { + @Override + public void severe(String message) { + + } + + @Override + public void severe(String message, Throwable error) { + + } + + @Override + public void error(String message) { + + } + + @Override + public void error(String message, Throwable error) { + + } + + @Override + public void warning(String message) { + + } + + @Override + public void info(String message) { + + } + + @Override + public void debug(String message) { + + } + + @Override + public void setDebug(boolean debug) { + + } + + @Override + public boolean isDebug() { + return false; + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java new file mode 100644 index 000000000..72515d714 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java @@ -0,0 +1,143 @@ +/* + * 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.scoreboard.network.util; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class GeyserMockContext { + private final List mocksAndSpies = new ArrayList<>(); + private final List storedObjects = new ArrayList<>(); + private final List packets = Collections.synchronizedList(new ArrayList<>()); + private MockedStatic geyserImplMock; + + public static void mockContext(Consumer geyserContext) { + var context = new GeyserMockContext(); + + var geyserImpl = context.mock(GeyserImpl.class); + var config = context.mock(GeyserConfiguration.class); + + when(config.getScoreboardPacketThreshold()).thenReturn(1_000); + + when(geyserImpl.getConfig()).thenReturn(config); + + var logger = context.storeObject(new EmptyGeyserLogger()); + when(geyserImpl.getLogger()).thenReturn(logger); + + try (var mocked = mockStatic(GeyserImpl.class)) { + mocked.when(GeyserImpl::getInstance).thenReturn(geyserImpl); + context.geyserImplMock = mocked; + geyserContext.accept(context); + } + } + + public static void mockContext(Runnable runnable) { + mockContext(context -> runnable.run()); + } + + public T mock(Class type) { + return addMockOrSpy(Mockito.mock(type)); + } + + public T spy(T object) { + return addMockOrSpy(Mockito.spy(object)); + } + + private T addMockOrSpy(T mockOrSpy) { + mocksAndSpies.add(mockOrSpy); + return mockOrSpy; + } + + public T storeObject(T object) { + storedObjects.add(object); + return object; + } + + /** + * Retries the mock or spy that is an instance of the specified type. + * This is only really intended for classes where you only need a single instance of. + */ + public T mockOrSpy(Class type) { + for (Object mock : mocksAndSpies) { + if (type.isInstance(mock)) { + return type.cast(mock); + } + } + return null; + } + + public T storedObject(Class type) { + for (Object storedObject : storedObjects) { + if (type.isInstance(storedObject)) { + return type.cast(storedObject); + } + } + return null; + } + + public GeyserSession session() { + return mockOrSpy(GeyserSession.class); + } + + void addPacket(BedrockPacket packet) { + packets.add(packet); + } + + public int packetCount() { + return packets.size(); + } + + public BedrockPacket nextPacket() { + if (packets.isEmpty()) { + return null; + } + return packets.remove(0); + } + + public List packets() { + return Collections.unmodifiableList(packets); + } + + public void translate(PacketTranslator translator, T packet) { + translator.translate(session(), packet); + } + + public MockedStatic geyserImplMock() { + return geyserImplMock; + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java new file mode 100644 index 000000000..bc76a1b70 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java @@ -0,0 +1,96 @@ +/* + * 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.scoreboard.network.util; + +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContext.mockContext; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.UUID; +import java.util.function.Consumer; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.EntityCache; +import org.geysermc.geyser.session.cache.WorldCache; +import org.mockito.stubbing.Answer; + +public class GeyserMockContextScoreboard { + public static void mockContextScoreboard(Consumer geyserContext) { + mockContext(context -> { + createSessionSpy(context); + geyserContext.accept(context); + + assertNoNextPacket(context); + }); + } + + private static void createSessionSpy(GeyserMockContext context) { + // GeyserSession has so many dependencies, it's easier to just mock it + var session = context.mock(GeyserSession.class); + + when(session.getGeyser()).thenReturn(context.mockOrSpy(GeyserImpl.class)); + + when(session.locale()).thenReturn("en_US"); + + doAnswer((Answer) invocation -> { + context.addPacket(invocation.getArgument(0, BedrockPacket.class)); + return null; + }).when(session).sendUpstreamPacket(any()); + + // SessionPlayerEntity loads stuff in like blocks, which is not what we want + var playerEntity = context.mock(SessionPlayerEntity.class); + when(playerEntity.getGeyserId()).thenReturn(1L); + when(playerEntity.getUsername()).thenReturn("Tim203"); + when(session.getPlayerEntity()).thenReturn(playerEntity); + + var entityCache = context.spy(new EntityCache(session)); + when(session.getEntityCache()).thenReturn(entityCache); + + var worldCache = context.spy(new WorldCache(session)); + when(session.getWorldCache()).thenReturn(worldCache); + + // disable global scoreboard updater + when(worldCache.increaseAndGetScoreboardPacketsPerSecond()).thenReturn(0); + } + + public static PlayerEntity mockAndAddPlayerEntity(GeyserMockContext context, String username, long geyserId) { + var playerEntity = spy(new PlayerEntity(context.session(), geyserId, UUID.randomUUID(), username)); + // fake the player being spawned + when(playerEntity.isValid()).thenReturn(true); + + var entityCache = context.mockOrSpy(EntityCache.class); + entityCache.addPlayerEntity(playerEntity); + // called when the player spawns + entityCache.cacheEntity(playerEntity); + return playerEntity; + } +} diff --git a/gradle.properties b/gradle.properties index a222b1d99..1ee0e12cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,12 @@ # Gradle settings org.gradle.jvmargs=-Xmx4G org.gradle.daemon=false +org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.caching=true org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.4.0-SNAPSHOT +version=2.4.3-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 49c02d190..3a3831044 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -base-api = "1.0.0-SNAPSHOT" +base-api = "1.0.1" cumulus = "1.1.2" -erosion = "1.1-20240515.191456-1" +erosion = "1.1-20240521.000109-3" events = "1.1-SNAPSHOT" jackson = "2.17.0" fastutil = "8.5.2" @@ -10,10 +10,12 @@ netty-io-uring = "0.0.25.Final-SNAPSHOT" guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" -protocol = "3.0.0.Beta2-20240704.153116-14" +protocol-connection = "3.0.0.Beta5-20240916.181041-6" +protocol-common = "3.0.0.Beta5-20240916.181041-6" +protocol-codec = "3.0.0.Beta5-20240916.181041-6" raknet = "1.0.0.CR3-20240416.144209-1" -mcauthlib = "e5b0bcc" -mcprotocollib = "1.21-20240616.154144-5" +minecraftauth = "4.1.1" +mcprotocollib = "1.21-20241010.155958-24" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" @@ -24,16 +26,20 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.9.2" adapters = "1.13-SNAPSHOT" +cloud = "2.0.0-rc.2" +cloud-minecraft = "2.0.0-beta.9" +cloud-minecraft-modded = "2.0.0-beta.7" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.3.0-SNAPSHOT" -viaproxy = "3.2.1" +viaproxy = "3.3.2-SNAPSHOT" fabric-loader = "0.15.11" fabric-api = "0.100.1+1.21" -fabric-permissions = "0.2-SNAPSHOT" -neoforge-minecraft = "21.0.0-beta" +neoforge-minecraft = "21.1.1" mixin = "0.8.5" -minecraft = "1.21" +mixinextras = "0.3.5" +minecraft = "1.21.1" +mockito = "5.+" # plugin versions indra = "3.1.3" @@ -85,17 +91,23 @@ jline-terminal = { group = "org.jline", name = "jline-terminal", version.ref = " jline-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" } jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jline" } +cloud-core = { group = "org.incendo", name = "cloud-core", version.ref = "cloud" } +cloud-paper = { group = "org.incendo", name = "cloud-paper", version.ref = "cloud-minecraft" } +cloud-velocity = { group = "org.incendo", name = "cloud-velocity", version.ref = "cloud-minecraft" } +cloud-bungee = { group = "org.incendo", name = "cloud-bungee", version.ref = "cloud-minecraft" } +cloud-fabric = { group = "org.incendo", name = "cloud-fabric", version.ref = "cloud-minecraft-modded" } +cloud-neoforge = { group = "org.incendo", name = "cloud-neoforge", version.ref = "cloud-minecraft-modded" } + folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" } -paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" } mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } +mixinextras = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinextras" } minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" } # Check these on https://modmuss50.me/fabric.html fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } -fabric-permissions = { group = "me.lucko", name = "fabric-permissions-api", version.ref = "fabric-permissions" } neoforge-minecraft = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-minecraft" } @@ -107,7 +119,7 @@ commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } -mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" } +minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" } mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" } raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" } terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" } @@ -116,13 +128,16 @@ viaproxy = { group = "net.raphimc", name = "ViaProxy", version.ref = "viaproxy" viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" } websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" } -protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol" } -protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" } -protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol" } +protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol-common" } +protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol-codec" } +protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" } math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" } +mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } + # plugins +lombok = { group = "io.freefair.gradle", name = "lombok-plugin", version.ref = "lombok" } indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" } shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" } architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" } @@ -130,7 +145,6 @@ architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury. minotaur = { group = "com.modrinth.minotaur", name = "Minotaur", version.ref = "minotaur" } [plugins] -lombok = { id = "io.freefair.lombok", version.ref = "lombok" } indra = { id = "net.kyori.indra", version.ref = "indra" } blossom = { id = "net.kyori.blossom", version.ref = "blossom" } diff --git a/settings.gradle.kts b/settings.gradle.kts index a39bfa3d2..9aaf6ba59 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,52 +2,6 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -dependencyResolutionManagement { - repositories { - // mavenLocal() - - // Floodgate, Cumulus etc. - maven("https://repo.opencollab.dev/main") - - // Paper, Velocity - maven("https://repo.papermc.io/repository/maven-public") - // Spigot - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { - mavenContent { snapshotsOnly() } - } - - // BungeeCord - maven("https://oss.sonatype.org/content/repositories/snapshots") { - mavenContent { snapshotsOnly() } - } - - // NeoForge - maven("https://maven.neoforged.net/releases") { - mavenContent { releasesOnly() } - } - - // Minecraft - maven("https://libraries.minecraft.net") { - name = "minecraft" - mavenContent { releasesOnly() } - } - - mavenCentral() - - // ViaVersion - maven("https://repo.viaversion.com") { - name = "viaversion" - } - - maven("https://jitpack.io") { - content { includeGroupByRegex("com\\.github\\..*") } - } - - // For Adventure snapshots - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - } -} - pluginManagement { repositories { gradlePluginPortal()