diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9d925c4dc..39e9fe188 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -19,12 +19,19 @@ jobs: uses: snickerbockers/submodules-init@v4 - name: Build with Gradle run: ./gradlew build + + - name: Archive artifacts (Geyser Fabric) + uses: actions/upload-artifact@v2 + if: success() + with: + name: Geyser Fabric + path: bootstrap/fabric/build/libs/Geyser-Fabric.jar - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@v2 if: success() with: name: Geyser Standalone - path: bootstrap/standalone/build/libs/Geyser.jar + path: bootstrap/standalone/build/libs/Geyser-Standalone.jar - name: Archive artifacts (Geyser Spigot) uses: actions/upload-artifact@v2 if: success() diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 598cab46a..000000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: SonarCloud -on: - push: - branches: - - master -jobs: - build: - name: SonarCloud - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - submodules: true - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: 17 - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=GeyserMC_Geyser \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index b3df4bc95..072f99154 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/*-sources.jar,bootstrap/**/build/libs/*-unshaded.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/Geyser-*.jar', fingerprint: true } } } @@ -29,7 +29,6 @@ pipeline { when { anyOf { branch "master" - branch "feature/extensions" } } @@ -50,7 +49,7 @@ pipeline { rootDir: "", useWrapper: true, buildFile: 'build.gradle.kts', - tasks: 'build artifactoryPublish', + tasks: 'artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) @@ -102,7 +101,6 @@ pipeline { success { script { if (env.BRANCH_NAME == 'master') { - build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.18', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] } } diff --git a/README.md b/README.md index 796170dfd..dc6e21b1a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.10 and Minecraft Java 1.19.0. +### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.51 and Minecraft Java 1.19.3. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. @@ -34,7 +34,6 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge ## What's Left to be Added/Fixed - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) -- Resource pack conversion/CustomModelData - Some Entity Flags - Structure block UI @@ -43,9 +42,8 @@ There are a few things Geyser is unable to support due to various differences be ## Compiling 1. Clone the repo to your computer -2. [Install Maven](https://maven.apache.org/install.html) -3. Navigate to the Geyser root directory and run `git submodule update --init --recursive`. This command downloads all the needed submodules for Geyser and is a crucial step in this process. -4. Run `mvn clean install` and locate to the `target` folder. +2. Navigate to the Geyser root directory and run `git submodule update --init --recursive`. This command downloads all the needed submodules for Geyser and is a crucial step in this process. +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 diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index d7500fdaa..6b6fb8f46 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -1 +1,7 @@ -provided("net.kyori", "event-api", Versions.eventVersion) \ No newline at end of file +dependencies { + api(libs.cumulus) + api(libs.events) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "org.lanternpowered", module = "lmbda") + } +} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java index 9f315faf4..7543d1661 100644 --- a/api/base/src/main/java/org/geysermc/api/Geyser.java +++ b/api/base/src/main/java/org/geysermc/api/Geyser.java @@ -39,6 +39,7 @@ public class Geyser { * * @return the base api */ + @NonNull public static GeyserApiBase api() { if (api == null) { throw new RuntimeException("Api has not been registered yet!"); @@ -69,7 +70,7 @@ public class Geyser { /** * Registers the given api type. The api cannot be - * registered if {@link #registered()} is true as + * registered if {@link #isRegistered()} is true as * an api has already been specified. * * @param api the api @@ -88,7 +89,7 @@ public class Geyser { * * @return if the api has been registered */ - public static boolean registered() { + public static boolean isRegistered() { return api != null; } } diff --git a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java index e5105b1be..a845e37fd 100644 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java @@ -25,9 +25,13 @@ package org.geysermc.api; +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.geysermc.api.connection.Connection; +import org.geysermc.cumulus.form.Form; +import org.geysermc.cumulus.form.util.FormBuilder; import java.util.List; import java.util.UUID; @@ -37,52 +41,88 @@ import java.util.UUID; */ public interface GeyserApiBase { /** - * Gets the session from the given UUID, if applicable. The player must be logged in to the Java server + * Gets the connection from the given UUID, if applicable. The player must be logged in to the Java server * for this to return a non-null value. * - * @param uuid the UUID of the session - * @return the session from the given UUID, if applicable + * @param uuid the UUID of the connection + * @return the connection from the given UUID, if applicable */ @Nullable Connection connectionByUuid(@NonNull UUID uuid); /** - * Gets the session from the given - * XUID, if applicable. + * Gets the connection from the given XUID, if applicable. This method only works for online connections. * * @param xuid the XUID of the session - * @return the session from the given UUID, if applicable + * @return the connection from the given UUID, if applicable */ @Nullable Connection connectionByXuid(@NonNull String xuid); /** - * Gets the session from the given - * name, if applicable. + * Method to determine if the given online player is a Bedrock player. * - * @param name the uuid of the session - * @return the session from the given name, if applicable + * @param uuid the uuid of the online player + * @return true if the given online player is a Bedrock player */ - @Nullable - Connection connectionByName(@NonNull String name); + boolean isBedrockPlayer(@NonNull UUID uuid); /** - * Gets all the online sessions. + * Sends a form to the given connection and opens it. * - * @return all the online sessions + * @param uuid the uuid of the connection to open it on + * @param form the form to send + * @return whether the form was successfully sent + */ + boolean sendForm(@NonNull UUID uuid, @NonNull Form form); + + /** + * Sends a form to the given connection and opens it. + * + * @param uuid the uuid of the connection to open it on + * @param formBuilder the formBuilder to send + * @return whether the form was successfully sent + */ + boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder); + + /** + * Transfer the given connection to a server. A Bedrock player can successfully transfer to the same server they are + * currently playing on. + * + * @param uuid the uuid of the connection + * @param address the address of the server + * @param port the port of the server + * @return true if the transfer was a success + */ + boolean transfer(@NonNull UUID uuid, @NonNull String address, @IntRange(from = 0, to = 65535) int port); + + + /** + * Returns all the online connections. */ @NonNull List onlineConnections(); /** - * @return the major API version. Bumped whenever a significant breaking change or feature addition is added. + * Returns the amount of online connections. + */ + int onlineConnectionsCount(); + + /** + * Returns the prefix used by Floodgate. Will be null when the auth-type isn't Floodgate. + */ + @MonotonicNonNull + String usernamePrefix(); + + /** + * Returns the major API version. Bumped whenever a significant breaking change or feature addition is added. */ default int majorApiVersion() { return 1; } /** - * @return the minor API version. May be bumped for new API additions. + * Returns the minor API version. May be bumped for new API additions. */ default int minorApiVersion() { return 0; diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java index fc6cdae20..1cd7a9d13 100644 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/connection/Connection.java @@ -25,43 +25,96 @@ package org.geysermc.api.connection; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.value.qual.IntRange; +import org.geysermc.api.util.BedrockPlatform; +import org.geysermc.api.util.InputMode; +import org.geysermc.api.util.UiProfile; +import org.geysermc.cumulus.form.Form; +import org.geysermc.cumulus.form.util.FormBuilder; import java.util.UUID; /** * Represents a player connection. */ -@NonNull public interface Connection { /** - * Gets the name of the connection. - * - * @return the name of the connection + * Returns the bedrock name of the connection. */ - String name(); + @NonNull String bedrockUsername(); /** - * Gets the {@link UUID} of the connection. - * - * @return the UUID of the connection + * Returns the java name of the connection. */ - UUID uuid(); + @MonotonicNonNull + String javaUsername(); /** - * Gets the XUID of the connection. - * - * @return the XUID of the connection + * Returns the UUID of the connection. */ - String xuid(); + @MonotonicNonNull + UUID javaUuid(); + + /** + * Returns the XUID of the connection. + */ + @NonNull String xuid(); + + /** + * Returns the version of the Bedrock client. + */ + @NonNull String version(); + + /** + * Returns the platform that the connection is playing on. + */ + @NonNull BedrockPlatform platform(); + + /** + * Returns the language code of the connection. + */ + @NonNull String languageCode(); + + /** + * Returns the User Interface Profile of the connection. + */ + @NonNull UiProfile uiProfile(); + + /** + * Returns the Input Mode of the Bedrock client. + */ + @NonNull InputMode inputMode(); + + /** + * Returns whether the connection is linked. + * This will always return false when the auth-type isn't Floodgate. + */ + boolean isLinked(); + + /** + * Sends a form to the connection and opens it. + * + * @param form the form to send + * @return whether the form was successfully sent + */ + boolean sendForm(@NonNull Form form); + + /** + * Sends a form to the connection and opens it. + * + * @param formBuilder the formBuilder to send + * @return whether the form was successfully sent + */ + boolean sendForm(@NonNull FormBuilder formBuilder); /** * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are * currently playing on. * - * @param address The address of the server - * @param port The port of the server + * @param address the address of the server + * @param port the port of the server * @return true if the transfer was a success */ boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java similarity index 53% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java rename to api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java index 9a04b697c..15d0da027 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java @@ -23,60 +23,51 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.event; +package org.geysermc.api.util; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.extension.Extension; -/** - * Represents a subscribed listener to a {@link Event}. Wraps around - * the event and is capable of unsubscribing from the event or give - * information about it. - * - * @param the class of the event - */ -public interface EventSubscription { +public enum BedrockPlatform { + UNKNOWN("Unknown"), + GOOGLE("Android"), + IOS("iOS"), + OSX("macOS"), + AMAZON("Amazon"), + GEARVR("Gear VR"), + HOLOLENS("Hololens"), + UWP("Windows"), + WIN32("Windows x86"), + DEDICATED("Dedicated"), + TVOS("Apple TV"), + PS4("PS4"), + NX("Switch"), + XBOX("Xbox One"), + WINDOWS_PHONE("Windows Phone"); + + private static final BedrockPlatform[] VALUES = values(); + + private final String displayName; + + BedrockPlatform(String displayName) { + this.displayName = displayName; + } /** - * Gets the event class. + * Get the BedrockPlatform from the identifier. * - * @return the event class + * @param id the BedrockPlatform identifier + * @return The BedrockPlatform or {@link #UNKNOWN} if the platform wasn't found */ @NonNull - Class eventClass(); + public static BedrockPlatform fromId(int id) { + return id < VALUES.length ? VALUES[id] : VALUES[0]; + } /** - * Gets the {@link Extension} that owns this - * event subscription. - * - * @return the extension that owns this subscription + * @return friendly display name of platform. */ - @NonNull - Extension owner(); - - /** - * Gets the post order of this event subscription. - * - * @return the post order of this event subscription - */ - Subscribe.PostOrder order(); - - /** - * Gets if this event subscription is active. - * - * @return if this event subscription is active - */ - boolean isActive(); - - /** - * Unsubscribes from this event listener - */ - void unsubscribe(); - - /** - * Invokes the given event - * - * @param event the event - */ - void invoke(@NonNull T event) throws Throwable; + @Override + public String toString() { + return displayName; + } } diff --git a/api/base/src/main/java/org/geysermc/api/util/InputMode.java b/api/base/src/main/java/org/geysermc/api/util/InputMode.java new file mode 100644 index 000000000..70346ffa5 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/InputMode.java @@ -0,0 +1,49 @@ +/* + * 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.api.util; + +import org.checkerframework.checker.nullness.qual.NonNull; + +public enum InputMode { + UNKNOWN, + KEYBOARD_MOUSE, + TOUCH, + CONTROLLER, + VR; + + private static final InputMode[] VALUES = values(); + + /** + * Get the InputMode from the identifier. + * + * @param id the InputMode identifier + * @return The InputMode or {@link #UNKNOWN} if the mode wasn't found + */ + @NonNull + public static InputMode fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} \ No newline at end of file diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java similarity index 71% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java rename to api/base/src/main/java/org/geysermc/api/util/UiProfile.java index e7b4cbe37..cddb97260 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java +++ b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java @@ -23,19 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.standalone.command; +package org.geysermc.api.util; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.checkerframework.checker.nullness.qual.NonNull; -public class GeyserStandaloneCommandManager extends GeyserCommandManager { +public enum UiProfile { + CLASSIC, POCKET; - public GeyserStandaloneCommandManager(GeyserImpl geyser) { - super(geyser); - } + private static final UiProfile[] VALUES = values(); - @Override - public String description(String command) { - return ""; // this is not sent over the protocol, so we return none + /** + * Get the UiProfile from the identifier. + * + * @param id the UiProfile identifier + * @return The UiProfile or {@link #CLASSIC} if the profile wasn't found + */ + @NonNull + public static UiProfile fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java index 16bfe7070..f86206d36 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -29,9 +29,9 @@ 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.geyser.api.command.CommandManager; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; @@ -55,12 +55,6 @@ public interface GeyserApi extends GeyserApiBase { @Override @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); - /** - * {@inheritDoc} - */ - @Override - @Nullable GeyserConnection connectionByName(@NonNull String name); - /** * {@inheritDoc} */ @@ -72,15 +66,9 @@ public interface GeyserApi extends GeyserApiBase { * * @return the extension manager */ + @NonNull ExtensionManager extensionManager(); - /** - * Gets the {@link CommandManager}. - * - * @return the command manager - */ - CommandManager commandManager(); - /** * Provides an implementation for the specified API type. * @@ -98,7 +86,8 @@ public interface GeyserApi extends GeyserApiBase { * * @return the event bus */ - EventBus eventBus(); + @NonNull + EventBus eventBus(); /** * Gets the default {@link RemoteServer} configured @@ -106,6 +95,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the default remote server used within Geyser */ + @NonNull RemoteServer defaultRemoteServer(); /** @@ -114,6 +104,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the listener used for Bedrock client connectins */ + @NonNull BedrockListener bedrockListener(); /** @@ -121,6 +112,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the current geyser api instance */ + @NonNull static GeyserApi api() { return Geyser.api(GeyserApi.class); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java index 0ad296669..2f1f2b24d 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -27,6 +27,8 @@ 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.extension.Extension; import java.util.Collections; import java.util.List; @@ -104,19 +106,39 @@ public interface Command { return false; } - static Command.Builder builder(Class sourceType) { - return GeyserApi.api().provider(Builder.class, sourceType); + /** + * Creates a new {@link Command.Builder} used to construct commands. + * + * @param extension the extension + * @param the source type + * @return a new command builder used to construct commands + */ + static Command.Builder builder(@NonNull Extension extension) { + return GeyserApi.api().provider(Builder.class, extension); } interface Builder { + /** + * Defines the source type to use for this command. + *

+ * Command source types can be anything that extend + * {@link CommandSource}, such as {@link GeyserConnection}. + * This will guarantee that the source used in the executor + * is an instance of this source. + * + * @param sourceType the source type + * @return the builder + */ + Builder source(@NonNull Class sourceType); + /** * Sets the command name. * * @param name the command name * @return the builder */ - Builder name(String name); + Builder name(@NonNull String name); /** * Sets the command description. @@ -124,7 +146,7 @@ public interface Command { * @param description the command description * @return the builder */ - Builder description(String description); + Builder description(@NonNull String description); /** * Sets the permission node. @@ -132,7 +154,7 @@ public interface Command { * @param permission the permission node * @return the builder */ - Builder permission(String permission); + Builder permission(@NonNull String permission); /** * Sets the aliases. @@ -140,7 +162,7 @@ public interface Command { * @param aliases the aliases * @return the builder */ - Builder aliases(List aliases); + Builder aliases(@NonNull List aliases); /** * Sets if this command is designed to be used only by server operators. @@ -164,7 +186,7 @@ public interface Command { * @param subCommands the subcommands * @return the builder */ - Builder subCommands(List subCommands); + Builder subCommands(@NonNull List subCommands); /** * Sets if this command is bedrock only. @@ -180,13 +202,14 @@ public interface Command { * @param executor the command executor * @return the builder */ - Builder executor(CommandExecutor executor); + Builder executor(@NonNull CommandExecutor executor); /** * Builds the command. * * @return the command */ + @NonNull Command build(); } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java index d384d097c..12a54ee90 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java @@ -25,13 +25,14 @@ package org.geysermc.geyser.api.command; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * Handles executing a command. * * @param the command source */ public interface CommandExecutor { - /** * Executes the given {@link Command} with the given * {@link CommandSource}. @@ -40,5 +41,5 @@ public interface CommandExecutor { * @param command the command * @param args the arguments */ - void execute(T source, Command command, String[] args); + void execute(@NonNull T source, @NonNull Command command, @NonNull String[] args); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index aabf0c4e8..45276e2c4 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.command; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * Represents an instance capable of sending commands. */ @@ -42,7 +44,7 @@ public interface CommandSource { * * @param message the message to send */ - void sendMessage(String message); + void sendMessage(@NonNull String message); /** * Sends the given messages to the command source @@ -58,7 +60,7 @@ public interface CommandSource { /** * If this source is the console. * - * @return true if this source is the console + * @return true if this source is the console */ boolean isConsole(); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java index b13f12300..801bfa45f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java @@ -26,71 +26,18 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.event.bus.OwnedEventBus; import org.geysermc.geyser.api.extension.Extension; import java.util.Set; -import java.util.function.Consumer; /** * Represents a bus capable of subscribing * or "listening" to events and firing them. */ -public interface EventBus { - - /** - * Subscribes to the given event see {@link EventSubscription}. - * - * The difference between this method and {@link ExtensionEventBus#subscribe(Class, Consumer)} - * is that this method takes in an extension parameter which allows for - * the event to be unsubscribed upon extension disable and reloads. - * - * @param extension the extension to subscribe the event to - * @param eventClass the class of the event - * @param consumer the consumer for handling the event - * @param the event class - * @return the event subscription - */ +public interface EventBus extends OwnedEventBus> { + @Override @NonNull - EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer); - - /** - * Unsubscribes the given {@link EventSubscription}. - * - * @param subscription the event subscription - */ - void unsubscribe(@NonNull EventSubscription subscription); - - /** - * Registers events for the given listener. - * - * @param extension the extension registering the event - * @param eventHolder the listener - */ - void register(@NonNull Extension extension, @NonNull Object eventHolder); - - /** - * Unregisters all events from a given {@link Extension}. - * - * @param extension the extension - */ - void unregisterAll(@NonNull Extension extension); - - /** - * Fires the given {@link Event} and returns the result. - * - * @param event the event to fire - * - * @return true if the event successfully fired - */ - boolean fire(@NonNull Event event); - - /** - * Gets the subscriptions for the given event class. - * - * @param eventClass the event class - * @param the value - * @return the subscriptions for the event class - */ - @NonNull - Set> subscriptions(@NonNull Class eventClass); + Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java new file mode 100644 index 000000000..064dd55f6 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java @@ -0,0 +1,47 @@ +/* + * 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.api.event; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; + +/** + * Represents an owner for an event that allows it + * to be registered through an {@link EventBus}. + */ +public interface EventRegistrar { + + /** + * Creates an {@link EventRegistrar} instance. + * + * @param object the object to wrap around + * @return an event registrar instance + */ + @NonNull + static EventRegistrar of(@NonNull Object object) { + return GeyserApi.api().provider(EventRegistrar.class, object); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java new file mode 100644 index 000000000..7f91d09a3 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java @@ -0,0 +1,40 @@ +/* + * 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.api.event; + +import org.geysermc.event.Event; +import org.geysermc.event.subscribe.OwnedSubscriber; +import org.geysermc.geyser.api.extension.Extension; + +/** + * Represents a subscribed listener to a {@link Event}. Wraps around + * the event and is capable of unsubscribing from the event or give + * information about it. + * + * @param the class of the event + */ +public interface EventSubscriber extends OwnedSubscriber { +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java index db0209e5f..a58d35891 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java @@ -26,36 +26,16 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.extension.Extension; -import java.util.function.Consumer; +import java.util.Set; /** * An {@link EventBus} with additional methods that implicitly * set the extension instance. - * */ -public interface ExtensionEventBus extends EventBus { - - /** - * Subscribes to the given event see {@link EventSubscription}. - * - * @param eventClass the class of the event - * @param consumer the consumer for handling the event - * @param the event class - * @return the event subscription - */ - @NonNull - EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer); - - /** - * Registers events for the given listener. - * - * @param eventHolder the listener - */ - void register(@NonNull Object eventHolder); - - /** - * Unregisters all events for this extension. - */ - void unregisterAll(); +public interface ExtensionEventBus extends org.geysermc.event.bus.EventBus> { + @Override + @NonNull Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java similarity index 85% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java index c32e1701e..9c5fffa2f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java @@ -25,17 +25,8 @@ package org.geysermc.geyser.api.event; -/** - * Represents an event. - */ -public interface Event { +import org.geysermc.event.Event; +import org.geysermc.event.subscribe.Subscriber; - /** - * Gets if the event is async. - * - * @return if the event is async - */ - default boolean isAsync() { - return false; - } +public interface ExtensionEventSubscriber extends Subscriber { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java deleted file mode 100644 index 488fa0ea3..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java +++ /dev/null @@ -1,103 +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.api.event; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An annotation used to signify the given method is - * an {@link Event}. Only should be applied to methods - * where the class containing them is designated for - * events specifically. - * - * When using {@link EventBus#subscribe}, this annotation should - * not be applied whatsoever as it will have no use and potentially - * throw errors due to it being used wrongly. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface Subscribe { - - /** - * The {@link PostOrder} of the event - * - * @return the post order of the event - */ - Subscribe.PostOrder postOrder() default PostOrder.NORMAL; - - /** - * Represents the post order of an event. - */ - enum PostOrder { - - /** - * The lowest priority. Called first to - * allow for other events to customize - * the outcome - */ - FIRST(net.kyori.event.PostOrders.FIRST), - - /** - * The second lowest priority. - */ - EARLY(net.kyori.event.PostOrders.EARLY), - - /** - * Normal priority. Event is neither - * important nor unimportant - */ - NORMAL(net.kyori.event.PostOrders.NORMAL), - - /** - * The second highest priority - */ - LATE(net.kyori.event.PostOrders.LATE), - - /** - * The highest of importance! Event is called - * last and has the final say in the outcome - */ - LAST(net.kyori.event.PostOrders.LAST); - - private final int postOrder; - - PostOrder(int postOrder) { - this.postOrder = postOrder; - } - - /** - * The numerical post order value. - * - * @return numerical post order value - */ - public int postOrder() { - return this.postOrder; - } - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java index 48f3acdb7..158f14d53 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.api.event.connection; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.Event; /** * An event that contains a {@link GeyserConnection}. diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java index 2ab1b9611..e46492b36 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.api.event.downstream; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Cancellable; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.Cancellable; import org.geysermc.geyser.api.event.connection.ConnectionEvent; import java.util.Set; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java similarity index 68% rename from api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index 9f29651ba..77d5efa65 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -23,36 +23,35 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.command; +package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.command.Command; import java.util.Map; /** - * Manages Bedrock commands within Geyser. + * Called when commands are defined within Geyser. + * + * This event allows you to register new commands using the {@link #register(Command)} + * method and retrieve the default commands defined. */ -public abstract class CommandManager { +public interface GeyserDefineCommandsEvent extends Event { /** - * Registers the given {@link Command}. + * Registers the given {@link Command} into the Geyser + * command manager. * * @param command the command to register */ - public abstract void register(@NonNull Command command); + void register(@NonNull Command command); /** - * Unregisters the given {@link Command}. + * Gets all the registered built-in {@link Command}s. * - * @param command the command to unregister - */ - public abstract void unregister(@NonNull Command command); - - /** - * Gets all the registered {@link Command}s. - * - * @return all the registered commands + * @return all the registered built-in commands */ @NonNull - public abstract Map commands(); + Map commands(); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index 308b39d22..0957b8551 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -25,45 +25,36 @@ package org.geysermc.geyser.api.event.lifecycle; -import com.google.common.collect.Multimap; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * Called on Geyser's startup when looking for custom items. Custom items must be registered through this event. * * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. */ -public abstract class GeyserDefineCustomItemsEvent implements Event { - private final Multimap customItems; - private final List nonVanillaCustomItems; - - public GeyserDefineCustomItemsEvent(Multimap customItems, List nonVanillaCustomItems) { - this.customItems = customItems; - this.nonVanillaCustomItems = nonVanillaCustomItems; - } - +public interface GeyserDefineCustomItemsEvent extends Event { /** * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. * * @return a multimap of all the already registered custom items */ - public Map> getExistingCustomItems() { - return Collections.unmodifiableMap(this.customItems.asMap()); - } + @NonNull + Map> getExistingCustomItems(); /** * Gets the list of the already registered non-vanilla custom items. * * @return the list of the already registered non-vanilla custom items */ - public List getExistingNonVanillaCustomItems() { - return Collections.unmodifiableList(this.nonVanillaCustomItems); - } + @NonNull + List getExistingNonVanillaCustomItems(); /** * Registers a custom item with a base Java item. This is used to register items with custom textures and properties @@ -73,7 +64,7 @@ public abstract class GeyserDefineCustomItemsEvent implements Event { * @param customItemData the custom item data to register * @return if the item was registered */ - public abstract boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); + boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); /** * Registers a custom item with no base item. This is used for mods. @@ -81,5 +72,5 @@ public abstract class GeyserDefineCustomItemsEvent implements Event { * @param customItemData the custom item data to register * @return if the item was registered */ - public abstract boolean register(@NonNull NonVanillaCustomItemData customItemData); + boolean register(@NonNull NonVanillaCustomItemData customItemData); } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java index 0f181aedf..e9b283ecb 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import java.nio.file.Path; import java.util.List; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java index 94e42e075..8d145f615 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java @@ -26,8 +26,9 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.ExtensionManager; /** @@ -36,5 +37,5 @@ import org.geysermc.geyser.api.extension.ExtensionManager; * @param extensionManager the extension manager * @param eventBus the event bus */ -public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java index fa130c883..8be89dafd 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java @@ -26,8 +26,9 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.ExtensionManager; /** @@ -36,5 +37,5 @@ import org.geysermc.geyser.api.extension.ExtensionManager; * @param extensionManager the extension manager * @param eventBus the event bus */ -public record GeyserPreInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +public record GeyserPreInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java index a0fc2294b..7793ef997 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -26,13 +26,13 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.command.CommandManager; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.ExtensionManager; /** * Called when Geyser is shutting down. */ -public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull CommandManager commandManager, @NonNull EventBus eventBus) implements Event { +public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 2982a76fb..33fc159de 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -25,16 +25,19 @@ package org.geysermc.geyser.api.extension; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.GeyserApiBase; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.ExtensionEventBus; import java.nio.file.Path; +import java.util.Objects; /** * Represents an extension within Geyser. */ -public interface Extension { +public interface Extension extends EventRegistrar { /** * Gets if the extension is enabled @@ -59,6 +62,7 @@ public interface Extension { * * @return the extension's data folder */ + @NonNull default Path dataFolder() { return this.extensionLoader().dataFolder(this); } @@ -68,6 +72,7 @@ public interface Extension { * * @return the extension event bus */ + @NonNull default ExtensionEventBus eventBus() { return this.extensionLoader().eventBus(this); } @@ -77,6 +82,7 @@ public interface Extension { * * @return the extension manager */ + @NonNull default ExtensionManager extensionManager() { return this.geyserApi().extensionManager(); } @@ -86,6 +92,7 @@ public interface Extension { * * @return the extension's name */ + @NonNull default String name() { return this.description().name(); } @@ -95,6 +102,7 @@ public interface Extension { * * @return the extension's description */ + @NonNull default ExtensionDescription description() { return this.extensionLoader().description(this); } @@ -104,6 +112,7 @@ public interface Extension { * * @return the extension's logger */ + @NonNull default ExtensionLogger logger() { return this.extensionLoader().logger(this); } @@ -113,8 +122,9 @@ public interface Extension { * * @return the extension loader */ + @NonNull default ExtensionLoader extensionLoader() { - return this.extensionManager().extensionLoader(this); + return Objects.requireNonNull(this.extensionManager().extensionLoader()); } /** @@ -122,6 +132,7 @@ public interface Extension { * * @return the geyser api instance */ + @NonNull default GeyserApi geyserApi() { return GeyserApi.api(); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index e77411144..2df3ee815 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -30,12 +30,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; /** - * This is the Geyser extension description + * Represents the description of an {@link Extension}. */ public interface ExtensionDescription { /** - * Gets the extension's name + * Gets the extension's id. + * + * @return the extension's id + */ + @NonNull + String id(); + + /** + * Gets the extension's name. * * @return the extension's name */ @@ -43,7 +51,7 @@ public interface ExtensionDescription { String name(); /** - * Gets the extension's main class + * Gets the extension's main class. * * @return the extension's main class */ @@ -51,15 +59,37 @@ public interface ExtensionDescription { String main(); /** - * Gets the extension's api version + * Gets the extension's major api version + * + * @return the extension's major api version + */ + int majorApiVersion(); + + /** + * Gets the extension's minor api version + * + * @return the extension's minor api version + */ + int minorApiVersion(); + + /** + * Gets the extension's patch api version + * + * @return the extension's patch api version + */ + int patchApiVersion(); + + /** + * Gets the extension's api version. * * @return the extension's api version */ - @NonNull - String apiVersion(); + default String apiVersion() { + return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); + } /** - * Gets the extension's description + * Gets the extension's description. * * @return the extension's description */ @@ -67,7 +97,7 @@ public interface ExtensionDescription { String version(); /** - * Gets the extension's authors + * Gets the extension's authors. * * @return the extension's authors */ diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java index c84c37919..30414d500 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -34,7 +34,6 @@ import java.nio.file.Path; * The extension loader is responsible for loading, unloading, enabling and disabling extensions */ public abstract class ExtensionLoader { - /** * Gets if the given {@link Extension} is enabled. * @@ -101,6 +100,6 @@ public abstract class ExtensionLoader { * @param extensionManager the extension manager */ protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { - extensionManager.register(extension, this); + extensionManager.register(extension); } } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java index 65d6c66da..a9d0d7376 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -29,7 +29,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; -import java.util.Map; /** * Manages Geyser {@link Extension}s @@ -59,15 +58,6 @@ public abstract class ExtensionManager { */ public abstract void disable(@NonNull Extension extension); - /** - * Gets the {@link ExtensionLoader} responsible for loading - * the given {@link Extension}. - * - * @return the extension loader for loading the given extension - */ - @Nullable - public abstract ExtensionLoader extensionLoader(@NonNull Extension extension); - /** * Gets all the {@link Extension}s currently loaded. * @@ -77,37 +67,19 @@ public abstract class ExtensionManager { public abstract Collection extensions(); /** - * Gets the {@link ExtensionLoader} with the given identifier. + * Gets the {@link ExtensionLoader}. * - * @param identifier the identifier - * @return the extension loader at the given identifier + * @return the extension loader */ @Nullable - public abstract ExtensionLoader extensionLoader(@NonNull String identifier); - - /** - * Registers an {@link ExtensionLoader} with the given identifier. - * - * @param identifier the identifier - * @param extensionLoader the extension loader - */ - public abstract void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader); - - /** - * Gets all the currently registered {@link ExtensionLoader}s. - * - * @return all the currently registered extension loaders - */ - @NonNull - public abstract Map extensionLoaders(); + public abstract ExtensionLoader extensionLoader(); /** * Registers an {@link Extension} with the given {@link ExtensionLoader}. * * @param extension the extension - * @param loader the loader */ - public abstract void register(@NonNull Extension extension, @NonNull ExtensionLoader loader); + public abstract void register(@NonNull Extension extension); /** * Loads all extensions from the given {@link ExtensionLoader}. diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java index 58a597eb6..61fe286aa 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.network; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * The listener that handles connections from Minecraft: * Bedrock Edition. @@ -37,6 +39,7 @@ public interface BedrockListener { * * @return the listening address */ + @NonNull String address(); /** diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java index b13ae5930..8ac5d8a03 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.network; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * Represents the Java server that Geyser is connecting to. */ @@ -63,5 +65,6 @@ public interface RemoteServer { * * @return the auth type required by the remote server */ + @NonNull AuthType authType(); } diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 873df692a..3e0e9c147 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -1,7 +1,7 @@ -val bungeeVersion = "a7c6ede"; - dependencies { api(projects.core) + + implementation(libs.adventure.text.serializer.bungeecord) } platformRelocate("net.md_5.bungee.jni") @@ -10,7 +10,7 @@ platformRelocate("io.netty.channel.kqueue") // This is not used because relocati platformRelocate("net.kyori") // These dependencies are already present on the platform -provided("com.github.SpigotMC.BungeeCord", "bungeecord-proxy", bungeeVersion) +provided(libs.bungeecord.proxy) application { mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain") diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java index 938e2fc3a..ba7bc464f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java @@ -29,7 +29,6 @@ import lombok.Getter; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.geysermc.geyser.dump.BootstrapDumpInfo; -import org.geysermc.geyser.text.AsteriskSerializer; import java.util.ArrayList; import java.util.Collections; @@ -52,17 +51,17 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo { this.plugins = new ArrayList<>(); for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) { - String hostname; - if (AsteriskSerializer.showSensitive || (listener.getHost().getHostString().equals("") || listener.getHost().getHostString().equals("0.0.0.0"))) { - hostname = listener.getHost().getHostString(); - } else { - hostname = "***"; - } - this.listeners.add(new ListenerInfo(hostname, listener.getHost().getPort())); + this.listeners.add(new ListenerInfo(listener.getHost().getHostString(), listener.getHost().getPort())); } for (Plugin plugin : proxy.getPluginManager().getPlugins()) { - this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Collections.singletonList(plugin.getDescription().getAuthor()))); + this.plugins.add(new PluginInfo( + true, + plugin.getDescription().getName(), + plugin.getDescription().getVersion(), + plugin.getDescription().getMain(), + Collections.singletonList(plugin.getDescription().getAuthor())) + ); } } } 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 7b937ac6b..dc7602163 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 @@ -25,12 +25,17 @@ package org.geysermc.geyser.platform.bungeecord; +import io.netty.channel.Channel; +import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; 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.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -38,22 +43,25 @@ 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.GeyserBungeeCommandManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.net.InetSocketAddress; 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.UUID; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { - private GeyserBungeeCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeInjector geyserInjector; private GeyserBungeeLogger geyserLogger; @@ -62,9 +70,23 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private GeyserImpl geyser; @Override - public void onEnable() { + public void onLoad() { GeyserLocale.init(this); + // Copied from ViaVersion. + // https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43 + try { + ProtocolConstants.class.getField("MINECRAFT_1_19_3"); + } catch (NoSuchFieldException e) { + getLogger().warning(" / \\"); + getLogger().warning(" / \\"); + getLogger().warning(" / | \\"); + getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); + getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); + getLogger().warning(" / o \\"); + getLogger().warning("/_____________\\"); + } + if (!getDataFolder().exists()) getDataFolder().mkdir(); @@ -80,6 +102,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { return; } + this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); + } + + @Override + public void onEnable() { + // Remove this in like a year + if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } + if (getProxy().getConfig().getListeners().size() == 1) { ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0]; @@ -100,13 +136,22 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } - this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - - // Remove this in like a year - if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); - return; + // Force-disable query if enabled, or else Geyser won't enable + for (ListenerInfo info : getProxy().getConfig().getListeners()) { + if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) { + try { + Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled"); + queryField.setAccessible(true); + queryField.setBoolean(info, false); + geyserLogger.warning("We force-disabled query on port " + info.getQueryPort() + " in order for Geyser to boot up successfully. " + + "To remove this message, disable query in your proxy's config."); + } catch (NoSuchFieldException | IllegalAccessException e) { + geyserLogger.warning("Could not force-disable query. Geyser may not start correctly!"); + if (geyserLogger.isDebug()) { + e.printStackTrace(); + } + } + } } if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { @@ -120,22 +165,63 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { geyserConfig.loadFloodgate(this); - this.geyser = GeyserImpl.start(PlatformType.BUNGEECORD, this); + // 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 + this.awaitStartupCompletion(0); + } + + @SuppressWarnings("unchecked") + private void awaitStartupCompletion(int tries) { + // After 20 tries give up waiting. This will happen + // just after 3 minutes approximately + if (tries >= 20) { + this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " + + "If all your plugins are loaded properly, this is a bug! " + + "If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times."); + this.postStartup(); + return; + } + + try { + Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners"); + listenersField.setAccessible(true); + + Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance()); + if (listeners.isEmpty()) { + this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS); + } else { + this.awaitStartupCompletion(++tries); + } + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + private void postStartup() { + GeyserImpl.start(); this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector.initializeLocalChannel(this); - this.geyserCommandManager = new GeyserBungeeCommandManager(geyser); + this.geyserCommandManager = new GeyserCommandManager(geyser); this.geyserCommandManager.init(); + 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)); + } + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); } - - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", geyser, geyserCommandManager.getCommands())); - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyserext", geyser, geyserCommandManager.commands())); } @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 new file mode 100644 index 000000000..c68839b20 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java @@ -0,0 +1,48 @@ +/* + * 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; + +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.platform.bungeecord.command.BungeeCommandSource; +import org.geysermc.geyser.util.VersionCheckUtils; + +public final class GeyserBungeeUpdateListener implements Listener { + + @EventHandler + public void onPlayerJoin(final PostLoginEvent event) { + if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { + final ProxiedPlayer player = event.getPlayer(); + if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + 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 801fc8777..f65377643 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 @@ -25,11 +25,15 @@ 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.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; +import java.util.Locale; + public class BungeeCommandSource implements GeyserCommandSource { private final net.md_5.bungee.api.CommandSender handle; @@ -50,6 +54,18 @@ public class BungeeCommandSource implements GeyserCommandSource { handle.sendMessage(TextComponent.fromLegacyText(message)); } + private static final int PROTOCOL_HEX_COLOR = 713; // Added 20w17a + + @Override + public void sendMessage(Component message) { + if (handle instanceof ProxiedPlayer player && player.getPendingConnection().getVersion() >= PROTOCOL_HEX_COLOR) { + // Include hex colors + handle.sendMessage(BungeeComponentSerializer.get().serialize(message)); + return; + } + handle.sendMessage(BungeeComponentSerializer.legacy().serialize(message)); + } + @Override public boolean isConsole() { return !(handle instanceof ProxiedPlayer); @@ -58,8 +74,11 @@ public class BungeeCommandSource implements GeyserCommandSource { @Override public String locale() { if (handle instanceof ProxiedPlayer player) { - String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); - return GeyserLocale.formatLocale(locale); + Locale locale = player.getLocale(); + if (locale != null) { + // Locale can be null early on in the conneciton + return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); + } } return GeyserLocale.getDefaultLocale(); } 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 index 6575f047c..2d02c9950 100644 --- 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 @@ -69,6 +69,9 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor 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]); diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts new file mode 100644 index 000000000..743b75a26 --- /dev/null +++ b/bootstrap/fabric/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + id("fabric-loom") version "1.0-SNAPSHOT" +} + +java { + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + //to change the versions see the gradle.properties file + minecraft(libs.fabric.minecraft) + mappings(loom.officialMojangMappings()) + modImplementation(libs.fabric.loader) + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation(libs.fabric.api) + + // This should be in the libs TOML, but something about modImplementation AND include just doesn't work + include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")) + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. + + api(projects.core) + shadow(projects.core) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "com.google.code.gson", module = "gson") + exclude(group = "org.slf4j") + exclude(group = "com.nukkitx.fastutil") + exclude(group = "io.netty.incubator") + } +} + +repositories { + mavenLocal() + maven("https://repo.opencollab.dev/maven-releases/") + maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://jitpack.io") + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +application { + mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") +} + +tasks { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this task, sources will not be generated. + sourcesJar { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + shadowJar { + // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be + configurations = listOf(project.configurations.shadow.get()) + // The remapped shadowJar is the final desired Geyser-Fabric.jar + archiveVersion.set(project.version.toString()) + archiveClassifier.set("shaded") + + relocate("org.objectweb.asm", "org.geysermc.relocate.asm") + relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139 + relocate("com.fasterxml.jackson", "org.geysermc.relocate.jackson") + relocate("net.kyori", "org.geysermc.relocate.kyori") + } + + remapJar { + dependsOn(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + archiveBaseName.set("Geyser-Fabric") + archiveClassifier.set("") + archiveVersion.set("") + } +} \ No newline at end of file diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java similarity index 57% rename from bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java index fa78a671c..f557d16c0 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java @@ -23,34 +23,29 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.spigot.world.manager; +package org.geysermc.geyser.platform.fabric; -import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; +import com.fasterxml.jackson.annotation.JsonIgnore; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.geysermc.geyser.FloodgateKeyLoader; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; -/** - * Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)} - * cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect. - * If this occurs to you somehow, please let us know!! - */ -public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { - public GeyserSpigotFallbackWorldManager(Plugin plugin) { - super(plugin); +import java.nio.file.Path; + +public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { + @JsonIgnore + private Path floodgateKeyPath; + + public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) { + Path geyserDataFolder = geyser.getConfigFolder(); + Path floodgateDataFolder = floodgate != null ? FabricLoader.getInstance().getConfigDir().resolve("floodgate") : null; + + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); } @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - return BlockStateValues.JAVA_AIR_ID; - } - - @Override - public boolean hasOwnChunkCache() { - return false; - } - - @Override - public boolean isLegacy() { - return true; + public Path getFloodgateKeyPath() { + return floodgateKeyPath; } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java new file mode 100644 index 000000000..ee986ee62 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java @@ -0,0 +1,82 @@ +/* + * 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.fabric; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.fabricmc.loader.api.metadata.Person; +import net.minecraft.server.MinecraftServer; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +public class GeyserFabricDumpInfo extends BootstrapDumpInfo { + + private String platformVersion = null; + private final EnvType environmentType; + + @AsteriskSerializer.Asterisk(isIp = true) + private final String serverIP; + private final int serverPort; + private final List mods; + + public GeyserFabricDumpInfo(MinecraftServer server) { + FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod -> + this.platformVersion = mod.getMetadata().getVersion().getFriendlyString()); + + this.environmentType = FabricLoader.getInstance().getEnvironmentType(); + this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp(); + this.serverPort = server.getPort(); + this.mods = new ArrayList<>(); + + for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { + ModMetadata meta = mod.getMetadata(); + this.mods.add(new ModInfo( + FabricLoader.getInstance().isModLoaded(meta.getId()), + meta.getId(), + meta.getVersion().getFriendlyString(), + meta.getAuthors().stream().map(Person::getName).collect(Collectors.toList())) + ); + } + } + + @Getter + @AllArgsConstructor + public static class ModInfo { + public boolean enabled; + public String name; + public String version; + public List authors; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java new file mode 100644 index 000000000..180197f2d --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java @@ -0,0 +1,100 @@ +/* + * 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.fabric; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.text.ChatColor; + +public class GeyserFabricLogger implements GeyserLogger { + private final Logger logger = LogManager.getLogger("geyser-fabric"); + + private boolean debug; + + public GeyserFabricLogger(boolean isDebug) { + debug = isDebug; + } + + @Override + public void severe(String message) { + logger.fatal(message); + } + + @Override + public void severe(String message, Throwable error) { + logger.fatal(message, error); + } + + @Override + public void error(String message) { + logger.error(message); + } + + @Override + public void error(String message, Throwable error) { + logger.error(message, error); + } + + @Override + public void warning(String message) { + logger.warn(message); + } + + @Override + public void info(String message) { + logger.info(message); + } + + @Override + public void sendMessage(Component message) { + // As of Java Edition 1.19.2, Fabric's console doesn't natively support legacy format + String flattened = LegacyComponentSerializer.legacySection().serialize(message); + // Add the reset at the end, or else format will persist... forever. + // https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png + String text = ChatColor.toANSI(flattened) + ChatColor.ANSI_RESET; + info(text); + } + + @Override + public void debug(String message) { + if (debug) { + logger.info(message); + } + } + + @Override + public void setDebug(boolean debug) { + this.debug = debug; + } + + @Override + public boolean isDebug() { + return debug; + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java similarity index 74% rename from bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java index e0fd7a4ac..f3f63324a 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java @@ -23,19 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.bungeecord.command; +package org.geysermc.geyser.platform.fabric; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.GeyserMain; -public class GeyserBungeeCommandManager extends GeyserCommandManager { +public class GeyserFabricMain extends GeyserMain { - public GeyserBungeeCommandManager(GeyserImpl geyser) { - super(geyser); + public static void main(String[] args) { + new GeyserFabricMain().displayMessage(); } @Override - public String description(String command) { - return ""; // no support for command descriptions in bungee + public String getPluginType() { + return "Fabric"; + } + + @Override + public String getPluginFolder() { + return "mods"; } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java new file mode 100644 index 000000000..e5ff4b577 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -0,0 +1,270 @@ +/* + * 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.fabric; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.geysermc.common.PlatformType; +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.network.AuthType; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandManager; +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.fabric.command.GeyserFabricCommandExecutor; +import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.*; + +public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { + private static GeyserFabricMod instance; + + private boolean reloading; + + private GeyserImpl geyser; + private ModContainer mod; + private Path dataFolder; + private MinecraftServer server; + + private GeyserCommandManager geyserCommandManager; + private GeyserFabricConfiguration geyserConfig; + private GeyserFabricLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; + private WorldManager geyserWorldManager; + + @Override + public void onInitialize() { + instance = this; + mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); + + this.onEnable(); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { + // Set as an event so we can get the proper IP and port if needed + ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser); + } + } + + @Override + public void onEnable() { + dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); + if (!dataFolder.toFile().exists()) { + //noinspection ResultOfMethodCallIgnored + dataFolder.toFile().mkdir(); + } + + // Init dataFolder first as local language overrides call getConfigFolder() + GeyserLocale.init(this); + + try { + File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); + } catch (IOException ex) { + LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + return; + } + + this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); + + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + this.geyser = GeyserImpl.load(PlatformType.FABRIC, this); + + if (server == null) { + // Server has yet to start + // Register onDisable so players are properly kicked + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + + ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); + } else { + // Server has started and this is a reload + startGeyser(this.server); + reloading = false; + } + } + + /** + * Initialize core Geyser. + * A function, as it needs to be called in different places depending on if Geyser is being reloaded or not. + * + * @param server The minecraft server. + */ + public void startGeyser(MinecraftServer server) { + this.server = server; + + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); + String ip = server.getLocalIp(); + int port = ((GeyserServerPortGetter) server).geyser$getServerPort(); + if (ip != null && !ip.isEmpty() && !ip.equals("0.0.0.0")) { + this.geyserConfig.getRemote().setAddress(ip); + } + this.geyserConfig.getRemote().setPort(port); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().port()); + } + + Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); + boolean floodgatePresent = floodgate.isPresent(); + if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + return; + } else if (geyserConfig.isAutoconfiguredRemote() && floodgatePresent) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + } + + geyserConfig.loadFloodgate(this, floodgate.orElse(null)); + + GeyserImpl.start(); + + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); + + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); + + this.geyserWorldManager = new GeyserFabricWorldManager(server); + + // Start command building + // Set just "geyser" as the help command + GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(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()) { + GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(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)); + } + server.getCommands().getDispatcher().register(builder); + } + + @Override + public void onDisable() { + if (geyser != null) { + geyser.shutdown(); + geyser = null; + } + if (!reloading) { + this.server = null; + } + } + + @Override + public GeyserConfiguration getGeyserConfig() { + return geyserConfig; + } + + @Override + public GeyserLogger getGeyserLogger() { + return geyserLogger; + } + + @Override + public GeyserCommandManager getGeyserCommandManager() { + return geyserCommandManager; + } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserPingPassthrough; + } + + @Override + public WorldManager getWorldManager() { + return geyserWorldManager; + } + + @Override + public Path getConfigFolder() { + return dataFolder; + } + + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserFabricDumpInfo(server); + } + + @Override + public String getMinecraftServerVersion() { + return this.server.getServerVersion(); + } + + @Nullable + @Override + public InputStream getResourceOrNull(String resource) { + // We need to handle this differently, because Fabric shares the classloader across multiple mods + Path path = this.mod.findPath(resource).orElse(null); + if (path == null) { + return null; + } + + try { + return path.getFileSystem() + .provider() + .newInputStream(path); + } catch (IOException e) { + return null; + } + } + + public void setReloading(boolean reloading) { + this.reloading = reloading; + } + + public static GeyserFabricMod getInstance() { + return instance; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java new file mode 100644 index 000000000..1ea69cbe2 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java @@ -0,0 +1,43 @@ +/* + * 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.fabric; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.platform.fabric.command.FabricCommandSender; +import org.geysermc.geyser.util.VersionCheckUtils; + +public final class GeyserFabricUpdateListener { + public static void onPlayReady(ServerGamePacketListenerImpl handler) { + if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) { + VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack())); + } + } + + private GeyserFabricUpdateListener() { + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java new file mode 100644 index 000000000..4f1c8b638 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java @@ -0,0 +1,48 @@ +/* + * 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.fabric; + +import net.minecraft.server.MinecraftServer; + +/** + * Represents a getter to the server port in the dedicated server and in the integrated server. + */ +public interface GeyserServerPortGetter { + /** + * Returns the server port. + * + *

    + *
  • If it's a dedicated server, it will return the server port specified in the {@code server.properties} file.
  • + *
  • If it's an integrated server, it will return the LAN port if opened, else -1.
  • + *
+ * + * The reason is that {@link MinecraftServer#getPort()} doesn't return the LAN port if it's the integrated server, + * and changing the behavior of this method via a mixin should be avoided as it could have unexpected consequences. + * + * @return The server port. + */ + int geyser$getServerPort(); +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java new file mode 100644 index 000000000..5973e04f1 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java @@ -0,0 +1,80 @@ +/* + * 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.fabric.command; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.text.ChatColor; + +import javax.annotation.Nonnull; + +public class FabricCommandSender implements GeyserCommandSource { + + private final CommandSourceStack source; + + public FabricCommandSender(CommandSourceStack source) { + this.source = source; + } + + @Override + public String name() { + return source.getTextName(); + } + + @Override + public void sendMessage(@Nonnull String message) { + if (source.getEntity() instanceof ServerPlayer) { + ((ServerPlayer) source.getEntity()).displayClientMessage(Component.literal(message), false); + } else { + GeyserImpl.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); + } + } + + @Override + public void sendMessage(net.kyori.adventure.text.Component message) { + if (source.getEntity() instanceof ServerPlayer player) { + String decoded = GsonComponentSerializer.gson().serialize(message); + player.displayClientMessage(Component.Serializer.fromJson(decoded), false); + return; + } + GeyserCommandSource.super.sendMessage(message); + } + + @Override + public boolean isConsole() { + return !(source.getEntity() instanceof ServerPlayer); + } + + @Override + public boolean hasPermission(String permission) { + return Permissions.check(source, permission); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java new file mode 100644 index 000000000..7600e4136 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -0,0 +1,74 @@ +/* + * 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.fabric.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import me.lucko.fabric.api.permissions.v0.Permissions; +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.fabric.GeyserFabricMod; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collections; + +public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { + private final GeyserCommand command; + + public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) { + super(connector, Collections.singletonMap(command.name(), command)); + this.command = command; + } + + public boolean testPermission(CommandSourceStack source) { + return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); + } + + @Override + public int run(CommandContext context) { + CommandSourceStack source = (CommandSourceStack) context.getSource(); + FabricCommandSender sender = new FabricCommandSender(source); + GeyserSession session = getGeyserSession(sender); + if (!testPermission(source)) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); + return 0; + } + if (this.command.name().equals("reload")) { + GeyserFabricMod.getInstance().setReloading(true); + } + + if (command.isBedrockOnly() && session == null) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); + return 0; + } + command.execute(session, sender, new String[0]); + return 0; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java new file mode 100644 index 000000000..942909068 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -0,0 +1,70 @@ +/* + * 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.fabric.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.GameType; +import org.geysermc.geyser.platform.fabric.GeyserFabricMod; +import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; +import org.geysermc.geyser.text.GeyserLocale; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Environment(EnvType.CLIENT) +@Mixin(IntegratedServer.class) +public class IntegratedServerMixin implements GeyserServerPortGetter { + @Shadow + private int publishedPort; + + @Shadow @Final private Minecraft minecraft; + + @Inject(method = "publishServer", at = @At("RETURN")) + private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) { + if (cir.getReturnValueZ()) { + // If the LAN is opened, starts Geyser. + GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this); + // Ensure player locale has been loaded, in case it's different from Java system language + GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); + // Give indication that Geyser is loaded + this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start", + this.minecraft.options.languageCode, "localhost", String.valueOf(this.publishedPort))), false); + } + } + + @Override + public int geyser$getServerPort() { + return this.publishedPort; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java new file mode 100644 index 000000000..23e148775 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -0,0 +1,51 @@ +/* + * 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.fabric.mixin.server; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.Services; +import net.minecraft.server.WorldStem; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; +import org.spongepowered.asm.mixin.Mixin; + +import java.net.Proxy; + +@Mixin(DedicatedServer.class) +public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { + public MinecraftDedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) { + super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory); + } + + @Override + public int geyser$getServerPort() { + return this.getPort(); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java new file mode 100644 index 000000000..eb4f61c67 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -0,0 +1,133 @@ +/* + * 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.fabric.world; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.WritableBookItem; +import net.minecraft.world.item.WrittenBookItem; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.LecternBlockEntity; +import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; +import org.geysermc.geyser.util.BlockEntityUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class GeyserFabricWorldManager extends GeyserWorldManager { + private final MinecraftServer server; + + public GeyserFabricWorldManager(MinecraftServer server) { + this.server = server; + } + + @Override + public boolean shouldExpectLecternHandled() { + return true; + } + + @Override + public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { + Runnable lecternGet = () -> { + // Mostly a reimplementation of Spigot lectern support + ServerPlayer player = getPlayer(session); + if (player != null) { + BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z)); + if (!(blockEntity instanceof LecternBlockEntity lectern)) { + return; + } + + if (!lectern.hasBook()) { + if (!isChunkLoad) { + BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z)); + } + return; + } + + ItemStack book = lectern.getBook(); + int pageCount = WrittenBookItem.getPageCount(book); + boolean hasBookPages = pageCount > 0; + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1); + lecternTag.putInt("page", lectern.getPage() / 2); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) book.getCount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book"); + List pages = new ArrayList<>(hasBookPages ? pageCount : 1); + if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) { + ListTag listTag = book.getTag().getList("pages", 8); + + for (int i = 0; i < listTag.size(); i++) { + String page = listTag.getString(i); + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", page); + pages.add(pageBuilder.build()); + } + } else { + // Empty page + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", ""); + pages.add(pageBuilder.build()); + } + + bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); + lecternTag.putCompound("book", bookTag.build()); + NbtMap blockEntityTag = lecternTag.build(); + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); + } + }; + if (isChunkLoad) { + // Hacky hacks to allow lectern loading to be delayed + session.scheduleInEventLoop(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS); + } else { + server.execute(lecternGet); + } + return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); + } + + @Override + public boolean hasPermission(GeyserSession session, String permission) { + ServerPlayer player = getPlayer(session); + return Permissions.check(player, permission); + } + + private ServerPlayer getPlayer(GeyserSession session) { + return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid()); + } +} diff --git a/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png b/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png new file mode 100644 index 000000000..4e6a38a78 Binary files /dev/null and b/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png differ diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..98a410950 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "${id}-fabric", + "version": "${version}", + "name": "${name}-Fabric", + "description": "A bridge/proxy allowing you to connect to Minecraft: Java Edition servers with Minecraft: Bedrock Edition. ", + "authors": [ + "${author}" + ], + "contact": { + "website": "${url}", + "repo": "https://github.com/GeyserMC/Geyser-Fabric" + }, + "license": "MIT", + "icon": "assets/geyser-fabric/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "org.geysermc.geyser.platform.fabric.GeyserFabricMod" + ] + }, + "mixins": [ + "geyser-fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14.8", + "fabric": "*", + "minecraft": ">=1.19", + "fabric-permissions-api-v0": "*" + } +} diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json new file mode 100644 index 000000000..c688ace36 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "org.geysermc.geyser.platform.fabric.mixin", + "compatibilityLevel": "JAVA_16", + "client": [ + "client.IntegratedServerMixin" + ], + "server": [ + "server.MinecraftDedicatedServerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 02883999d..b5ef4e69e 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,22 +1,19 @@ -val paperVersion = "1.19-R0.1-SNAPSHOT" -val viaVersion = "4.0.0" -val adaptersVersion = "1.5-SNAPSHOT" -val commodoreVersion = "1.13" - dependencies { api(projects.core) - implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion) + implementation(libs.adapters.spigot) - implementation("me.lucko", "commodore", commodoreVersion) + implementation(libs.commodore) + + implementation(libs.adventure.text.serializer.bungeecord) // Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 - compileOnly("io.papermc.paper", "paper-api", paperVersion) { + compileOnly(libs.paper.api) { attributes { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } } - compileOnly("io.papermc.paper", "paper-mojangapi", paperVersion) { + compileOnly(libs.paper.mojangapi) { attributes { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } @@ -25,13 +22,14 @@ dependencies { platformRelocate("it.unimi.dsi.fastutil") platformRelocate("com.fasterxml.jackson") -platformRelocate("net.kyori") +// Relocate net.kyori but exclude the component logger +platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") platformRelocate("io.netty.channel.kqueue") // These dependencies are already present on the platform -provided("com.viaversion", "viaversion", viaVersion) +provided(libs.viaversion) application { mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java new file mode 100644 index 000000000..930f84cec --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java @@ -0,0 +1,59 @@ +/* + * 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; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import org.bukkit.plugin.Plugin; + +import java.util.logging.Logger; + +public final class GeyserPaperLogger extends GeyserSpigotLogger { + private final ComponentLogger componentLogger; + + public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) { + super(logger, debug); + componentLogger = plugin.getComponentLogger(); + } + + /** + * Since 1.18.2 this is required so legacy format symbols don't show up in the console for colors + */ + @Override + public void sendMessage(Component message) { + // Done like this so the native component object field isn't relocated + componentLogger.info("{}", PaperAdventure.toNativeComponent(message)); + } + + static boolean supported() { + try { + Plugin.class.getMethod("getComponentLogger"); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java index 8055a375f..d340935b3 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java @@ -41,6 +41,8 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { private final String platformVersion; private final String platformAPIVersion; private final boolean onlineMode; + + @AsteriskSerializer.Asterisk(isIp = true) private final String serverIP; private final int serverPort; private final List plugins; @@ -51,11 +53,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { this.platformVersion = Bukkit.getVersion(); this.platformAPIVersion = Bukkit.getBukkitVersion(); this.onlineMode = Bukkit.getOnlineMode(); - if (AsteriskSerializer.showSensitive || (Bukkit.getIp().equals("") || Bukkit.getIp().equals("0.0.0.0"))) { - this.serverIP = Bukkit.getIp(); - } else { - this.serverIP = "***"; - } + this.serverIP = Bukkit.getIp(); this.serverPort = Bukkit.getPort(); this.plugins = new ArrayList<>(); 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 bf715ad57..5f0061382 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 @@ -32,9 +32,15 @@ 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.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.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -42,6 +48,7 @@ import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; 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.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -53,7 +60,6 @@ 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.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.*; @@ -62,6 +68,8 @@ import org.geysermc.geyser.util.FileUtils; 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; @@ -90,9 +98,40 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private String minecraftVersion; @Override - public void onEnable() { + public void onLoad() { GeyserLocale.init(this); + try { + // AvailableCommandsSerializer_v291 complains otherwise - affects at least 1.8 + ByteBuf.class.getMethod("writeShortLE", int.class); + // Only available in 1.13.x + Class.forName("org.bukkit.event.server.ServerLoadEvent"); + // We depend on this as a fallback in certain scenarios + BlockData.class.getMethod("getAsString"); + } catch (ClassNotFoundException | NoSuchMethodException e) { + getLogger().severe("*********************************************"); + getLogger().severe(""); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); + getLogger().severe(""); + getLogger().severe("*********************************************"); + return; + } + + try { + Class.forName("net.md_5.bungee.chat.ComponentSerializer"); + } catch (ClassNotFoundException e) { + if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat + getLogger().severe("*********************************************"); + getLogger().severe(""); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName())); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); + getLogger().severe(""); + getLogger().severe("*********************************************"); + return; + } + } + // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed try { if (!getDataFolder().exists()) { @@ -108,21 +147,29 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return; } - try { - // AvailableCommandsSerializer_v291 complains otherwise - ByteBuf.class.getMethod("writeShortLE", int.class); - } catch (NoSuchMethodException e) { - getLogger().severe("*********************************************"); - getLogger().severe(""); - getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); - getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.12.2")); - getLogger().severe(""); - getLogger().severe("*********************************************"); + this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) + : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this); + } + + @Override + public void onEnable() { + if (this.geyserConfig == null) { + // We failed to initialize correctly Bukkit.getPluginManager().disablePlugin(this); return; } + // Remove this in like a year + if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", Constants.FLOODGATE_DOWNLOAD_LOCATION)); + this.getPluginLoader().disablePlugin(this); + return; + } + // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); @@ -137,20 +184,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserConfig.getBedrock().setPort(Bukkit.getPort()); } - this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - - // Remove this in like a year - if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", Constants.FLOODGATE_DOWNLOAD_LOCATION)); - this.getPluginLoader().disablePlugin(this); - return; - } - if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); - return; } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); @@ -159,11 +195,52 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserConfig.loadFloodgate(this); + if (!INITIALIZED) { + // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes + Bukkit.getPluginManager().registerEvents(new Listener() { + + @EventHandler + public void onServerLoaded(ServerLoadEvent event) { + // Wait until all plugins have loaded so Geyser can start + postStartup(); + } + }, this); + + 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 + 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.description().name(), ex); + } + } + } + + if (INITIALIZED) { + // Reload; continue with post startup + postStartup(); + } + } + + private void postStartup() { + GeyserImpl.start(); + // Turn "(MC: 1.16.4)" into 1.16.4. this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; - this.geyser = GeyserImpl.start(PlatformType.SPIGOT, this); - if (geyserConfig.isLegacyPingPassthrough()) { this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { @@ -176,10 +253,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } } - geyserLogger.info("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); - this.geyserCommandManager.init(); + geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { @@ -194,14 +268,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } } - // Used to determine if Block.getBlockData() is present. - boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); - if (isLegacy) - geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - - boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); - // Set if we need to use a different method for getting a player's locale - SpigotCommandSource.setUseLegacyLocaleMethod(isPre1_12); // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib // To do their job injecting, then connect into *that* @@ -214,13 +280,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { String nmsVersion = name.substring(name.lastIndexOf('.') + 1); SpigotAdapters.registerWorldAdapter(nmsVersion); if (isViaVersion && isViaVersionNeeded()) { - if (isLegacy) { - // Pre-1.13 - this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this); - } else { - // Post-1.13 - this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); - } + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); } else { // No ViaVersion this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this); @@ -237,28 +297,31 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } if (this.geyserWorldManager == null) { // No NMS adapter - if (isLegacy && isViaVersion) { - // Use ViaVersion for converting pre-1.13 block states - this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this); - } else if (isLegacy) { - // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air - this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); - } else { - // Post-1.13 - this.geyserWorldManager = new GeyserSpigotWorldManager(this); - } - geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + this.geyserWorldManager = new GeyserSpigotWorldManager(this); + geyserLogger.debug("Using default world manager."); } PluginCommand geyserCommand = this.getCommand("geyser"); geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); - PluginCommand geyserExtCommand = this.getCommand("geyserext"); - geyserExtCommand.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)); + } if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions throws an error - for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { + for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { Command command = entry.getValue(); if (command.aliases().contains(entry.getKey())) { // Don't register aliases @@ -270,11 +333,35 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { 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; + } + + 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)); + // Events cannot be unregistered - re-registering results in duplicate firings GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); } boolean brigadierSupported = CommodoreProvider.isSupported(); 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 new file mode 100644 index 000000000..5e3c4def8 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java @@ -0,0 +1,48 @@ +/* + * 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; + +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.platform.spigot.command.SpigotCommandSource; +import org.geysermc.geyser.util.VersionCheckUtils; + +public final class GeyserSpigotUpdateListener implements Listener { + + @EventHandler + public void onPlayerJoin(final PlayerJoinEvent event) { + if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { + final Player player = event.getPlayer(); + if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); + } + } + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java new file mode 100644 index 000000000..5dd16da33 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java @@ -0,0 +1,154 @@ +/* + * 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; + +import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.geysermc.geyser.GeyserImpl; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Utility class for converting our shaded Adventure into the Adventure bundled in Paper. + * + * Code mostly taken from https://github.com/KyoriPowered/adventure-platform/blob/94d5821f2e755170f42bd8a5fe1d5bf6f66d04ad/platform-bukkit/src/main/java/net/kyori/adventure/platform/bukkit/PaperFacet.java#L46 + * and the MinecraftReflection class. + */ +public final class PaperAdventure { + private static final MethodHandle NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND; + private static final Method SEND_MESSAGE_COMPONENT; + + static { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodHandle nativeGsonComponentSerializerDeserializeMethodBound = null; + + // String.join because otherwise the class name will be relocated + final Class nativeGsonComponentSerializerClass = findClass(String.join(".", + "net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializer")); + final Class nativeGsonComponentSerializerImplClass = findClass(String.join(".", + "net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializerImpl")); + if (nativeGsonComponentSerializerClass != null && nativeGsonComponentSerializerImplClass != null) { + MethodHandle nativeGsonComponentSerializerGsonGetter = null; + try { + nativeGsonComponentSerializerGsonGetter = lookup.findStatic(nativeGsonComponentSerializerClass, + "gson", MethodType.methodType(nativeGsonComponentSerializerClass)); + } catch (final NoSuchMethodException | IllegalAccessException ignored) { + } + + MethodHandle nativeGsonComponentSerializerDeserializeMethod = null; + try { + final Method method = nativeGsonComponentSerializerImplClass.getDeclaredMethod("deserialize", String.class); + method.setAccessible(true); + nativeGsonComponentSerializerDeserializeMethod = lookup.unreflect(method); + } catch (final NoSuchMethodException | IllegalAccessException ignored) { + } + + if (nativeGsonComponentSerializerGsonGetter != null) { + if (nativeGsonComponentSerializerDeserializeMethod != null) { + try { + nativeGsonComponentSerializerDeserializeMethodBound = nativeGsonComponentSerializerDeserializeMethod + .bindTo(nativeGsonComponentSerializerGsonGetter.invoke()); + } catch (final Throwable throwable) { + GeyserImpl.getInstance().getLogger().error("Failed to access native GsonComponentSerializer", throwable); + } + } + } + } + + NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND = nativeGsonComponentSerializerDeserializeMethodBound; + + Method playerComponentSendMessage = null; + final Class nativeComponentClass = findClass(String.join(".", + "net", "kyori", "adventure", "text", "Component")); + if (nativeComponentClass != null) { + try { + playerComponentSendMessage = CommandSender.class.getMethod("sendMessage", nativeComponentClass); + } catch (final NoSuchMethodException e) { + if (GeyserImpl.getInstance().getLogger().isDebug()) { + e.printStackTrace(); + } + } + } + SEND_MESSAGE_COMPONENT = playerComponentSendMessage; + } + + public static Object toNativeComponent(final Component component) { + if (NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND == null) { + GeyserImpl.getInstance().getLogger().error("Illegal state where Component serialization was called when it wasn't available!"); + return null; + } + + try { + return NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND.invoke(DefaultComponentSerializer.get().serialize(component)); + } catch (final Throwable throwable) { + GeyserImpl.getInstance().getLogger().error("Failed to create native Component message", throwable); + return null; + } + } + + public static void sendMessage(final CommandSender sender, final Component component) { + if (SEND_MESSAGE_COMPONENT == null) { + GeyserImpl.getInstance().getLogger().error("Illegal state where Component sendMessage was called when it wasn't available!"); + return; + } + + final Object nativeComponent = toNativeComponent(component); + if (nativeComponent != null) { + try { + SEND_MESSAGE_COMPONENT.invoke(sender, nativeComponent); + } catch (final InvocationTargetException | IllegalAccessException e) { + GeyserImpl.getInstance().getLogger().error("Failed to send native Component message", e); + } + } + } + + public static boolean canSendMessageUsingComponent() { + return SEND_MESSAGE_COMPONENT != null; + } + + /** + * Gets a class by the first name available. + * + * @return a class or {@code null} if not found + */ + private static @Nullable Class findClass(final String className) { + try { + return Class.forName(className); + } catch (final ClassNotFoundException ignored) { + } + return null; + } + + private PaperAdventure() { + } +} 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 index 52779db23..61d394214 100644 --- 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 @@ -66,6 +66,9 @@ public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implement } 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]); 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/GeyserSpigotCommandManager.java index 9fb19f0da..655d3be23 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/GeyserSpigotCommandManager.java @@ -65,4 +65,8 @@ public class GeyserSpigotCommandManager extends GeyserCommandManager { Command cmd = COMMAND_MAP.getCommand(command.replace("/", "")); return cmd != null ? cmd.getDescription() : ""; } + + public static CommandMap getCommandMap() { + return COMMAND_MAP; + } } 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 839e4d88e..95fba707f 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 @@ -25,32 +25,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.ConsoleCommandSender; import org.bukkit.entity.Player; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - public class SpigotCommandSource implements GeyserCommandSource { - - /** - * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. - * 1.12 or greater should not use the legacy method. - */ - private static boolean USE_LEGACY_METHOD = false; - private static Method LOCALE_METHOD; - private final org.bukkit.command.CommandSender handle; - private final String locale; public SpigotCommandSource(org.bukkit.command.CommandSender handle) { this.handle = handle; - this.locale = getSpigotLocale(); // Ensure even Java players' languages are loaded - GeyserLocale.loadGeyserLocale(locale); + GeyserLocale.loadGeyserLocale(locale()); } @Override @@ -63,6 +52,17 @@ public class SpigotCommandSource implements GeyserCommandSource { handle.sendMessage(message); } + @Override + public void sendMessage(Component message) { + if (PaperAdventure.canSendMessageUsingComponent()) { + PaperAdventure.sendMessage(handle, message); + return; + } + + // CommandSender#sendMessage(BaseComponent[]) is Paper-only + handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message)); + } + @Override public boolean isConsole() { return handle instanceof ConsoleCommandSender; @@ -70,50 +70,15 @@ public class SpigotCommandSource implements GeyserCommandSource { @Override public String locale() { - return locale; + if (this.handle instanceof Player player) { + return player.getLocale(); + } + + return GeyserLocale.getDefaultLocale(); } @Override public boolean hasPermission(String permission) { return handle.hasPermission(permission); } - - /** - * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get - * {@code player.spigot().getLocale()}. - * - * @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale - */ - public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) { - USE_LEGACY_METHOD = useLegacyMethod; - if (USE_LEGACY_METHOD) { - try { - //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it - LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); - } catch (NoSuchMethodException e) { - GeyserImpl.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); - } - } - } - - /** - * So we only have to do nasty reflection stuff once per command - * - * @return the locale of the Spigot player - */ - private String getSpigotLocale() { - if (handle instanceof Player player) { - if (USE_LEGACY_METHOD) { - try { - // sigh - // This was the only option on older Spigot instances and now it's gone - return (String) LOCALE_METHOD.invoke(player.spigot()); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } else { - return player.getLocale(); - } - } - return GeyserLocale.getDefaultLocale(); - } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java index 8be1cb84e..5eb99e10c 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java @@ -105,13 +105,10 @@ public class GeyserPistonListener implements Listener { // Trying to grab the blocks from the world like other platforms would result in the moving piston block // being returned instead. if (!blocksFilled) { - // Blocks currently require a player for 1.12, so let's just leech off one player to get all blocks - // and call it a day for the rest of the sessions (mostly to save on execution time) List blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks(); for (Block block : blocks) { Location attachedLocation = block.getLocation(); - int blockId = worldManager.getBlockNetworkId(player, block, - attachedLocation.getBlockX(), attachedLocation.getBlockY(), attachedLocation.getBlockZ()); + int blockId = worldManager.getBlockNetworkId(block); // Ignore blocks that will be destroyed if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) { attachedBlocks.put(getVector(attachedLocation), blockId); @@ -120,7 +117,7 @@ public class GeyserPistonListener implements Listener { blocksFilled = true; } - int pistonBlockId = worldManager.getBlockNetworkId(player, event.getBlock(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); + int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock()); // event.getDirection() is unreliable Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java deleted file mode 100644 index 0ac8d6856..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.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.world.manager; - -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; - -/** - * Used with ViaVersion and pre-1.13. - */ -public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { - private final SpigotWorldAdapter adapter; - - public GeyserSpigot1_12NativeWorldManager(Plugin plugin) { - super(plugin); - this.adapter = SpigotAdapters.getWorldAdapter(); - // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return BlockStateValues.JAVA_AIR_ID; - } - // Get block entity storage - BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); - int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); - return getLegacyBlock(storage, blockId, x, y, z); - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java deleted file mode 100644 index 2ca024abf..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ /dev/null @@ -1,125 +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.world.manager; - -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.api.data.MappingData; -import com.viaversion.viaversion.api.minecraft.Position; -import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; -import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; -import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; -import org.bukkit.Bukkit; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; - -import java.util.List; - -/** - * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. - * - * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. - */ -public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { - /** - * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. - * (Block IDs did not change between server versions until 1.13 and after) - */ - private final MappingData mappingData1_12to1_13; - - /** - * The list of all protocols from the client's version to 1.13. - */ - private final List protocolList; - - public GeyserSpigot1_12WorldManager(Plugin plugin) { - super(plugin); - this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getVersion()); - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return BlockStateValues.JAVA_AIR_ID; - } - if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) { - // Prevent nasty async errors if a player is loading in - return BlockStateValues.JAVA_AIR_ID; - } - - Block block = player.getWorld().getBlockAt(x, y, z); - return getBlockNetworkId(player, block, x, y, z); - } - - @Override - @SuppressWarnings("deprecation") - public int getBlockNetworkId(Player player, Block block, int x, int y, int z) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); - // Black magic that gets the old block state ID - int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - return getLegacyBlock(storage, oldBlockId, x, y, z); - } - - /** - * - * @param storage ViaVersion's block entity storage (used to fix block entity state differences) - * @param blockId the pre-1.13 block id - * @param x X coordinate of block - * @param y Y coordinate of block - * @param z Z coordinate of block - * @return the block state updated to the latest Minecraft version - */ - public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = mappingData1_12to1_13.getNewBlockId(blockId); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; - } - - @Override - public boolean isLegacy() { - return true; - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java index bf9085979..6b5d1ea1e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -32,6 +32,7 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; +import org.jetbrains.annotations.Nullable; public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { protected final SpigotWorldAdapter adapter; @@ -49,4 +50,12 @@ public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { } return adapter.getBlockAt(player.getWorld(), x, y, z); } + + @Nullable + @Override + public String[] getBiomeIdentifiers(boolean withTags) { + // Biome identifiers will basically always be the same for one server, since you have to re-send the + // ClientboundLoginPacket to change the registry. Therefore, don't bother caching for each player. + return adapter.getBiomeSuggestions(withTags); + } } 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 093e28794..52f29dcfe 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 @@ -33,15 +33,13 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.Lectern; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.plugin.Plugin; import org.geysermc.geyser.level.GameRule; -import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; @@ -53,12 +51,7 @@ import java.util.List; /** * The base world manager to use when there is no supported NMS revision */ -public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** - * The current client protocol version for ViaVersion usage. - */ - protected static final int CLIENT_PROTOCOL_VERSION = GameProtocol.getJavaProtocolVersion(); - +public class GeyserSpigotWorldManager extends WorldManager { private final Plugin plugin; public GeyserSpigotWorldManager(Plugin plugin) { @@ -77,10 +70,10 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return BlockStateValues.JAVA_AIR_ID; } - return getBlockNetworkId(bukkitPlayer, world.getBlockAt(x, y, z), x, y, z); + return getBlockNetworkId(world.getBlockAt(x, y, z)); } - public int getBlockNetworkId(Player player, Block block, int x, int y, int z) { + public int getBlockNetworkId(Block block) { return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID); } @@ -158,12 +151,12 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return true; } - public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { + public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()); if (!value.isEmpty()) { return Boolean.parseBoolean(value); } - return (Boolean) gameRule.getDefaultValue(); + return gameRule.getDefaultBooleanValue(); } @Override @@ -172,7 +165,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if (!value.isEmpty()) { return Integer.parseInt(value); } - return (int) gameRule.getDefaultValue(); + return gameRule.getDefaultIntValue(); } @Override @@ -181,8 +174,6 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } /** - * This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}. - * * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id * to the current one. * diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 0f6398b49..e28b8981d 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -8,7 +8,4 @@ api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser - geyserext: - description: The command any extensions can register to. - usage: /geyserext \ No newline at end of file + usage: /geyser \ No newline at end of file diff --git a/bootstrap/sponge/build.gradle.kts b/bootstrap/sponge/build.gradle.kts index 2850b2c5e..3d89e8649 100644 --- a/bootstrap/sponge/build.gradle.kts +++ b/bootstrap/sponge/build.gradle.kts @@ -1,5 +1,3 @@ -val spongeVersion = "7.1.0" - dependencies { api(projects.core) } @@ -9,16 +7,11 @@ platformRelocate("io.netty") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("com.google.common") platformRelocate("com.google.guava") -platformRelocate("net.kyori") - -// Exclude these dependencies -exclude("com.google.code.gson:*") -exclude("org.yaml:*") -exclude("org.slf4j:*") -exclude("org.ow2.asm:*") +platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") +platformRelocate("net.kyori.adventure.nbt") // These dependencies are already present on the platform -provided("org.spongepowered", "spongeapi", spongeVersion) +provided(libs.sponge.api) application { mainClass.set("org.geysermc.geyser.platform.sponge.GeyserSpongeMain") @@ -32,5 +25,14 @@ tasks.withType { exclude(dependency("org.yaml:.*")) exclude(dependency("org.slf4j:.*")) exclude(dependency("org.ow2.asm:.*")) + + // Exclude all Kyori dependencies except the legacy NBT serializer and NBT + exclude(dependency("net.kyori:adventure-api:.*")) + exclude(dependency("net.kyori:examination-api:.*")) + exclude(dependency("net.kyori:examination-string:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-gson:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-plain:.*")) + exclude(dependency("net.kyori:adventure-key:.*")) } } \ No newline at end of file diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java index e65684af2..628c85fd1 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java @@ -27,35 +27,45 @@ package org.geysermc.geyser.platform.sponge; import lombok.Getter; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; import org.spongepowered.api.Platform; import org.spongepowered.api.Sponge; -import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.metadata.PluginMetadata; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; @Getter public class GeyserSpongeDumpInfo extends BootstrapDumpInfo { private final String platformName; private final String platformVersion; private final boolean onlineMode; + + @AsteriskSerializer.Asterisk(isIp = true) private final String serverIP; private final int serverPort; private final List plugins; GeyserSpongeDumpInfo() { - super(); - PluginContainer container = Sponge.getPlatform().getContainer(Platform.Component.IMPLEMENTATION); - this.platformName = container.getName(); - this.platformVersion = container.getVersion().get(); - this.onlineMode = Sponge.getServer().getOnlineMode(); - this.serverIP = Sponge.getServer().getBoundAddress().get().getHostString(); - this.serverPort = Sponge.getServer().getBoundAddress().get().getPort(); + PluginContainer container = Sponge.platform().container(Platform.Component.IMPLEMENTATION); + PluginMetadata platformMeta = container.metadata(); + this.platformName = platformMeta.name().orElse("unknown"); + this.platformVersion = platformMeta.version().getQualifier(); + this.onlineMode = Sponge.server().isOnlineModeEnabled(); + Optional socketAddress = Sponge.server().boundAddress(); + this.serverIP = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown"); + this.serverPort = socketAddress.map(InetSocketAddress::getPort).orElse(-1); this.plugins = new ArrayList<>(); - for (PluginContainer plugin : Sponge.getPluginManager().getPlugins()) { - String pluginClass = plugin.getInstance().map((pl) -> pl.getClass().getName()).orElse("unknown"); - this.plugins.add(new PluginInfo(true, plugin.getName(), plugin.getVersion().get(), pluginClass, plugin.getAuthors())); + for (PluginContainer plugin : Sponge.pluginManager().plugins()) { + PluginMetadata meta = plugin.metadata(); + List contributors = meta.contributors().stream().map(PluginContributor::name).collect(Collectors.toList()); + this.plugins.add(new PluginInfo(true, meta.name().orElse("unknown"), meta.version().toString(), meta.entrypoint(), contributors)); } } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java index 4ab4e5346..2bed78ac9 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java @@ -29,7 +29,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserLogger; -import org.slf4j.Logger; +import org.apache.logging.log4j.Logger; @AllArgsConstructor public class GeyserSpongeLogger implements GeyserLogger { diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java index a661061e2..f69a3ffb4 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java @@ -28,11 +28,12 @@ package org.geysermc.geyser.platform.sponge; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.EventContext; import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.cause.Cause; -import org.spongepowered.api.event.cause.EventContext; import org.spongepowered.api.event.server.ClientPingServerEvent; import org.spongepowered.api.network.status.StatusClient; import org.spongepowered.api.profile.GameProfile; @@ -43,7 +44,7 @@ import java.util.Optional; public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { - private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.getServer()); + private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.server()); private static Method SpongeStatusResponse_create; @@ -59,50 +60,46 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { SpongeStatusResponse_create = SpongeStatusResponse.getDeclaredMethod("create", MinecraftServer); } - Object response = SpongeStatusResponse_create.invoke(null, Sponge.getServer()); + Object response = SpongeStatusResponse_create.invoke(null, Sponge.server()); event = SpongeEventFactory.createClientPingServerEvent(CAUSE, new GeyserStatusClient(inetSocketAddress), (ClientPingServerEvent.Response) response); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } - Sponge.getEventManager().post(event); + Sponge.eventManager().post(event); GeyserPingInfo geyserPingInfo = new GeyserPingInfo( - event.getResponse().getDescription().toPlain(), + MessageTranslator.convertMessage(event.response().description()), new GeyserPingInfo.Players( - event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax(), - event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline() + event.response().players().orElseThrow(IllegalStateException::new).max(), + event.response().players().orElseThrow(IllegalStateException::new).online() ), new GeyserPingInfo.Version( - event.getResponse().getVersion().getName(), + event.response().version().name(), GameProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge ); - event.getResponse().getPlayers().get().getProfiles().stream() - .map(GameProfile::getName) - .map(op -> op.orElseThrow(IllegalStateException::new)) - .forEach(geyserPingInfo.getPlayerList()::add); + event.response().players().ifPresent(players -> players.profiles().stream() + .map(GameProfile::name) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(geyserPingInfo.getPlayerList()::add) + ); + return geyserPingInfo; } - @SuppressWarnings("NullableProblems") - private static class GeyserStatusClient implements StatusClient { - - private final InetSocketAddress remote; - - public GeyserStatusClient(InetSocketAddress remote) { - this.remote = remote; - } + private record GeyserStatusClient(InetSocketAddress remote) implements StatusClient { @Override - public InetSocketAddress getAddress() { + public InetSocketAddress address() { return this.remote; } @Override - public MinecraftVersion getVersion() { - return Sponge.getPlatform().getMinecraftVersion(); + public MinecraftVersion version() { + return Sponge.platform().minecraftVersion(); } @Override - public Optional getVirtualHost() { + public Optional virtualHost() { return Optional.empty(); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index 312dfb087..1f9541631 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -26,81 +26,169 @@ package org.geysermc.geyser.platform.sponge; import com.google.inject.Inject; +import org.apache.logging.log4j.Logger; import org.geysermc.common.PlatformType; 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.command.GeyserCommandManager; 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.sponge.command.GeyserSpongeCommandExecutor; import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; -import org.slf4j.Logger; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; +import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.game.state.GameStartedServerEvent; -import org.spongepowered.api.event.game.state.GameStoppedEvent; -import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; +import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.builtin.jvm.Plugin; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Path; +import java.util.Map; import java.util.UUID; -@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Sponge", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") +@Plugin(value = "geyser") public class GeyserSpongePlugin implements GeyserBootstrap { + /** + * True if the plugin should be in a disabled state. + * This exists because you can't unregister or disable plugins in Sponge + */ + private boolean enabled = true; + + @Inject + private PluginContainer pluginContainer; + @Inject private Logger logger; @Inject @ConfigDir(sharedRoot = false) - private File configDir; + private Path configPath; - private GeyserSpongeCommandManager geyserCommandManager; + // Available after construction lifecycle private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; + private GeyserImpl geyser; + private GeyserSpongeCommandManager geyserCommandManager; // Commands are only registered after command registration lifecycle + + // Available after StartedEngine lifecycle private IGeyserPingPassthrough geyserSpongePingPassthrough; - private GeyserImpl geyser; + /** + * Only to be used for reloading + */ @Override public void onEnable() { + enabled = true; + onConstruction(null); + // new commands cannot be registered, and geyser's command manager does not need be reloaded + onStartedEngine(null); + } + + @Override + public void onDisable() { + enabled = false; + if (geyser != null) { + geyser.shutdown(); + geyser = null; + } + } + + /** + * Construct the configuration, logger, and command manager. command manager will only be filled with commands once + * the connector is started, but it allows us to register events in sponge. + * + * @param event Not used. + */ + @Listener + public void onConstruction(@Nullable ConstructPluginEvent event) { GeyserLocale.init(this); - if (!configDir.exists()) + File configDir = configPath.toFile(); + if (!configDir.exists()) { configDir.mkdirs(); + } File configFile; try { configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); } catch (IOException ex) { logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); + onDisable(); return; } - try { - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); - } catch (IOException ex) { - logger.warn(GeyserLocale.getLocaleStringLog("geyser.config.failed")); - ex.printStackTrace(); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); + + this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); + + this.geyserCommandManager = new GeyserSpongeCommandManager(geyser); + this.geyserCommandManager.init(); + } + + /** + * Construct the {@link GeyserSpongeCommandManager} and register the commands + * + * @param event required to register the commands + */ + @Listener + public void onRegisterCommands(@Nonnull RegisterCommandEvent event) { + if (enabled) { + event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); + + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id()); + } + } + } + + /** + * Configure the config properly if remote address is auto. Start connector and ping passthrough, and register subcommands of /geyser + * + * @param event not required + */ + @Listener + public void onStartedEngine(@Nullable StartedEngineEvent event) { + if (!enabled) { return; } - if (Sponge.getServer().getBoundAddress().isPresent()) { - InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); + if (Sponge.server().boundAddress().isPresent()) { + InetSocketAddress javaAddr = Sponge.server().boundAddress().get(); - // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); + // Don't change the ip if its listening on all interfaces + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { + this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } geyserConfig.getRemote().setPort(javaAddr.getPort()); } } @@ -109,25 +197,18 @@ public class GeyserSpongePlugin implements GeyserBootstrap { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().port()); } - this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.start(PlatformType.SPONGE, this); + GeyserImpl.start(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserSpongePingPassthrough = new GeyserSpongePingPassthrough(); } - - this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); - this.geyserCommandManager.init(); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.commands()), "geyserext"); } - @Override - public void onDisable() { - geyser.shutdown(); + @Listener + public void onEngineStopping(StoppingEngineEvent event) { + onDisable(); } @Override @@ -142,7 +223,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { @Override public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + return geyserCommandManager; } @Override @@ -152,17 +233,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { @Override public Path getConfigFolder() { - return configDir.toPath(); - } - - @Listener - public void onServerStart(GameStartedServerEvent event) { - onEnable(); - } - - @Listener - public void onServerStop(GameStoppedEvent event) { - onDisable(); + return configPath; } @Override @@ -172,6 +243,6 @@ public class GeyserSpongePlugin implements GeyserBootstrap { @Override public String getMinecraftServerVersion() { - return Sponge.getPlatform().getMinecraftVersion().getName(); + return Sponge.platform().minecraftVersion().name(); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java index 3598ea8c2..a1a0d99ad 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -25,81 +25,88 @@ package org.geysermc.geyser.platform.sponge.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; 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 org.spongepowered.api.command.CommandCallable; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.text.Text; -import org.spongepowered.api.world.Location; -import org.spongepowered.api.world.World; +import org.spongepowered.api.command.parameter.ArgumentReader; -import javax.annotation.Nullable; import java.util.*; +import java.util.stream.Collectors; -public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements org.spongepowered.api.command.Command.Raw { public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map commands) { super(geyser, commands); } @Override - public CommandResult process(CommandSource source, String arguments) { - GeyserCommandSource commandSender = new SpongeCommandSource(source); - GeyserSession session = getGeyserSession(commandSender); + public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { + GeyserCommandSource commandSource = new SpongeCommandSource(cause); + GeyserSession session = getGeyserSession(commandSource); - String[] args = arguments.split(" "); - if (args.length > 0) { + String[] args = arguments.input().split(" "); + // This split operation results in an array of length 1, containing a zero length string, if the input string is empty + if (args.length > 0 && !args[0].isEmpty()) { GeyserCommand command = getCommand(args[0]); if (command != null) { - if (!source.hasPermission(command.permission())) { - // Not ideal to use log here but we dont get a session - source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + if (!cause.hasPermission(command.permission())) { + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")).color(NamedTextColor.RED)); return CommandResult.success(); } if (command.isBedrockOnly() && session == null) { - source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"))); + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")).color(NamedTextColor.RED)); return CommandResult.success(); } - getCommand(args[0]).execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + command.execute(session, commandSource, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + } else { + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.not_found")).color(NamedTextColor.RED)); } } else { - getCommand("help").execute(session, commandSender, new String[0]); + getCommand("help").execute(session, commandSource, new String[0]); } return CommandResult.success(); } @Override - public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { - if (arguments.split(" ").length == 1) { - return tabComplete(new SpongeCommandSource(source)); + public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { + if (arguments.input().split(" ").length == 1) { + return tabComplete(new SpongeCommandSource(cause)).stream().map(CommandCompletion::of).collect(Collectors.toList()); } return Collections.emptyList(); } @Override - public boolean testPermission(CommandSource source) { + public boolean canExecute(CommandCause cause) { return true; } @Override - public Optional getShortDescription(CommandSource source) { - return Optional.of(Text.of("The main command for Geyser.")); + public Optional shortDescription(CommandCause cause) { + return Optional.of(Component.text("The main command for Geyser.")); } @Override - public Optional getHelp(CommandSource source) { - return Optional.of(Text.of("/geyser help")); + public Optional extendedDescription(CommandCause cause) { + return shortDescription(cause); } @Override - public Text getUsage(CommandSource source) { - return Text.of("/geyser help"); + public Optional help(@NotNull CommandCause cause) { + return Optional.of(Component.text("/geyser help")); + } + + @Override + public Component usage(CommandCause cause) { + return Component.text("/geyser help"); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java index 8e981f72a..d83e3a723 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java @@ -25,25 +25,37 @@ package org.geysermc.geyser.platform.sponge.command; +import net.kyori.adventure.text.Component; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.spongepowered.api.Sponge; -import org.spongepowered.api.command.CommandMapping; -import org.spongepowered.api.text.Text; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.manager.CommandMapping; + +import java.util.Optional; public class GeyserSpongeCommandManager extends GeyserCommandManager { - private final org.spongepowered.api.command.CommandManager handle; - public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserImpl geyser) { + public GeyserSpongeCommandManager(GeyserImpl geyser) { super(geyser); - - this.handle = handle; } @Override public String description(String command) { - return handle.get(command).map(CommandMapping::getCallable) - .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) - .orElse(Text.EMPTY).toPlain(); + if (!Sponge.isServerAvailable()) { + return ""; + } + + // Note: The command manager may be replaced at any point during the game lifecycle + return Sponge.server().commandManager().commandMapping(command) + .map(this::description) + .map(Optional::get) + .map(MessageTranslator::convertMessage) + .orElse(""); + } + + public Optional description(CommandMapping mapping) { + return mapping.registrar().shortDescription(CommandCause.create(), mapping); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java index 12fdcb989..31dccc1fb 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java @@ -26,29 +26,30 @@ package org.geysermc.geyser.platform.sponge.command; import lombok.AllArgsConstructor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.command.source.ConsoleSource; -import org.spongepowered.api.text.Text; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; @AllArgsConstructor public class SpongeCommandSource implements GeyserCommandSource { - private CommandSource handle; + private final CommandCause handle; @Override public String name() { - return handle.getName(); + return handle.friendlyIdentifier().orElse(handle.identifier()); } @Override - public void sendMessage(String message) { - handle.sendMessage(Text.of(message)); + public void sendMessage(@NonNull String message) { + handle.audience().sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); } @Override public boolean isConsole() { - return handle instanceof ConsoleSource; + return !(handle.cause().root() instanceof ServerPlayer); } @Override diff --git a/bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json b/bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json new file mode 100644 index 000000000..2540f87b7 --- /dev/null +++ b/bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json @@ -0,0 +1,30 @@ +{ + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "MIT", + "plugins": [ + { + "id": "${id}", + "name": "${name}-Sponge", + "version": "${version}", + "entrypoint": "org.geysermc.geyser.platform.sponge.GeyserSpongePlugin", + "description": "${description}", + "links": { + "homepage": "${url}" + }, + "contributors": [ + { + "name": "${author}" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "8.0.0" + } + ] + } + ] +} diff --git a/bootstrap/sponge/src/main/resources/pack.mcmeta b/bootstrap/sponge/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..19e8dca71 --- /dev/null +++ b/bootstrap/sponge/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Geyser for Sponge", + "pack_format": 6 + } +} diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index d49c7c490..9c2194445 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -6,20 +6,16 @@ val jlineVersion = "3.21.0" dependencies { api(projects.core) - implementation("net.minecrell", "terminalconsoleappender", terminalConsoleVersion) { + implementation(libs.terminalconsoleappender) { exclude("org.apache.logging.log4j", "log4j-core") exclude("org.jline", "jline-reader") exclude("org.jline", "jline-terminal") exclude("org.jline", "jline-terminal-jna") } - implementation("org.jline", "jline-terminal", jlineVersion) - implementation("org.jline", "jline-terminal-jna", jlineVersion) - implementation("org.jline", "jline-reader", jlineVersion) + implementation(libs.bundles.jline) - implementation("org.apache.logging.log4j", "log4j-api", Versions.log4jVersion) - implementation("org.apache.logging.log4j", "log4j-core", Versions.log4jVersion) - implementation("org.apache.logging.log4j", "log4j-slf4j18-impl", Versions.log4jVersion) + implementation(libs.bundles.log4j) } application { @@ -27,7 +23,7 @@ application { } tasks.withType { - archiveBaseName.set("Geyser") + archiveBaseName.set("Geyser-Standalone") transform(Log4j2PluginsCacheFileTransformer()) } \ No newline at end of file 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 44194d75c..5cbbab9d4 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 @@ -47,10 +47,10 @@ import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandManager; import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.LoopbackUtil; import java.io.File; import java.io.IOException; @@ -63,7 +63,7 @@ import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { - private GeyserStandaloneCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -188,7 +188,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyserLogger = new GeyserStandaloneLogger(); - LoopbackUtil.checkLoopback(geyserLogger); + LoopbackUtil.checkAndApplyLoopback(geyserLogger); try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", @@ -216,8 +216,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { // Allow libraries like Protocol to have their debug information passthrough logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); - geyser = GeyserImpl.start(PlatformType.STANDALONE, this); - geyserCommandManager = new GeyserStandaloneCommandManager(geyser); + geyser = GeyserImpl.load(PlatformType.STANDALONE, this); + GeyserImpl.start(); + + geyserCommandManager = new GeyserCommandManager(geyser); geyserCommandManager.init(); if (gui != null) { 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 31b395a61..e7e24a465 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 @@ -95,24 +95,4 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey public boolean isDebug() { return log.isDebugEnabled(); } - - @Override - public String name() { - return "CONSOLE"; - } - - @Override - public void sendMessage(String message) { - info(message); - } - - @Override - public boolean isConsole() { - return true; - } - - @Override - public boolean hasPermission(String permission) { - return true; - } } 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 a8bce303f..41cbafb25 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,8 +28,8 @@ package org.geysermc.geyser.platform.standalone.gui; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger; -import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -256,7 +256,7 @@ public class GeyserStandaloneGUI { * @param geyserStandaloneLogger The current logger * @param geyserCommandManager The commands manager */ - public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserStandaloneCommandManager geyserCommandManager) { + public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) { commandsMenu.removeAll(); optionsMenu.removeAll(); diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index ab2f85b85..8908b2afd 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -1,7 +1,5 @@ -val velocityVersion = "3.0.0" - dependencies { - annotationProcessor("com.velocitypowered", "velocity-api", velocityVersion) + annotationProcessor(libs.velocity.api) api(projects.core) } @@ -34,7 +32,7 @@ exclude("net.kyori:adventure-text-serializer-legacy:*") exclude("net.kyori:adventure-nbt:*") // These dependencies are already present on the platform -provided("com.velocitypowered", "velocity-api", velocityVersion) +provided(libs.velocity.api) application { mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain") diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java index 9f429cc83..45eb7abb9 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java @@ -41,6 +41,8 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { private final String platformVersion; private final String platformVendor; private final boolean onlineMode; + + @AsteriskSerializer.Asterisk(isIp = true) private final String serverIP; private final int serverPort; private final List plugins; @@ -51,11 +53,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { this.platformVersion = proxy.getVersion().getVersion(); this.platformVendor = proxy.getVersion().getVendor(); this.onlineMode = proxy.getConfiguration().isOnlineMode(); - if (AsteriskSerializer.showSensitive || (proxy.getBoundAddress().getHostString().equals("") || proxy.getBoundAddress().getHostString().equals("0.0.0.0"))) { - this.serverIP = proxy.getBoundAddress().getHostString(); - } else { - this.serverIP = "***"; - } + this.serverIP = proxy.getBoundAddress().getHostString(); this.serverPort = proxy.getBoundAddress().getPort(); this.plugins = new ArrayList<>(); 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 739e99d43..5ac09416c 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 @@ -35,9 +35,12 @@ import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; +import net.kyori.adventure.util.Codec; import org.geysermc.common.PlatformType; 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.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -45,7 +48,6 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo; 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.GeyserVelocityCommandManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.Nullable; @@ -57,6 +59,7 @@ import java.net.InetSocketAddress; 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") @@ -71,7 +74,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Inject private CommandManager commandManager; - private GeyserVelocityCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityInjector geyserInjector; private GeyserVelocityLogger geyserLogger; @@ -84,6 +87,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public void onEnable() { + try { + Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); + } catch (NoSuchMethodException e) { + // velocitypowered.com has a build that is very outdated + logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + + "that has likely been downloaded is very outdated and does not support 1.19."); + return; + } + GeyserLocale.init(this); try { @@ -118,6 +130,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); + // Remove this in like a year try { // Should only exist on 1.0 @@ -140,21 +154,34 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); - this.geyser = GeyserImpl.start(PlatformType.VELOCITY, this); + } + + private void postStartup() { + GeyserImpl.start(); this.geyserInjector = new GeyserVelocityInjector(proxyServer); // Will be initialized after the proxy has been bound - this.geyserCommandManager = new GeyserVelocityCommandManager(geyser); + this.geyserCommandManager = new GeyserCommandManager(geyser); this.geyserCommandManager.init(); this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); - this.commandManager.register("geyserext", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.commands())); + 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)); + } + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } + + proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override @@ -199,9 +226,14 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Subscribe public void onProxyBound(ListenerBoundEvent event) { - if (event.getListenerType() == ListenerType.MINECRAFT && geyserInjector != null) { - // After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too - geyserInjector.initializeLocalChannel(this); + if (event.getListenerType() == ListenerType.MINECRAFT) { + // Once listener is bound, do our startup process + this.postStartup(); + + if (geyserInjector != null) { + // After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too + geyserInjector.initializeLocalChannel(this); + } } } 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 new file mode 100644 index 000000000..31e584612 --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java @@ -0,0 +1,47 @@ +/* + * 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; + +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.platform.velocity.command.VelocityCommandSource; +import org.geysermc.geyser.util.VersionCheckUtils; + +public final class GeyserVelocityUpdateListener { + + @Subscribe + public void onPlayerJoin(PostLoginEvent event) { + if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { + final Player player = event.getPlayer(); + if (player.hasPermission(Constants.UPDATE_PERMISSION)) { + 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 index c77a3daef..c89c35b06 100644 --- 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 @@ -63,6 +63,9 @@ public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor impleme 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]); 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 fa70d1cf7..00c99e92b 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 @@ -28,6 +28,7 @@ package org.geysermc.geyser.platform.velocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; @@ -59,6 +60,12 @@ public class VelocityCommandSource implements GeyserCommandSource { handle.sendMessage(LegacyComponentSerializer.legacy('§').deserialize(message)); } + @Override + public void sendMessage(Component message) { + // Be careful that we don't shade in Adventure!! + handle.sendMessage(message); + } + @Override public boolean isConsole() { return handle instanceof ConsoleCommandSource; diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 25cbfe9de..e21806660 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -6,16 +6,21 @@ plugins { repositories { gradlePluginPortal() + maven("https://repo.opencollab.dev/maven-snapshots") } dependencies { implementation("net.kyori", "indra-common", "2.0.6") implementation("org.jfrog.buildinfo", "build-info-extractor-gradle", "4.26.1") - implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.1") + implementation("com.github.johnrengelman", "shadow", "7.1.3-SNAPSHOT") + + // Within the gradle plugin classpath, there is a version conflict between loom and some other + // plugin for databind. This fixes it: minimum 2.13.2 is required by loom. + implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") } tasks.withType { kotlinOptions { jvmTarget = "16" } -} \ No newline at end of file +} diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt deleted file mode 100644 index 27f7bcaf5..000000000 --- a/build-logic/src/main/kotlin/Versions.kt +++ /dev/null @@ -1,46 +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 - */ - -object Versions { - const val jacksonVersion = "2.13.2" - const val fastutilVersion = "8.5.2" - const val nettyVersion = "4.1.66.Final" - const val guavaVersion = "29.0-jre" - const val nbtVersion = "2.1.0" - const val websocketVersion = "1.5.1" - const val protocolVersion = "a78a64b" - // Not pinned to specific version due to possible gradle bug - // See comment in settings.gradle.kts - const val raknetVersion = "1.6.28-SNAPSHOT" - const val mcauthlibVersion = "d9d773e" - const val mcprotocollibversion = "54fc9f0" - const val packetlibVersion = "3.0" - const val adventureVersion = "4.9.3" - const val eventVersion = "3.0.0" - const val junitVersion = "4.13.1" - const val checkerQualVersion = "3.19.0" - const val cumulusVersion = "1.1" - const val log4jVersion = "2.17.1" -} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 1f9793ee4..0c01913d2 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -25,7 +25,9 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.named fun Project.isSnapshot(): Boolean = @@ -43,9 +45,11 @@ fun Project.exclude(group: String) { } } -fun Project.platformRelocate(pattern: String) { +fun Project.platformRelocate(pattern: String, exclusion: String = "") { tasks.named("shadowJar") { - relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") + relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") { + exclude(exclusion) + } } } @@ -62,5 +66,11 @@ fun Project.provided(pattern: String, name: String, version: String, excludedOn: fun Project.provided(dependency: ProjectDependency) = provided(dependency.group!!, dependency.name, dependency.version!!) +fun Project.provided(dependency: MinimalExternalModuleDependency) = + provided(dependency.module.group, dependency.module.name, dependency.versionConstraint.requiredVersion) + +fun Project.provided(provider: Provider) = + provided(provider.get()) + private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = if (excludedOn and bit > 0) section else "" \ No newline at end of file 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 2ea5d88a4..44a74db3d 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -4,14 +4,15 @@ plugins { } dependencies { - compileOnly("org.checkerframework", "checker-qual", Versions.checkerQualVersion) + compileOnly("org.checkerframework", "checker-qual", "3.19.0") } tasks { processResources { - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) { + // Spigot, BungeeCord, Velocity, Sponge, Fabric + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json", "fabric.mod.json")) { expand( - "id" to "Geyser", + "id" to "geyser", "name" to "Geyser", "version" to project.version, "description" to project.description, @@ -30,4 +31,4 @@ java { targetCompatibility = JavaVersion.VERSION_16 withSourcesJar() -} \ No newline at end of file +} diff --git a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts index f1cb8b139..7525f97fa 100644 --- a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -5,27 +5,29 @@ plugins { } publishing { - publications.create("mavenJava") { - groupId = project.group as String - artifactId = project.name - version = project.version as String + publications { + create("mavenJava") { + groupId = project.group as String + artifactId = project.name + version = project.version as String - artifact(tasks["shadowJar"]) - artifact(tasks["sourcesJar"]) + from(components["java"]) + } } } artifactory { + setContextUrl("https://repo.opencollab.dev/artifactory") publish { repository { setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases") setMavenCompatible(true) } defaults { - publishConfigs("archives") + publications("mavenJava") setPublishArtifacts(true) setPublishPom(true) setPublishIvy(false) } } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index 7371978d3..06c2e987b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ allprojects { } val platforms = setOf( + projects.fabric, projects.bungeecord, projects.spigot, projects.sponge, diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 205b20c0e..db3fe3a77 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,3 +1,8 @@ +plugins { + id("geyser.publish-conventions") +} + dependencies { - api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) -} \ No newline at end of file + api(libs.cumulus) + api(libs.gson) +} diff --git a/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java index f06c0f9da..58281dec8 100644 --- a/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java +++ b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.pluginmessage; -import com.google.common.base.Charsets; +import java.nio.charset.StandardCharsets; public final class PluginMessageChannels { public static final String SKIN = "floodgate:skin"; @@ -35,7 +35,7 @@ public final class PluginMessageChannels { private static final byte[] FLOODGATE_REGISTER_DATA = String.join("\0", SKIN, FORM, TRANSFER, PACKET) - .getBytes(Charsets.UTF_8); + .getBytes(StandardCharsets.UTF_8); /** * Get the prebuilt register data as a byte array 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 ef7709859..406204759 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -40,7 +40,7 @@ public enum DeviceOs { AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - UWP("Windows 10"), + UWP("Windows"), WIN32("Windows x86"), DEDICATED("Dedicated"), TVOS("Apple TV"), diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 561c2f554..994325ea0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,5 +1,4 @@ import net.kyori.blossom.BlossomExtension -import net.kyori.indra.git.IndraGitExtension plugins { id("net.kyori.blossom") @@ -12,66 +11,50 @@ dependencies { api(projects.common) // Jackson JSON and YAML serialization - api("com.fasterxml.jackson.core", "jackson-annotations", Versions.jacksonVersion) - api("com.fasterxml.jackson.core", "jackson-databind", Versions.jacksonVersion + ".1") // Extra .1 as databind is a slightly different version - api("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", Versions.jacksonVersion) - api("com.google.guava", "guava", Versions.guavaVersion) - - api("com.nukkitx", "nbt", Versions.nbtVersion) + api(libs.bundles.jackson) + api(libs.guava) // Fastutil Maps - implementation("com.nukkitx.fastutil", "fastutil-int-int-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-long-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-byte-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-boolean-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-int-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-object-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-reference-maps", Versions.fastutilVersion) + implementation(libs.bundles.fastutil) // Network libraries - implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) + implementation(libs.websocket) - api("com.github.CloudburstMC.Protocol", "bedrock-v534", Versions.protocolVersion) { + api(libs.protocol) { exclude("com.nukkitx.network", "raknet") - exclude("com.nukkitx", "nbt") } - api("com.github.GeyserMC", "MCAuthLib", Versions.mcauthlibVersion) - api("com.github.GeyserMC", "MCProtocolLib", Versions.mcprotocollibversion) { + api(libs.mcauthlib) + api(libs.mcprotocollib) { + exclude("io.netty", "netty-all") exclude("com.github.GeyserMC", "packetlib") exclude("com.github.GeyserMC", "mcauthlib") } - api("com.github.steveice10", "packetlib", Versions.packetlibVersion) { + api(libs.packetlib) { exclude("io.netty", "netty-all") - // This is still experimental - additionally, it could only really benefit standalone - exclude("io.netty.incubator", "netty-incubator-transport-native-io_uring") } - implementation("com.nukkitx.network", "raknet", Versions.raknetVersion) { + implementation(libs.raknet) { exclude("io.netty", "*"); } - implementation("io.netty", "netty-resolver-dns", Versions.nettyVersion) - implementation("io.netty", "netty-resolver-dns-native-macos", Versions.nettyVersion, null, "osx-x86_64") - implementation("io.netty", "netty-codec-haproxy", Versions.nettyVersion) + implementation(libs.netty.resolver.dns) + implementation(libs.netty.resolver.dns.native.macos) { artifact { classifier = "osx-x86_64" } } + implementation(libs.netty.codec.haproxy) // Network dependencies we are updating ourselves - api("io.netty", "netty-handler", Versions.nettyVersion) + api(libs.netty.handler) - implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-x86_64") - implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-aarch_64") - implementation("io.netty", "netty-transport-native-kqueue", Versions.nettyVersion, null, "osx-x86_64") + implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } } + implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } } + implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } } // Adventure text serialization - implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) - implementation("net.kyori", "adventure-text-serializer-plain", Versions.adventureVersion) - - // Kyori Misc - implementation("net.kyori", "event-api", Versions.eventVersion) + api(libs.bundles.adventure) // Test - testImplementation("junit", "junit", Versions.junitVersion) + testImplementation(libs.junit) // Annotation Processors compileOnly(projects.ap) @@ -79,20 +62,70 @@ dependencies { annotationProcessor(projects.ap) } -configure { - val indra = the() - - val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${branchName()}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" - - replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) - replaceToken("\${gitVersion}", gitVersion, mainFile) - replaceToken("\${buildNumber}", buildNumber(), mainFile) - replaceToken("\${branch}", branchName(), mainFile) +configurations.api { + // This is still experimental - additionally, it could only really benefit standalone + exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring") } -fun Project.branchName(): String = - System.getenv("GIT_BRANCH") ?: "local/dev" +tasks.processResources { + // This is solely for backwards compatibility for other programs that used this file before the switch to gradle. + // It used to be generated by the maven Git-Commit-Id-Plugin + filesMatching("git.properties") { + val info = GitInfo() + expand( + "branch" to info.branch, + "buildNumber" to info.buildNumber, + "projectVersion" to project.version, + "commit" to info.commit, + "commitAbbrev" to info.commitAbbrev, + "commitMessage" to info.commitMessage, + "repository" to info.repository + ) + } +} + +configure { + val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" + val info = GitInfo() + + replaceToken("\${version}", "${project.version} (${info.gitVersion})", mainFile) + replaceToken("\${gitVersion}", info.gitVersion, mainFile) + replaceToken("\${buildNumber}", info.buildNumber, mainFile) + replaceToken("\${branch}", info.branch, mainFile) + replaceToken("\${commit}", info.commit, mainFile) + replaceToken("\${repository}", info.repository, mainFile) +} fun Project.buildNumber(): Int = - Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") \ No newline at end of file + System.getenv("BUILD_NUMBER")?.let { Integer.parseInt(it) } ?: -1 + +inner class GitInfo { + val branch: String + val commit: String + val commitAbbrev: String + + val gitVersion: String + val version: String + val buildNumber: Int + + val commitMessage: String + val repository: String + + init { + // On Jenkins, a detached head is checked out, so indra cannot determine the branch. + // Fortunately, this environment variable is available. + branch = indraGit.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV" + + val commit = indraGit.commit() + this.commit = commit?.name ?: "0".repeat(40) + commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7) + + gitVersion = "git-${branch}-${commitAbbrev}" + version = "${project.version} ($gitVersion)" + buildNumber = buildNumber() + + val git = indraGit.git() + commitMessage = git?.commit()?.message ?: "" + repository = git?.repository?.config?.getString("remote", "origin", "url") ?: "" + } +} diff --git a/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 21aa35efc..258787e78 100644 --- a/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -128,7 +128,7 @@ public class GeyserSession { } public String getName() { - return this.handle.name(); + return this.handle.bedrockUsername(); } public boolean isConsole() { diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 360ef8347..f3593600d 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -37,6 +37,9 @@ public final class Constants { public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"; + public static final String GEYSER_DOWNLOAD_LOCATION = "https://ci.geysermc.org"; + public static final String UPDATE_PERMISSION = "geyser.update"; + static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json"; public static final String GEYSER_NAMESPACE = "geyser:"; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 146cb985f..a10e54f90 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -41,10 +41,15 @@ import io.netty.util.internal.SystemPropertyUtil; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; import org.geysermc.common.PlatformType; +import org.geysermc.cumulus.form.Form; +import org.geysermc.cumulus.form.util.FormBuilder; import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.Base64Topping; @@ -52,6 +57,7 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; @@ -74,15 +80,12 @@ import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.skin.SkinProvider; -import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.*; -import javax.naming.directory.Attribute; -import javax.naming.directory.InitialDirContext; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -113,6 +116,8 @@ public class GeyserImpl implements GeyserApi { public static final String BUILD_NUMBER = "${buildNumber}"; public static final String BRANCH = "${branch}"; + public static final String COMMIT = "${commit}"; + public static final String REPOSITORY = "${repository}"; /** * Oauth client ID for Microsoft authentication @@ -141,7 +146,7 @@ public class GeyserImpl implements GeyserApi { private final PlatformType platformType; private final GeyserBootstrap bootstrap; - private final EventBus eventBus; + private final EventBus eventBus; private final GeyserExtensionManager extensionManager; private Metrics metrics; @@ -160,16 +165,7 @@ public class GeyserImpl implements GeyserApi { this.platformType = platformType; this.bootstrap = bootstrap; - long startupTime = System.currentTimeMillis(); - GeyserLocale.finalizeDefaultLocale(this); - GeyserLogger logger = bootstrap.getGeyserLogger(); - - logger.info("******************************************"); - logger.info(""); - logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); - logger.info(""); - logger.info("******************************************"); /* Initialize event bus */ this.eventBus = new GeyserEventBus(); @@ -177,9 +173,19 @@ public class GeyserImpl implements GeyserApi { /* Load Extensions */ this.extensionManager = new GeyserExtensionManager(); this.extensionManager.init(); - - this.extensionManager.enableExtensions(); this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); + } + + public void initialize() { + long startupTime = System.currentTimeMillis(); + + GeyserLogger logger = bootstrap.getGeyserLogger(); + + logger.info("******************************************"); + logger.info(""); + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); + logger.info(""); + logger.info("******************************************"); /* Initialize registries */ Registries.init(); @@ -191,7 +197,7 @@ public class GeyserImpl implements GeyserApi { MessageTranslator.init(); MinecraftLocale.init(); - start(); + startInstance(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -223,7 +229,7 @@ public class GeyserImpl implements GeyserApi { } } - private void start() { + private void startInstance() { this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); GeyserLogger logger = bootstrap.getGeyserLogger(); @@ -250,23 +256,12 @@ public class GeyserImpl implements GeyserApi { 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")) { - int remotePort; - try { - // Searches for a server address and a port from a SRV record of the specified host name - InitialDirContext ctx = new InitialDirContext(); - Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV"); - // size > 0 = SRV entry found - if (attr != null && attr.size() > 0) { - String[] record = ((String) attr.get(0)).split(" "); - // Overwrites the existing address and port with that from the SRV record. - config.getRemote().setAddress(remoteAddress = record[3]); - config.getRemote().setPort(remotePort = Integer.parseInt(record[2])); - logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); - } - } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes - logger.debug("Exception while trying to find an SRV record for the remote host."); - if (config.isDebugMode()) - ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record + String[] record = WebUtils.findSrvRecord(this, remoteAddress); + if (record != null) { + int remotePort = Integer.parseInt(record[2]); + config.getRemote().setAddress(remoteAddress = record[3]); + config.getRemote().setPort(remotePort); + logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); } } @@ -322,8 +317,8 @@ public class GeyserImpl implements GeyserApi { 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(ChatColor.GREEN + "Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0"); - logger.info(ChatColor.GREEN + "Then, restart this server."); + 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(); @@ -475,22 +470,24 @@ public class GeyserImpl implements GeyserApi { newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); - } - - @Override - public @Nullable GeyserSession connectionByName(@NonNull String name) { - for (GeyserSession session : sessionManager.getAllSessions()) { - if (session.name().equals(name) || session.getProtocol().getProfile().getName().equals(name)) { - return session; - } + if (config.isNotifyOnNewBedrockUpdate()) { + VersionCheckUtils.checkForGeyserUpdate(this::getLogger); } - - return null; } @Override public @NonNull List onlineConnections() { - return this.sessionManager.getAllSessions(); + return sessionManager.getAllSessions(); + } + + @Override + public int onlineConnectionsCount() { + return sessionManager.size(); + } + + @Override + public @MonotonicNonNull String usernamePrefix() { + return null; } @Override @@ -500,13 +497,38 @@ public class GeyserImpl implements GeyserApi { @Override public @Nullable GeyserSession connectionByXuid(@NonNull String xuid) { - for (GeyserSession session : sessionManager.getAllSessions()) { - if (session.xuid().equals(xuid)) { - return session; - } - } + return sessionManager.sessionByXuid(xuid); + } - return null; + @Override + public boolean isBedrockPlayer(@NonNull UUID uuid) { + return connectionByUuid(uuid) != null; + } + + @Override + public boolean sendForm(@NonNull UUID uuid, @NonNull Form form) { + Objects.requireNonNull(uuid); + Objects.requireNonNull(form); + GeyserSession session = connectionByUuid(uuid); + if (session == null) { + return false; + } + return session.sendForm(form); + } + + @Override + public boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder) { + return sendForm(uuid, formBuilder.build()); + } + + @Override + public boolean transfer(@NonNull UUID uuid, @NonNull String address, int port) { + Objects.requireNonNull(uuid); + GeyserSession session = connectionByUuid(uuid); + if (session == null) { + return false; + } + return session.transfer(address, port); } public void shutdown() { @@ -529,7 +551,7 @@ public class GeyserImpl implements GeyserApi { ResourcePack.PACKS.clear(); - this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.commandManager(), this.eventBus)); + this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); @@ -554,11 +576,12 @@ public class GeyserImpl implements GeyserApi { } @Override + @NonNull public GeyserExtensionManager extensionManager() { return this.extensionManager; } - @Override + @NonNull public GeyserCommandManager commandManager() { return this.bootstrap.getGeyserCommandManager(); } @@ -569,15 +592,18 @@ public class GeyserImpl implements GeyserApi { } @Override - public EventBus eventBus() { + @NonNull + public EventBus eventBus() { return this.eventBus; } + @NonNull public RemoteServer defaultRemoteServer() { return getConfig().getRemote(); } @Override + @NonNull public BedrockListener bedrockListener() { return getConfig().getBedrock(); } @@ -590,18 +616,26 @@ public class GeyserImpl implements GeyserApi { return Integer.parseInt(BUILD_NUMBER); } - public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { + public static GeyserImpl load(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); } + return instance; + } + + public static void start() { + if (instance == null) { + throw new RuntimeException("Geyser has not been loaded yet!"); + } + // We've been reloaded if (instance.isShuttingDown()) { instance.shuttingDown = false; - instance.start(); + instance.startInstance(); + } else { + instance.initialize(); } - - return instance; } public GeyserLogger getLogger() { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index b47801cb5..88220eec9 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -25,9 +25,12 @@ package org.geysermc.geyser; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.command.GeyserCommandSource; + import javax.annotation.Nullable; -public interface GeyserLogger { +public interface GeyserLogger extends GeyserCommandSource { /** * Logs a severe message to console @@ -73,6 +76,15 @@ public interface GeyserLogger { */ void info(String message); + /** + * Logs an info component to console + * + * @param message the message to log + */ + default void info(Component message) { + sendMessage(message); + } + /** * Logs a debug message to console * @@ -100,4 +112,24 @@ public interface GeyserLogger { * If debug is enabled for this logger */ boolean isDebug(); + + @Override + default String name() { + return "CONSOLE"; + } + + @Override + default void sendMessage(String message) { + info(message); + } + + @Override + default boolean isConsole() { + return true; + } + + @Override + default boolean hasPermission(String permission) { + return true; + } } 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 0d020ad08..5808dbc2c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -33,7 +33,6 @@ import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.session.GeyserSession; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,7 +48,7 @@ public abstract class GeyserCommand implements Command { protected final String description; protected final String permission; - private List aliases = new ArrayList<>(); + private List aliases = Collections.emptyList(); public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args); diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 4fd5ba411..d28f9d24e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -33,30 +33,46 @@ import org.geysermc.common.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.CommandManager; import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.command.defaults.*; +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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @RequiredArgsConstructor -public abstract class GeyserCommandManager extends CommandManager { +public class GeyserCommandManager { @Getter private final Map commands = new Object2ObjectOpenHashMap<>(12); - private final Map extensionCommands = new Object2ObjectOpenHashMap<>(0); + 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", commands)); + 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")); @@ -66,11 +82,33 @@ public abstract class GeyserCommandManager extends CommandManager { 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")); - if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { + 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")); } - register(new HelpCommand(geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", "geyserext", extensionCommands)); + if (this.geyser.extensionManager().extensions().size() > 0) { + 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()) { + registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", entry.getKey().description().id(), entry.getValue())); + } } /** @@ -80,9 +118,8 @@ public abstract class GeyserCommandManager extends CommandManager { register(command, this.commands); } - @Override - public void register(@NonNull Command command) { - register(command, this.extensionCommands); + 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) { @@ -98,32 +135,30 @@ public abstract class GeyserCommandManager extends CommandManager { } } - @Override - public void unregister(@NonNull Command command) { - this.extensionCommands.remove(command.name(), command); - - if (command.aliases().isEmpty()) { - return; - } - - for (String alias : command.aliases()) { - this.extensionCommands.remove(alias, command); - } + @NotNull + public Map commands() { + return Collections.unmodifiableMap(this.commands); } @NotNull - @Override - public Map commands() { + public Map> extensionCommands() { return Collections.unmodifiableMap(this.extensionCommands); } public boolean runCommand(GeyserCommandSource sender, String command) { - boolean extensionCommand = command.startsWith("geyserext "); - if (!command.startsWith("geyser ") && !extensionCommand) { + 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(extensionCommand ? "geyserext " : "geyser ", ""); + command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", ""); String label; String[] args; @@ -136,9 +171,9 @@ public abstract class GeyserCommandManager extends CommandManager { args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; } - Command cmd = (extensionCommand ? this.extensionCommands : this.commands).get(label); + Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label); if (cmd == null) { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); + sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); return false; } @@ -163,11 +198,14 @@ public abstract class GeyserCommandManager extends CommandManager { * @param command Command to get the description for * @return Command description */ - public abstract String description(String command); + public String description(String command) { + return ""; + } @RequiredArgsConstructor public static class CommandBuilder implements Command.Builder { - private final Class sourceType; + private final Extension extension; + private Class sourceType; private String name; private String description = ""; private String permission = ""; @@ -178,22 +216,28 @@ public abstract class GeyserCommandManager extends CommandManager { private boolean bedrockOnly; private CommandExecutor executor; - public CommandBuilder name(String name) { + @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(String description) { + public CommandBuilder description(@NonNull String description) { this.description = description; return this; } - public CommandBuilder permission(String permission) { + public CommandBuilder permission(@NonNull String permission) { this.permission = permission; return this; } - public CommandBuilder aliases(List aliases) { + public CommandBuilder aliases(@NonNull List aliases) { this.aliases = aliases; return this; } @@ -209,7 +253,7 @@ public abstract class GeyserCommandManager extends CommandManager { return this; } - public CommandBuilder subCommands(List subCommands) { + public CommandBuilder subCommands(@NonNull List subCommands) { this.subCommands = subCommands; return this; } @@ -219,22 +263,27 @@ public abstract class GeyserCommandManager extends CommandManager { return this; } - public CommandBuilder executor(CommandExecutor executor) { + public CommandBuilder executor(@NonNull CommandExecutor executor) { this.executor = executor; return this; } - public GeyserCommand build() { + @NonNull + public GeyserExtensionCommand build() { if (this.name == null || this.name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); } - return new GeyserCommand(this.name, this.description, this.permission) { + 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; + Class sourceType = CommandBuilder.this.sourceType; CommandExecutor executor = CommandBuilder.this.executor; if (sourceType.isInstance(session)) { executor.execute((T) session, this, args); 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 eabccc243..88d148b11 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -27,6 +27,8 @@ package org.geysermc.geyser.command; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.text.GeyserLocale; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; /** * Implemented on top of any class that can send a command. @@ -40,4 +42,8 @@ public interface GeyserCommandSource extends CommandSource { default String locale() { return GeyserLocale.getDefaultLocale(); } + + default void sendMessage(Component message) { + sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); + } } 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 new file mode 100644 index 000000000..95c115769 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -0,0 +1,142 @@ +/* + * 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.defaults; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +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.geysermc.geyser.util.LoopbackUtil; +import org.geysermc.geyser.util.WebUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class ConnectionTestCommand extends GeyserCommand { + private final GeyserImpl geyser; + + public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + 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; + } + + if (args.length == 0) { + sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`"); + return; + } + + // Still allow people to not supply a port and fallback to 19132 + String[] fullAddress = args[0].split(":", 2); + int port; + if (fullAddress.length == 2) { + port = Integer.parseInt(fullAddress[1]); + } else { + port = 19132; + } + + // Issue: do the ports not line up? + if (port != geyser.getConfig().getBedrock().port()) { + sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration (" + + geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`."); + } + + // Issue: is the `bedrock` `address` in the config different? + if (!geyser.getConfig().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."); + } + + // Issue: did someone turn on enable-proxy-protocol and they didn't mean it? + if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + sender.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."); + } + + CompletableFuture.runAsync(() -> { + try { + // Issue: SRV record? + String ip = fullAddress[0]; + 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] + + ". 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. " + + "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"); + } + + // mcsrvstatus will likely be replaced in the future with our own service where we can also test + // around the OVH workaround without worrying about caching + JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + args[0]); + + long cacheTime = output.get("debug").get("cachetime").asLong(); + String when; + if (cacheTime == 0) { + when = "now"; + } else { + when = ((System.currentTimeMillis() / 1000L) - cacheTime) + " seconds ago"; + } + + if (output.get("online").asBoolean()) { + sender.sendMessage("Your server is likely online as of " + when + "!"); + sendLinks(sender); + return; + } + + sender.sendMessage("Your server is likely unreachable from outside the network as of " + when + "."); + sendLinks(sender); + } catch (Exception e) { + sender.sendMessage("Error while trying to check your connection!"); + geyser.getLogger().error("Error while trying to check your connection!", e); + } + }); + } + + private void sendLinks(GeyserCommandSource sender) { + sender.sendMessage("If you still have issues, check to see if your hosting provider has a specific setup: " + + "https://wiki.geysermc.org/geyser/supported-hosting-providers/" + ", see this page: " + + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on our Discord: " + "https://discord.gg/geysermc"); + } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } +} 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 81f34b759..6e7ad2f04 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 @@ -66,19 +66,19 @@ public class HelpCommand extends GeyserCommand { String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); sender.sendMessage(header); - for (Map.Entry entry : commands.entrySet()) { + 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) { - continue; + return; } sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " + GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale())); } - } + }); } } 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 f911e431e..90446fbb6 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 @@ -47,7 +47,7 @@ public class ListCommand extends GeyserCommand { 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::name).collect(Collectors.joining(" "))); + geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); sender.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 bba2e8d21..0015149be 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 @@ -47,7 +47,7 @@ public class OffhandCommand extends GeyserCommand { } ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, Vector3i.ZERO, - Direction.DOWN, session.getWorldCache().nextPredictionSequence()); + Direction.DOWN, 0); session.sendDownstreamPacket(releaseItemPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 6f183b89a..1d2988a2c 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -111,6 +111,12 @@ public interface GeyserConfiguration { int getCustomSkullRenderDistance(); + boolean isLogPlayerIpAddresses(); + + boolean isNotifyOnNewBedrockUpdate(); + + String getUnusableSpaceBlock(); + IMetricsInfo getMetrics(); int getPendingAuthenticationTimeout(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 0818f2ef0..e295e6053 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -154,6 +154,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("xbox-achievements-enabled") private boolean xboxAchievementsEnabled = false; + @JsonProperty("log-player-ip-addresses") + private boolean logPlayerIpAddresses = true; + + @JsonProperty("notify-on-new-bedrock-update") + private boolean notifyOnNewBedrockUpdate = true; + + @JsonProperty("unusable-space-block") + private String unusableSpaceBlock = "minecraft:barrier"; + private MetricsInfo metrics = new MetricsInfo(); @JsonProperty("pending-authentication-timeout") @@ -162,6 +171,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "0.0.0.0"; @Override @@ -170,6 +180,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration } @Setter + @JsonProperty("port") private int port = 19132; @Override @@ -181,6 +192,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("clone-remote-port") private boolean cloneRemotePort = false; + @JsonProperty("motd1") private String motd1 = "GeyserMC"; @Override @@ -188,6 +200,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration return motd1; } + @JsonProperty("motd2") private String motd2 = "Geyser"; @Override @@ -240,6 +253,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "auto"; @Override @@ -249,6 +263,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonDeserialize(using = PortDeserializer.class) @Setter + @JsonProperty("port") private int port = 25565; @Override diff --git a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java index 0bbc1c0ed..fda0566fd 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java @@ -29,6 +29,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.text.AsteriskSerializer; import java.util.List; @@ -53,6 +54,8 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor public static class ListenerInfo { + + @AsteriskSerializer.Asterisk(isIp = true) public String ip; public int port; } 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 da52d064c..5197f2107 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.dump; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.hash.Hashing; import com.google.common.io.ByteSource; @@ -50,7 +51,6 @@ import org.geysermc.geyser.util.WebUtils; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -70,7 +70,7 @@ public class DumpInfo { private final String cpuName; private final Locale systemLocale; private final String systemEncoding; - private Properties gitInfo; + private final GitInfo gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; private final Object2IntMap userPlatforms; @@ -89,11 +89,7 @@ public class DumpInfo { this.systemLocale = Locale.getDefault(); this.systemEncoding = System.getProperty("file.encoding"); - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { - this.gitInfo = new Properties(); - this.gitInfo.load(stream); - } catch (IOException ignored) { - } + this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY); this.config = GeyserImpl.getInstance().getConfig(); this.floodgate = new Floodgate(); @@ -300,4 +296,18 @@ public class DumpInfo { public String main; public List authors; } + + @Getter + @AllArgsConstructor + public static class GitInfo { + private final String buildNumber; + @JsonProperty("git.commit.id.abbrev") + private final String commitHashAbbrev; + @JsonProperty("git.commit.id") + private final String commitHash; + @JsonProperty("git.branch") + private final String branchName; + @JsonProperty("git.remote.origin.url") + private final String originUrl; + } } 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 52d9250ac..b97e23847 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -60,6 +60,7 @@ public final class EntityDefinitions { public static final EntityDefinition BEE; public static final EntityDefinition BLAZE; public static final EntityDefinition BOAT; + public static final EntityDefinition CAMEL; public static final EntityDefinition CAT; public static final EntityDefinition CAVE_SPIDER; public static final EntityDefinition CHEST_MINECART; @@ -453,6 +454,8 @@ public final class EntityDefinitions { ALLAY = EntityDefinition.inherited(AllayEntity::new, mobEntityBase) .type(EntityType.ALLAY) .height(0.6f).width(0.35f) + .addTranslator(MetadataType.BOOLEAN, AllayEntity::setDancing) + .addTranslator(MetadataType.BOOLEAN, AllayEntity::setCanDuplicate) .build(); BAT = EntityDefinition.inherited(BatEntity::new, mobEntityBase) .type(EntityType.BAT) @@ -857,6 +860,13 @@ public final class EntityDefinitions { .addTranslator(MetadataType.BYTE, AbstractHorseEntity::setHorseFlags) .addTranslator(null) // UUID of owner .build(); + CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase) + .type(EntityType.CAMEL) + .identifier("minecraft:llama") // todo 1.20 + .height(2.375f).width(1.7f) + .addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing) + .addTranslator(null) // Last pose change tick + .build(); HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase) .type(EntityType.HORSE) .height(1.6f).width(1.3965f) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java index ab43bf7b3..c562df476 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java @@ -57,7 +57,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { @Override public void setCustomBlock(IntEntityMetadata entityMetadata) { - customBlock = ((IntEntityMetadata) entityMetadata).getPrimitiveValue(); + customBlock = entityMetadata.getPrimitiveValue(); if (showCustomBlock) { dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(customBlock)); 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 1db2e6117..663dd3c33 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 @@ -44,6 +44,8 @@ import lombok.Setter; import net.kyori.adventure.text.Component; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; @@ -216,19 +218,41 @@ public class Entity { } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { - setYaw(yaw); - setPitch(pitch); - setHeadYaw(headYaw); - setOnGround(isOnGround); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); - MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + MoveEntityDeltaPacket moveEntityPacket = new MoveEntityDeltaPacket(); moveEntityPacket.setRuntimeEntityId(geyserId); - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(false); - + if (relX != 0.0) { + moveEntityPacket.setX(position.getX()); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); + } + if (relY != 0.0) { + moveEntityPacket.setY(position.getY()); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); + } + if (relZ != 0.0) { + moveEntityPacket.setZ(position.getZ()); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); + } + if (pitch != this.pitch) { + this.pitch = pitch; + moveEntityPacket.setPitch(pitch); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); + } + if (yaw != this.yaw) { + this.yaw = yaw; + moveEntityPacket.setYaw(yaw); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); + } + if (headYaw != this.headYaw) { + this.headYaw = headYaw; + moveEntityPacket.setHeadYaw(headYaw); + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); + } + setOnGround(isOnGround); + if (isOnGround) { + moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); + } session.sendUpstreamPacket(moveEntityPacket); } @@ -333,6 +357,7 @@ public class Entity { setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); + // Swimming is ignored here and instead we rely on the pose setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); 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 2550643d3..f7e055417 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 @@ -106,6 +106,9 @@ public class LivingEntity extends Entity { // Riptide spin attack setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); + + // OptionalPack usage + setFlag(EntityFlag.EMERGING, isUsingItem && isUsingOffhand); } public void setHealth(FloatEntityMetadata entityMetadata) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java index ab444c4ab..d37a67938 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java @@ -25,8 +25,10 @@ package org.geysermc.geyser.entity.type.living; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; @@ -37,14 +39,27 @@ import javax.annotation.Nonnull; import java.util.UUID; public class AllayEntity extends MobEntity { + private boolean canDuplicate; + public AllayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + public void setDancing(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.DANCING, entityMetadata.getPrimitiveValue()); + } + + public void setCanDuplicate(BooleanEntityMetadata entityMetadata) { + this.canDuplicate = entityMetadata.getPrimitiveValue(); + } + @Nonnull @Override protected InteractiveTag testMobInteraction(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) { - if (!this.hand.isValid() && !itemInHand.isEmpty()) { + if (this.canDuplicate && getFlag(EntityFlag.DANCING) && isDuplicationItem(itemInHand)) { + // Maybe better as another tag? + return InteractiveTag.GIVE_ITEM_TO_ALLAY; + } else if (!this.hand.isValid() && !itemInHand.isEmpty()) { return InteractiveTag.GIVE_ITEM_TO_ALLAY; } else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) { // Seems like there isn't a good tag for this yet @@ -57,7 +72,10 @@ public class AllayEntity extends MobEntity { @Nonnull @Override protected InteractionResult mobInteract(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) { - if (!this.hand.isValid() && !itemInHand.isEmpty()) { + if (this.canDuplicate && getFlag(EntityFlag.DANCING) && isDuplicationItem(itemInHand)) { + //TOCHECK sound + return InteractionResult.SUCCESS; + } else if (!this.hand.isValid() && !itemInHand.isEmpty()) { //TODO play sound? return InteractionResult.SUCCESS; } else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) { @@ -67,4 +85,8 @@ public class AllayEntity extends MobEntity { return super.mobInteract(hand, itemInHand); } } + + private boolean isDuplicationItem(GeyserItemStack itemStack) { + return itemStack.getJavaId() == session.getItemMappings().getStoredItems().amethystShard(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 75b2ad991..8ab882f4b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -26,13 +26,13 @@ package org.geysermc.geyser.entity.type.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import lombok.Getter; import net.kyori.adventure.text.Component; import org.geysermc.geyser.entity.EntityDefinition; @@ -54,6 +54,8 @@ public class ArmorStandEntity extends LivingEntity { @Getter private boolean isSmall = false; + private boolean isNameTagVisible = false; + /** * On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock. * By having a second entity, we can allow an invisible entity with the name tag. @@ -75,7 +77,6 @@ public class ArmorStandEntity extends LivingEntity { * - No armor, no name: false * - No armor, yes name: true */ - @Getter private boolean positionRequiresOffset = false; /** * Whether we should update the position of this armor stand after metadata updates. @@ -88,7 +89,11 @@ public class ArmorStandEntity extends LivingEntity { @Override public void spawnEntity() { + Vector3f javaPosition = position; + // Apply the offset if we're the second entity + position = position.up(getYOffset()); super.spawnEntity(); + position = javaPosition; } @Override @@ -101,22 +106,18 @@ public class ArmorStandEntity extends LivingEntity { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { - if (secondEntity != null) { - secondEntity.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); - } - super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + moveAbsolute(position.add(relX, relY, relZ), yaw, pitch, headYaw, onGround, false); } @Override public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { if (secondEntity != null) { - secondEntity.moveAbsolute(applyOffsetToPosition(position), yaw, pitch, headYaw, isOnGround, teleported); - } else if (positionRequiresOffset) { - // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - position = applyOffsetToPosition(position); + secondEntity.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported); } - - super.moveAbsolute(position, yaw, yaw, yaw, isOnGround, teleported); + // Fake the height to be above where it is so the nametag appears in the right location + float yOffset = getYOffset(); + super.moveAbsolute(yOffset != 0 ? position.up(yOffset) : position , yaw, yaw, yaw, isOnGround, teleported); + this.position = position; } @Override @@ -127,20 +128,14 @@ public class ArmorStandEntity extends LivingEntity { public void setArmorStandFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); - + boolean offsetChanged = false; // isSmall boolean newIsSmall = (xd & 0x01) == 0x01; if (newIsSmall != isSmall) { - if (positionRequiresOffset) { - // Fix new inconsistency with offset - this.position = fixOffsetForSize(position, newIsSmall); - positionUpdateRequired = true; - } - isSmall = newIsSmall; - if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780 - toggleSmallStatus(); - } + offsetChanged = true; + // Update the passenger offset as the armor stand's height has changed + updatePassengerOffsets(); } // setMarker @@ -150,12 +145,21 @@ public class ArmorStandEntity extends LivingEntity { if (isMarker) { setBoundingBoxWidth(0.0f); setBoundingBoxHeight(0.0f); - dirtyMetadata.put(EntityData.SCALE, 0f); } else { - toggleSmallStatus(); + setBoundingBoxWidth(definition.width()); + setBoundingBoxHeight(definition.height()); } updateMountOffset(); + offsetChanged = true; + } + + if (offsetChanged) { + if (positionRequiresOffset) { + positionUpdateRequired = true; + } else if (secondEntity != null) { + secondEntity.positionUpdateRequired = true; + } updateSecondEntityStatus(false); } @@ -163,6 +167,7 @@ public class ArmorStandEntity extends LivingEntity { // But if given a resource pack, then we can use these values to control armor stand visual properties setFlag(EntityFlag.ANGRY, (xd & 0x04) != 0x04); // Has arms setFlag(EntityFlag.ADMIRING, (xd & 0x08) == 0x08); // Has no baseplate + setFlag(EntityFlag.BABY, isSmall); // Is small (for setting head scale) } public void setHeadRotation(EntityMetadata entityMetadata) { @@ -226,7 +231,7 @@ public class ArmorStandEntity extends LivingEntity { super.updateBedrockMetadata(); if (positionUpdateRequired) { positionUpdateRequired = false; - updatePosition(); + moveAbsolute(position, yaw, pitch, headYaw, onGround, true); } } @@ -285,6 +290,13 @@ public class ArmorStandEntity extends LivingEntity { updateSecondEntityStatus(true); } + @Override + public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) { + super.setDisplayNameVisible(entityMetadata); + isNameTagVisible = entityMetadata.getPrimitiveValue(); + updateSecondEntityStatus(false); + } + /** * Determine if we need to load or unload the second entity. * @@ -293,40 +305,44 @@ public class ArmorStandEntity extends LivingEntity { private void updateSecondEntityStatus(boolean sendMetadata) { // A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows. if (!primaryEntity) return; - if (!isInvisible || isMarker) { - // It is either impossible to show armor, or the armor stand isn't invisible. We good. + if (!isInvisible) { + // The armor stand isn't invisible. We good. setFlag(EntityFlag.INVISIBLE, false); + dirtyMetadata.put(EntityData.SCALE, getScale()); updateOffsetRequirement(false); - if (positionUpdateRequired) { - positionUpdateRequired = false; - updatePosition(); - } if (secondEntity != null) { secondEntity.despawnEntity(); secondEntity = null; } + if (sendMetadata) { + this.updateBedrockMetadata(); + } return; } boolean isNametagEmpty = nametag.isEmpty(); if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR) || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) { - // If the second entity exists, no need to recreate it. - // We can't stuff this check above or else it'll fall into another else case and delete the second entity - if (secondEntity != null) return; + // Reset scale of the proper armor stand + this.dirtyMetadata.put(EntityData.SCALE, getScale()); + // Set the proper armor stand to invisible to show armor + setFlag(EntityFlag.INVISIBLE, true); + // Update the position of the armor stand + updateOffsetRequirement(false); - // Create the second entity. It doesn't need to worry about the items, but it does need to worry about - // the metadata as it will hold the name tag. - secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, - EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw()); - secondEntity.primaryEntity = false; - if (!this.positionRequiresOffset) { - // Ensure the offset is applied for the 0 scale - secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position); + if (secondEntity == null) { + // Create the second entity. It doesn't need to worry about the items, but it does need to worry about + // the metadata as it will hold the name tag. + secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, + EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw()); + secondEntity.primaryEntity = false; } // Copy metadata secondEntity.isSmall = isSmall; - //secondEntity.getDirtyMetadata().putAll(dirtyMetadata); //TODO check + secondEntity.isMarker = isMarker; + secondEntity.positionRequiresOffset = true; // Offset should always be applied + secondEntity.getDirtyMetadata().put(EntityData.NAMETAG, nametag); + secondEntity.getDirtyMetadata().put(EntityData.NAMETAG_ALWAYS_SHOW, isNameTagVisible ? (byte) 1 : (byte) 0); secondEntity.flags.merge(this.flags); // Guarantee this copy is NOT invisible secondEntity.setFlag(EntityFlag.INVISIBLE, false); @@ -335,18 +351,13 @@ public class ArmorStandEntity extends LivingEntity { // No bounding box as we don't want to interact with this entity secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - secondEntity.spawnEntity(); - - // Reset scale of the proper armor stand - this.dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); - // Set the proper armor stand to invisible to show armor - setFlag(EntityFlag.INVISIBLE, true); - // Update the position of the armor stand - updateOffsetRequirement(false); + if (!secondEntity.valid) { // Spawn the entity once + secondEntity.spawnEntity(); + } } else if (isNametagEmpty) { // We can just make an invisible entity // Reset scale of the proper armor stand - dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + dirtyMetadata.put(EntityData.SCALE, getScale()); // Set the proper armor stand to invisible to show armor setFlag(EntityFlag.INVISIBLE, true); // Update offset @@ -362,7 +373,7 @@ public class ArmorStandEntity extends LivingEntity { setFlag(EntityFlag.INVISIBLE, false); dirtyMetadata.put(EntityData.SCALE, 0.0f); // As the above is applied, we need an offset - updateOffsetRequirement(true); + updateOffsetRequirement(!isMarker); if (secondEntity != null) { secondEntity.despawnEntity(); @@ -374,35 +385,34 @@ public class ArmorStandEntity extends LivingEntity { } } - /** - * If this armor stand is not a marker, set its bounding box size and scale. - */ - private void toggleSmallStatus() { - setBoundingBoxWidth(isSmall ? 0.25f : definition.width()); - setBoundingBoxHeight(isSmall ? 0.9875f : definition.height()); - dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + @Override + public float getBoundingBoxWidth() { + // For consistency with getBoundingBoxHeight() + return super.getBoundingBoxWidth() * getScale(); + } + + @Override + public float getBoundingBoxHeight() { + // This is required so that EntityUtils#updateMountOffset() calculates the correct offset for small + // armor stands. The bounding box height is not changed as the SCALE entity data handles that for us. + return super.getBoundingBoxHeight() * getScale(); } /** - * @return the selected position with the position offset applied. + * @return the y offset required to position the name tag correctly */ - private Vector3f applyOffsetToPosition(Vector3f position) { - return position.add(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d); + public float getYOffset() { + if (!positionRequiresOffset || isMarker || secondEntity != null) { + return 0; + } + return definition.height() * getScale(); } /** - * @return an adjusted offset for the new small status. + * @return the scale according to Java */ - private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) { - position = removeOffsetFromPosition(position); - return position.add(0d, definition.height() * (isNowSmall ? 0.55d : 1d), 0d); - } - - /** - * @return the selected position with the position offset removed. - */ - private Vector3f removeOffsetFromPosition(Vector3f position) { - return position.sub(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d); + private float getScale() { + return isSmall ? 0.5f : 1f; } /** @@ -411,30 +421,12 @@ public class ArmorStandEntity extends LivingEntity { private void updateOffsetRequirement(boolean newValue) { if (newValue != positionRequiresOffset) { this.positionRequiresOffset = newValue; - if (positionRequiresOffset) { - this.position = applyOffsetToPosition(position); - // Update the passenger offset as armorstand is moving up by roughly 2 blocks - updatePassengerOffsets(); - } else { - this.position = removeOffsetFromPosition(position); - } - positionUpdateRequired = true; + this.positionUpdateRequired = true; + // Update the passenger offset as the armor stand's y offset has changed + updatePassengerOffsets(); } } - /** - * Updates position without calling movement code. - */ - private void updatePosition() { - MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); - moveEntityPacket.setRuntimeEntityId(geyserId); - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - moveEntityPacket.setOnGround(isOnGround()); - moveEntityPacket.setTeleported(false); - session.sendUpstreamPacket(moveEntityPacket); - } - @Override public Vector3f getBedrockRotation() { return Vector3f.from(getYaw(), getYaw(), getYaw()); 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 new file mode 100644 index 000000000..408e2ec21 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java @@ -0,0 +1,70 @@ +/* + * 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.entity.type.living.animal.horse; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class CamelEntity extends AbstractHorseEntity { + + private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F; + + 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); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + this.dirtyMetadata.put(EntityData.VARIANT, 2); // Closest llama colour to camel + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return "cactus".equals(javaIdentifierStripped); + } + + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.SITTING) { + setBoundingBoxWidth(definition.height() - SITTING_HEIGHT_DIFFERENCE); + setBoundingBoxWidth(definition.width()); + } else { + super.setDimensions(pose); + } + } + + public void setDashing(BooleanEntityMetadata entityMetadata) { + + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java index 03492d518..04b46997d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java @@ -25,8 +25,9 @@ package org.geysermc.geyser.entity.type.living.monster; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.OptionalIntMetadataType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -35,6 +36,7 @@ import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import java.util.OptionalInt; import java.util.UUID; public class EndermanEntity extends MonsterEntity { @@ -43,8 +45,15 @@ public class EndermanEntity extends MonsterEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - public void setCarriedBlock(IntEntityMetadata entityMetadata) { - dirtyMetadata.put(EntityData.CARRIED_BLOCK, session.getBlockMappings().getBedrockBlockId(entityMetadata.getPrimitiveValue())); + public void setCarriedBlock(EntityMetadata entityMetadata) { + int bedrockBlockId; + if (entityMetadata.getValue().isPresent()) { + bedrockBlockId = session.getBlockMappings().getBedrockBlockId(entityMetadata.getValue().getAsInt()); + } else { + bedrockBlockId = session.getBlockMappings().getBedrockAirId(); + } + + dirtyMetadata.put(EntityData.CARRIED_BLOCK, bedrockBlockId); } /** 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 f16f46e2e..74b95b73c 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 @@ -73,7 +73,7 @@ public class SessionPlayerEntity extends PlayerEntity { private int fakeTradeXp; public SessionPlayerEntity(GeyserSession session) { - super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null); + super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null); valid = true; } @@ -120,6 +120,16 @@ public class SessionPlayerEntity extends PlayerEntity { refreshSpeed = true; } + /** + * Since 1.19.40, the client must be re-informed of its bounding box on respawn + * See https://github.com/GeyserMC/Geyser/issues/3370 + */ + public void updateBoundingBox() { + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, getBoundingBoxHeight()); + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, getBoundingBoxWidth()); + updateBedrockMetadata(); + } + @Override public boolean setBoundingBoxHeight(float height) { if (super.setBoundingBoxHeight(height)) { diff --git a/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java deleted file mode 100644 index 69dc74935..000000000 --- a/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java +++ /dev/null @@ -1,68 +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.event; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; -import net.kyori.event.EventSubscriber; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; -import org.geysermc.geyser.api.event.Subscribe; -import org.geysermc.geyser.api.extension.Extension; - -@Getter -@Accessors(fluent = true) -@RequiredArgsConstructor -public abstract class AbstractEventSubscription implements EventSubscription, EventSubscriber { - protected final EventBus eventBus; - protected final Class eventClass; - protected final Extension owner; - protected final Subscribe.PostOrder order; - @Getter(AccessLevel.NONE) private boolean active; - - @Override - public boolean isActive() { - return this.active; - } - - @Override - public void unsubscribe() { - if (!this.active) { - return; - } - - this.active = false; - this.eventBus.unsubscribe(this); - } - - @Override - public int postOrder() { - return this.order.postOrder(); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java deleted file mode 100644 index b1ba7bf8b..000000000 --- a/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java +++ /dev/null @@ -1,60 +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.event; - -import lombok.Getter; -import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.Subscribe; -import org.geysermc.geyser.api.extension.Extension; - -import java.util.function.BiConsumer; - -@Getter -@Accessors(fluent = true) -public class GeneratedEventSubscription extends AbstractEventSubscription { - private final Object eventHolder; - private final BiConsumer eventConsumer; - - public GeneratedEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Object eventHolder, BiConsumer eventConsumer) { - super(eventBus, eventClass, owner, order); - - this.eventHolder = eventHolder; - this.eventConsumer = eventConsumer; - } - - @Override - public void invoke(@NonNull T event) throws Throwable { - try { - this.eventConsumer.accept(this.eventHolder, event); - } catch (Throwable ex) { - this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); - ex.printStackTrace(); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index 60e354ac9..9593e327e 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -25,97 +25,48 @@ package org.geysermc.geyser.event; -import net.kyori.event.EventSubscriber; -import net.kyori.event.SimpleEventBus; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.event.bus.impl.OwnedEventBusImpl; +import org.geysermc.event.subscribe.OwnedSubscriber; +import org.geysermc.event.subscribe.Subscribe; import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; -import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.event.EventSubscriber; import org.geysermc.geyser.api.extension.Extension; -import org.lanternpowered.lmbda.LambdaFactory; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; -public class GeyserEventBus implements EventBus { - private static final MethodHandles.Lookup CALLER = MethodHandles.lookup(); +@SuppressWarnings("unchecked") +public final class GeyserEventBus extends OwnedEventBusImpl> + implements EventBus { + @Override + protected > B makeSubscription( + @NonNull EventRegistrar owner, + @NonNull Class eventClass, + @NonNull Subscribe subscribe, + @NonNull L listener, + @NonNull BiConsumer handler) { + return (B) new GeyserEventSubscriber<>( + owner, eventClass, subscribe.postOrder(), subscribe.ignoreCancelled(), listener, handler + ); + } - private final SimpleEventBus bus = new SimpleEventBus<>(Event.class); + @Override + protected > B makeSubscription( + @NonNull EventRegistrar owner, + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder) { + return (B) new GeyserEventSubscriber<>(owner, eventClass, handler, postOrder); + } + @Override @NonNull - @Override - public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { - return this.subscribe(eventClass, consumer, extension, Subscribe.PostOrder.NORMAL); - } - - @Override - public void unsubscribe(@NonNull EventSubscription subscription) { - this.bus.unregister((AbstractEventSubscription) subscription); - } - - @SuppressWarnings("unchecked") - @Override - public void register(@NonNull Extension extension, @NonNull Object eventHolder) { - for (Method method : eventHolder.getClass().getMethods()) { - if (!method.isAnnotationPresent(Subscribe.class)) { - continue; - } - - if (method.getParameterCount() > 1) { - continue; - } - - if (!Event.class.isAssignableFrom(method.getParameters()[0].getType())) { - continue; - } - - Subscribe subscribe = method.getAnnotation(Subscribe.class); - - try { - Class type = (Class) method.getParameters()[0].getType(); - this.subscribe(type, eventHolder, LambdaFactory.createBiConsumer(CALLER.unreflect(method)), extension, subscribe.postOrder()); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } - } - } - - @Override - public void unregisterAll(@NonNull Extension extension) { - this.bus.unregister((Predicate>) subscriber -> extension.equals(((AbstractEventSubscription) subscriber).owner())); - } - - @Override - public boolean fire(@NonNull Event event) { - return this.bus.post(event).wasSuccessful(); - } - - @SuppressWarnings("unchecked") - @NonNull - @Override - public Set> subscriptions(@NonNull Class eventClass) { - return bus.subscribers().values() - .stream() - .filter(sub -> sub instanceof EventSubscription && ((EventSubscription) sub).eventClass().isAssignableFrom(eventClass)) - .map(sub -> ((EventSubscription) sub)) - .collect(Collectors.toSet()); - } - - private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.PostOrder postOrder) { - BaseEventSubscription eventSubscription = new BaseEventSubscription<>(this, eventClass, extension, postOrder, handler); - this.bus.register(eventClass, eventSubscription); - return eventSubscription; - } - - private EventSubscription subscribe(Class eventClass, Object eventHolder, BiConsumer handler, Extension extension, Subscribe.PostOrder postOrder) { - GeneratedEventSubscription eventSubscription = new GeneratedEventSubscription<>(this, eventClass, extension, postOrder, eventHolder, handler); - this.bus.register(eventClass, eventSubscription); - return eventSubscription; + public Set> subscribers(@NonNull Class eventClass) { + return castGenericSet(super.subscribers(eventClass)); } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java similarity index 75% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java rename to core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java index 94d0b832d..85c36a132 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java @@ -23,24 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.event; +package org.geysermc.geyser.event; -/** - * Represents a cancellable event. - */ -public interface Cancellable { +import org.geysermc.geyser.api.event.EventRegistrar; - /** - * Gets if the event is cancelled. - * - * @return if the event is cancelled - */ - boolean isCancelled(); +public record GeyserEventRegistrar(Object owner) implements EventRegistrar { - /** - * Cancels the event. - * - * @param cancelled if the event is cancelled - */ - void setCancelled(boolean cancelled); + @Override + public String toString() { + return "GeyserEventRegistrar{" + + "owner=" + this.owner + + '}'; + } } diff --git a/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java similarity index 57% rename from core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java rename to core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java index 79df94e5d..d33de8cdd 100644 --- a/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java @@ -25,34 +25,34 @@ package org.geysermc.geyser.event; -import lombok.Getter; -import lombok.experimental.Accessors; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.event.subscribe.impl.OwnedSubscriberImpl; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.event.ExtensionEventSubscriber; import org.geysermc.geyser.api.extension.Extension; +import java.util.function.BiConsumer; import java.util.function.Consumer; -@Getter -@Accessors(fluent = true) -public class BaseEventSubscription extends AbstractEventSubscription { - private final Consumer eventConsumer; - - public BaseEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Consumer eventConsumer) { - super(eventBus, eventClass, owner, order); - - this.eventConsumer = eventConsumer; +public final class GeyserEventSubscriber extends OwnedSubscriberImpl + implements ExtensionEventSubscriber { + GeyserEventSubscriber( + @NonNull R owner, + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder) { + super(owner, eventClass, handler, postOrder); } - @Override - public void invoke(@NonNull T event) throws Throwable { - try { - this.eventConsumer.accept(event); - } catch (Throwable ex) { - this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); - ex.printStackTrace(); - } + GeyserEventSubscriber( + @NonNull R owner, + @NonNull Class eventClass, + @NonNull PostOrder postOrder, + boolean ignoreCancelled, + @NonNull H handlerInstance, + @NonNull BiConsumer handler) { + super(owner, eventClass, postOrder, ignoreCancelled, handlerInstance, handler); } } 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 new file mode 100644 index 000000000..e07a62d8a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java @@ -0,0 +1,46 @@ +/* + * 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.event.type; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; + +import java.util.Collections; +import java.util.Map; + +public abstract class GeyserDefineCommandsEventImpl implements GeyserDefineCommandsEvent { + private final Map commands; + + public GeyserDefineCommandsEventImpl(Map commands) { + this.commands = commands; + } + + @Override + public @NonNull Map commands() { + return Collections.unmodifiableMap(this.commands); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java new file mode 100644 index 000000000..65fd7ea0d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java @@ -0,0 +1,85 @@ +/* + * 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.event.type; + +import com.google.common.collect.Multimap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public abstract class GeyserDefineCustomItemsEventImpl implements GeyserDefineCustomItemsEvent { + private final Multimap customItems; + private final List nonVanillaCustomItems; + + public GeyserDefineCustomItemsEventImpl(Multimap customItems, List nonVanillaCustomItems) { + this.customItems = customItems; + this.nonVanillaCustomItems = nonVanillaCustomItems; + } + + /** + * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. + * + * @return a multimap of all the already registered custom items + */ + @Override + public Map> getExistingCustomItems() { + return Collections.unmodifiableMap(this.customItems.asMap()); + } + + /** + * Gets the list of the already registered non-vanilla custom items. + * + * @return the list of the already registered non-vanilla custom items + */ + @Override + public List getExistingNonVanillaCustomItems() { + return Collections.unmodifiableList(this.nonVanillaCustomItems); + } + + /** + * Registers a custom item with a base Java item. This is used to register items with custom textures and properties + * based on NBT data. + * + * @param identifier the base (java) item + * @param customItemData the custom item data to register + * @return if the item was registered + */ + public abstract boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); + + /** + * Registers a custom item with no base item. This is used for mods. + * + * @param customItemData the custom item data to register + * @return if the item was registered + */ + public abstract boolean register(@NonNull NonVanillaCustomItemData customItemData); +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index b220ab576..b94e70ed0 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; @@ -39,7 +39,7 @@ import java.nio.file.Path; public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; - private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); + private final Object2ObjectMap> classes = new Object2ObjectOpenHashMap<>(); public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path) throws MalformedURLException { super(new URL[] { path.toUri().toURL() }, parent); 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 eaf29a819..716b763f5 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,53 +25,95 @@ package org.geysermc.geyser.extension; +import lombok.Getter; +import lombok.Setter; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; -import org.yaml.snakeyaml.DumperOptions; +import org.geysermc.geyser.text.GeyserLocale; import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; import java.io.Reader; import java.util.*; +import java.util.function.Supplier; +import java.util.regex.Pattern; -public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { - @SuppressWarnings("unchecked") +public record GeyserExtensionDescription(@NonNull String id, + @NonNull String name, + @NonNull String main, + int majorApiVersion, + int minorApiVersion, + int patchApiVersion, + @NonNull String version, + @NonNull List authors) implements ExtensionDescription { + + private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader())); + + public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}"); + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]+$"); + public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); + + @NonNull public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { - DumperOptions dumperOptions = new DumperOptions(); - dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - Yaml yaml = new Yaml(dumperOptions); - Map yamlMap = yaml.loadAs(reader, LinkedHashMap.class); - - String name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (name.isBlank()) { - throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); + Source source; + try { + source = YAML.loadAs(reader, Source.class); + } catch (Exception e) { + throw new InvalidDescriptionException(e); } - name = name.replace(" ", "_"); - String version = String.valueOf(yamlMap.get("version")); - String main = (String) yamlMap.get("main"); - String apiVersion; - - Object api = yamlMap.get("api"); - if (api instanceof String) { - apiVersion = (String) api; - } else { - throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); + String id = require(source::getId, "id"); + if (!ID_PATTERN.matcher(id).matches()) { + throw new InvalidDescriptionException("Invalid extension id, must match: " + ID_PATTERN.pattern()); } + String name = require(source::getName, "name"); + if (!NAME_PATTERN.matcher(name).matches()) { + throw new InvalidDescriptionException("Invalid extension name, must match: " + NAME_PATTERN.pattern()); + } + + String version = String.valueOf(source.version); + String main = require(source::getMain, "main"); + + String apiVersion = require(source::getApi, "api"); + if (!API_VERSION_PATTERN.matcher(apiVersion).matches()) { + 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]); + List authors = new ArrayList<>(); - if (yamlMap.containsKey("author")) { - authors.add((String) yamlMap.get("author")); + if (source.author != null) { + authors.add(source.author); + } + if (source.authors != null) { + authors.addAll(source.authors); } - if (yamlMap.containsKey("authors")) { - try { - authors.addAll((Collection) yamlMap.get("authors")); - } catch (Exception e) { - throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); - } - } + return new GeyserExtensionDescription(id, name, main, majorApi, minorApi, patchApi, version, authors); + } - return new GeyserExtensionDescription(name, main, apiVersion, version, authors); + @NonNull + private static String require(Supplier supplier, String name) throws InvalidDescriptionException { + String value = supplier.get(); + if (value == null) { + throw new InvalidDescriptionException("Extension description is missing string property '" + name + "'"); + } + return value; + } + + @Getter + @Setter + public static class Source { + String id; + String name; + String main; + String api; + String version; + String author; + List authors; } } 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 55f018ddb..7e998e413 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +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; @@ -39,25 +39,24 @@ import org.geysermc.geyser.extension.event.GeyserExtensionEventBus; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; +import java.io.Reader; import java.nio.file.*; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Stream; @RequiredArgsConstructor public class GeyserExtensionLoader extends ExtensionLoader { - private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); - private static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; - private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); + private final Object2ObjectMap> classes = new Object2ObjectOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); + private final Path extensionsDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("extensions"); - public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException, InvalidDescriptionException { + public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException { if (path == null) { throw new InvalidExtensionException("Path is null"); } @@ -94,7 +93,9 @@ public class GeyserExtensionLoader extends ExtensionLoader { Map environment = new HashMap<>(); try (FileSystem fileSystem = FileSystems.newFileSystem(path, environment, null)) { Path extensionYml = fileSystem.getPath("extension.yml"); - return GeyserExtensionDescription.fromYaml(Files.newBufferedReader(extensionYml)); + try (Reader reader = Files.newBufferedReader(extensionYml)) { + return GeyserExtensionDescription.fromYaml(reader); + } } catch (IOException ex) { throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); } @@ -127,15 +128,15 @@ public class GeyserExtensionLoader extends ExtensionLoader { @Override protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { try { - if (Files.notExists(EXTENSION_DIRECTORY)) { - Files.createDirectory(EXTENSION_DIRECTORY); + if (Files.notExists(extensionsDirectory)) { + Files.createDirectory(extensionsDirectory); } Map extensions = new LinkedHashMap<>(); Map loadedExtensions = new LinkedHashMap<>(); Pattern[] extensionFilters = this.extensionFilters(); - try (Stream entries = Files.walk(EXTENSION_DIRECTORY)) { + try (Stream entries = Files.walk(extensionsDirectory)) { entries.forEach(path -> { if (Files.isDirectory(path)) { return; @@ -149,9 +150,6 @@ public class GeyserExtensionLoader extends ExtensionLoader { try { GeyserExtensionDescription description = this.extensionDescription(path); - if (description == null) { - return; - } String name = description.name(); if (extensions.containsKey(name) || extensionManager.extension(name) != null) { @@ -159,30 +157,15 @@ public class GeyserExtensionLoader extends ExtensionLoader { return; } - int majorVersion = Geyser.api().majorApiVersion(); - int minorVersion = Geyser.api().minorApiVersion(); - - try { - // Check the format: majorVersion.minorVersion.patch - if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, majorVersion + "." + minorVersion)); - return; - } - - String[] versionArray = description.apiVersion().split("\\."); - // Completely different API version - if (Integer.parseInt(versionArray[0]) != majorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.majorApiVersion() != Geyser.api().majorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } // If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > minorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.minorApiVersion() > Geyser.api().minorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 7d80c2cf6..5dd924301 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; @@ -39,34 +37,23 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Collectors; public class GeyserExtensionManager extends ExtensionManager { - private static final Key BASE_EXTENSION_LOADER_KEY = Key.key("geysermc", "base"); - - private final Map extensionLoaderTypes = new Object2ObjectOpenHashMap<>(); - + private final GeyserExtensionLoader extensionLoader = new GeyserExtensionLoader(); private final Map extensions = new LinkedHashMap<>(); - private final Map extensionsLoaders = new LinkedHashMap<>(); public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - extensionLoaderTypes.put(BASE_EXTENSION_LOADER_KEY, new GeyserExtensionLoader()); - for (ExtensionLoader loader : this.extensionLoaders().values()) { - this.loadAllExtensions(loader); - } + loadAllExtensions(this.extensionLoader); + enableExtensions(); GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); } @Override public Extension extension(@NonNull String name) { - if (this.extensions.containsKey(name)) { - return this.extensions.get(name); - } - - return null; + return this.extensions.get(name); } @Override @@ -121,37 +108,19 @@ public class GeyserExtensionManager extends ExtensionManager { } } - @Override - public ExtensionLoader extensionLoader(@NonNull Extension extension) { - return this.extensionsLoaders.get(extension); - } - @NonNull @Override public Collection extensions() { return Collections.unmodifiableCollection(this.extensions.values()); } - @Nullable @Override - public ExtensionLoader extensionLoader(@NonNull String identifier) { - return this.extensionLoaderTypes.get(Key.key(identifier)); + public @Nullable ExtensionLoader extensionLoader() { + return this.extensionLoader; } @Override - public void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader) { - this.extensionLoaderTypes.put(Key.key(identifier), extensionLoader); - } - - @NonNull - @Override - public Map extensionLoaders() { - return this.extensionLoaderTypes.entrySet().stream().collect(Collectors.toMap(key -> key.getKey().asString(), Map.Entry::getValue)); - } - - @Override - public void register(@NonNull Extension extension, @NonNull ExtensionLoader loader) { - this.extensionsLoaders.put(extension, loader); + public void register(@NonNull Extension extension) { this.extensions.put(extension.name(), extension); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java similarity index 70% rename from bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java rename to core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 6f9faba8f..4a7830c90 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -23,19 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.velocity.command; +package org.geysermc.geyser.extension.command; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.GeyserCommand; -public class GeyserVelocityCommandManager extends GeyserCommandManager { +public abstract class GeyserExtensionCommand extends GeyserCommand { + private final Extension extension; - public GeyserVelocityCommandManager(GeyserImpl geyser) { - super(geyser); + public GeyserExtensionCommand(Extension extension, String name, String description, String permission) { + super(name, description, permission); + + this.extension = extension; } - @Override - public String description(String command) { - return ""; // no support for command descriptions in velocity + public Extension extension() { + return this.extension; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java index 4104871fa..f56b254a6 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -26,62 +26,60 @@ package org.geysermc.geyser.extension.event; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.event.subscribe.Subscriber; import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.event.EventSubscriber; import org.geysermc.geyser.api.event.ExtensionEventBus; import org.geysermc.geyser.api.extension.Extension; import java.util.Set; import java.util.function.Consumer; -public record GeyserExtensionEventBus(EventBus eventBus, - Extension extension) implements ExtensionEventBus { - @NonNull - @Override - public EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer) { - return this.eventBus.subscribe(this.extension, eventClass, consumer); - } +public record GeyserExtensionEventBus(EventBus eventBus, Extension extension) implements ExtensionEventBus { + @SuppressWarnings({"rawtypes", "unchecked"}) @Override - public void register(@NonNull Object eventHolder) { - this.eventBus.register(this.extension, eventHolder); - } - - @Override - public void unregisterAll() { - this.eventBus.unregisterAll(this.extension); - } - - @NonNull - @Override - public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { - return this.eventBus.subscribe(extension, eventClass, consumer); - } - - @Override - public void unsubscribe(@NonNull EventSubscription subscription) { - this.eventBus.unsubscribe(subscription); - } - - @Override - public void register(@NonNull Extension extension, @NonNull Object eventHolder) { - this.eventBus.register(extension, eventHolder); - } - - @Override - public void unregisterAll(@NonNull Extension extension) { - this.eventBus.unregisterAll(extension); + public void unsubscribe(@NonNull EventSubscriber subscription) { + eventBus.unsubscribe((EventSubscriber) subscription); } @Override public boolean fire(@NonNull Event event) { - return this.eventBus.fire(event); + return eventBus.fire(event); } - @NonNull @Override - public Set> subscriptions(@NonNull Class eventClass) { - return this.eventBus.subscriptions(eventClass); + public @NonNull Set> subscribers(@NonNull Class eventClass) { + return eventBus.subscribers(eventClass); + } + + @Override + public void register(@NonNull Object listener) { + eventBus.register(extension, listener); + } + + @Override + @SuppressWarnings("unchecked") + public > @NonNull U subscribe( + @NonNull Class eventClass, @NonNull Consumer consumer) { + return eventBus.subscribe(extension, eventClass, consumer); + } + + @Override + @SuppressWarnings("unchecked") + public > @NonNull U subscribe( + @NonNull Class eventClass, + @NonNull Consumer consumer, + @NonNull PostOrder postOrder + ) { + return eventBus.subscribe(extension, eventClass, consumer, postOrder); + } + + @Override + public void unregisterAll() { + eventBus.unregisterAll(extension); } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java index 141f2b6f2..471aff8b2 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -76,7 +76,7 @@ public class AnvilContainer extends Container { String originalName = ItemUtils.getCustomName(getInput().getNbt()); String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale()); - String plainNewName = MessageTranslator.convertToPlainText(rename, session.locale()); + String plainNewName = MessageTranslator.convertToPlainText(rename); if (!plainOriginalName.equals(plainNewName)) { // Strip out formatting since Java Edition does not allow it correctRename = plainNewName; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java index 58d551489..f01d242ad 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -44,7 +44,7 @@ import java.util.Arrays; @ToString public abstract class Inventory { @Getter - protected final int id; + protected final int javaId; /** * The Java inventory state ID from the server. As of Java Edition 1.18.1 this value has one instance per player. @@ -93,15 +93,23 @@ public abstract class Inventory { this("Inventory", id, size, containerType); } - protected Inventory(String title, int id, int size, ContainerType containerType) { + protected Inventory(String title, int javaId, int size, ContainerType containerType) { this.title = title; - this.id = id; + this.javaId = javaId; this.size = size; this.containerType = containerType; this.items = new GeyserItemStack[size]; Arrays.fill(items, GeyserItemStack.EMPTY); } + // This is to prevent conflicts with special bedrock inventory IDs. + // The vanilla java server only sends an ID between 1 and 100 when opening an inventory, + // so this is rarely needed. (certain plugins) + // Example: https://github.com/GeyserMC/Geyser/issues/3254 + public int getBedrockId() { + return javaId <= 100 ? javaId : (javaId % 100) + 1; + } + public GeyserItemStack getItem(int slot) { if (slot > this.size) { GeyserImpl.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this); 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 ec36645da..bfe5a7d9d 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 @@ -30,10 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.inventory.MoveToHotbarAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; -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.ints.*; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.SlotType; @@ -124,12 +121,14 @@ public final class ClickPlan { } ItemStack clickedItemStack; - if (!planIter.hasNext() && refresh) { - clickedItemStack = InventoryUtils.REFRESH_ITEM; + if (emulatePost1_16Logic) { + // The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) + clickedItemStack = simulatedCursor.getItemStack(); } else { - if (emulatePost1_16Logic) { - // The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) - clickedItemStack = simulatedCursor.getItemStack(); + if (!planIter.hasNext() && refresh) { + // Doesn't have the intended effect with state IDs since this won't cause a complete window refresh + // (It will eventually once state IDs desync, but this causes more problems than not) + clickedItemStack = InventoryUtils.REFRESH_ITEM; } else { if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { clickedItemStack = null; @@ -144,7 +143,7 @@ public final class ClickPlan { } ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( - inventory.getId(), + inventory.getJavaId(), stateId, action.slot, action.click.actionType, diff --git a/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java index fd26cc170..3e0892be4 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.inventory.holder; -import com.google.common.collect.ImmutableSet; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; @@ -39,6 +38,7 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.InventoryUtils; import java.util.Collections; import java.util.HashSet; @@ -63,14 +63,14 @@ public class BlockInventoryHolder extends InventoryHolder { Set validBlocksTemp = new HashSet<>(validBlocks.length + 1); Collections.addAll(validBlocksTemp, validBlocks); validBlocksTemp.add(BlockUtils.getCleanIdentifier(javaBlockIdentifier)); - this.validBlocks = ImmutableSet.copyOf(validBlocksTemp); + this.validBlocks = Set.copyOf(validBlocksTemp); } else { this.validBlocks = Collections.singleton(BlockUtils.getCleanIdentifier(javaBlockIdentifier)); } } @Override - public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + public boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { // Check to see if there is an existing block we can use that the player just selected. // First, verify that the player's position has not changed, so we don't try to select a block wildly out of range. // (This could be a virtual inventory that the player is opening) @@ -83,13 +83,16 @@ public class BlockInventoryHolder extends InventoryHolder { inventory.setHolderPosition(session.getLastInteractionBlockPosition()); ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId); - return; + + return true; } } - // Otherwise, time to conjure up a fake block! - Vector3i position = session.getPlayerEntity().getPosition().toInt(); - position = position.add(Vector3i.UP); + Vector3i position = InventoryUtils.findAvailableWorldSpace(session); + if (position == null) { + return false; + } + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); @@ -99,6 +102,8 @@ public class BlockInventoryHolder extends InventoryHolder { inventory.setHolderPosition(position); setCustomName(session, position, inventory, defaultJavaBlockState); + + return true; } /** @@ -133,7 +138,7 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) inventory.getId()); + containerOpenPacket.setId((byte) inventory.getBedrockId()); containerOpenPacket.setType(containerType); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); @@ -146,7 +151,7 @@ public class BlockInventoryHolder extends InventoryHolder { // No need to reset a block since we didn't change any blocks // But send a container close packet because we aren't destroying the original. ContainerClosePacket packet = new ContainerClosePacket(); - packet.setId((byte) inventory.getId()); + packet.setId((byte) inventory.getBedrockId()); packet.setUnknownBool0(true); //TODO needs to be changed in Protocol to "server-side" or something session.sendUpstreamPacket(packet); return; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java index fe54e1dc0..986df53db 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java @@ -30,7 +30,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; public abstract class InventoryHolder { - public abstract void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); + public abstract boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); } 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 8f9eb415f..b50a9f7d5 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 @@ -38,6 +38,7 @@ import java.util.Map; @Getter @Accessors(fluent = true) public class StoredItemMappings { + private final int amethystShard; private final ItemMapping bamboo; private final ItemMapping banner; private final ItemMapping barrier; @@ -71,6 +72,7 @@ public class StoredItemMappings { private final ItemMapping writableBook; public StoredItemMappings(Map itemMappings) { + this.amethystShard = load(itemMappings, "amethyst_shard").getJavaId(); this.bamboo = load(itemMappings, "bamboo"); this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID this.barrier = load(itemMappings, "barrier"); 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 fd25b5907..8a174c2c5 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 @@ -59,7 +59,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { } InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); + contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(bedrockItems); session.sendUpstreamPacket(contentPacket); } @@ -70,7 +70,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { return true; InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(inventory.getId()); + slotPacket.setContainerId(inventory.getBedrockId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); 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 705a8b242..c943a62b4 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 @@ -47,7 +47,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { } InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); + contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(Arrays.asList(bedrockItems)); session.sendUpstreamPacket(contentPacket); } @@ -58,7 +58,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { return true; InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(inventory.getId()); + slotPacket.setContainerId(inventory.getBedrockId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); 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 fa680c201..20ce7e467 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 @@ -47,7 +47,7 @@ public class HorseInventoryUpdater extends InventoryUpdater { } InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); + contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(Arrays.asList(bedrockItems)); session.sendUpstreamPacket(contentPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java index 9f1212875..9bb317996 100644 --- a/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java @@ -33,7 +33,7 @@ public enum BedrockMapIcon { ICON_ITEM_FRAME(MapIconType.GREEN_ARROW, 7), ICON_RED_ARROW(MapIconType.RED_ARROW, 2), ICON_BLUE_ARROW(MapIconType.BLUE_ARROW, 3), - ICON_TREASURE_MARKER(MapIconType.TREASURE_MARKER, 4), + ICON_WHITE_CROSS(MapIconType.WHITE_CROSS, 4, 0, 0, 0), // Doesn't exist on Bedrock, replaced with a black cross ICON_RED_POINTER(MapIconType.RED_POINTER, 5), ICON_WHITE_CIRCLE(MapIconType.WHITE_CIRCLE, 6), ICON_SMALL_WHITE_CIRCLE(MapIconType.SMALL_WHITE_CIRCLE, 13), @@ -54,7 +54,8 @@ public enum BedrockMapIcon { ICON_BROWN_BANNER(MapIconType.BROWN_BANNER, 13, 131, 84, 50), ICON_GREEN_BANNER(MapIconType.GREEN_BANNER, 13, 94, 124, 22), ICON_RED_BANNER(MapIconType.RED_BANNER, 13, 176, 46, 38), - ICON_BLACK_BANNER(MapIconType.BLACK_BANNER, 13, 29, 29, 33); + ICON_BLACK_BANNER(MapIconType.BLACK_BANNER, 13, 29, 29, 33), + ICON_TREASURE_MARKER(MapIconType.TREASURE_MARKER, 4); private static final BedrockMapIcon[] VALUES = values(); diff --git a/core/src/main/java/org/geysermc/geyser/level/GameRule.java b/core/src/main/java/org/geysermc/geyser/level/GameRule.java index be647cff6..015f9c50c 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GameRule.java +++ b/core/src/main/java/org/geysermc/geyser/level/GameRule.java @@ -32,43 +32,41 @@ import lombok.Getter; * It is used to construct the list for the settings menu */ public enum GameRule { - ANNOUNCEADVANCEMENTS("announceAdvancements", Boolean.class, true), // JE only - COMMANDBLOCKOUTPUT("commandBlockOutput", Boolean.class, true), - DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", Boolean.class, false), // JE only - DISABLERAIDS("disableRaids", Boolean.class, false), // JE only - DODAYLIGHTCYCLE("doDaylightCycle", Boolean.class, true), - DOENTITYDROPS("doEntityDrops", Boolean.class, true), - DOFIRETICK("doFireTick", Boolean.class, true), - DOIMMEDIATERESPAWN("doImmediateRespawn", Boolean.class, false), - DOINSOMNIA("doInsomnia", Boolean.class, true), - DOLIMITEDCRAFTING("doLimitedCrafting", Boolean.class, false), // JE only - DOMOBLOOT("doMobLoot", Boolean.class, true), - DOMOBSPAWNING("doMobSpawning", Boolean.class, true), - DOPATROLSPAWNING("doPatrolSpawning", Boolean.class, true), // JE only - DOTILEDROPS("doTileDrops", Boolean.class, true), - DOTRADERSPAWNING("doTraderSpawning", Boolean.class, true), // JE only - DOWEATHERCYCLE("doWeatherCycle", Boolean.class, true), - DROWNINGDAMAGE("drowningDamage", Boolean.class, true), - FALLDAMAGE("fallDamage", Boolean.class, true), - FIREDAMAGE("fireDamage", Boolean.class, true), - FREEZEDAMAGE("freezeDamage", Boolean.class, true), - FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only - KEEPINVENTORY("keepInventory", Boolean.class, false), - LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only - MAXCOMMANDCHAINLENGTH("maxCommandChainLength", Integer.class, 65536), - MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only - MOBGRIEFING("mobGriefing", Boolean.class, true), - NATURALREGENERATION("naturalRegeneration", Boolean.class, true), - PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", Integer.class, 100), // JE only - RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3), - REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only - SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true), - SHOWDEATHMESSAGES("showDeathMessages", Boolean.class, true), - SPAWNRADIUS("spawnRadius", Integer.class, 10), - SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", Boolean.class, true), // JE only - UNIVERSALANGER("universalAnger", Boolean.class, false), // JE only - - UNKNOWN("unknown", Object.class); + ANNOUNCEADVANCEMENTS("announceAdvancements", true), // JE only + COMMANDBLOCKOUTPUT("commandBlockOutput", true), + DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", false), // JE only + DISABLERAIDS("disableRaids", false), // JE only + DODAYLIGHTCYCLE("doDaylightCycle", true), + DOENTITYDROPS("doEntityDrops", true), + DOFIRETICK("doFireTick", true), + DOIMMEDIATERESPAWN("doImmediateRespawn", false), + DOINSOMNIA("doInsomnia", true), + DOLIMITEDCRAFTING("doLimitedCrafting", false), // JE only + DOMOBLOOT("doMobLoot", true), + DOMOBSPAWNING("doMobSpawning", true), + DOPATROLSPAWNING("doPatrolSpawning", true), // JE only + DOTILEDROPS("doTileDrops", true), + DOTRADERSPAWNING("doTraderSpawning", true), // JE only + DOWEATHERCYCLE("doWeatherCycle", true), + DROWNINGDAMAGE("drowningDamage", true), + FALLDAMAGE("fallDamage", true), + FIREDAMAGE("fireDamage", true), + FREEZEDAMAGE("freezeDamage", true), + FORGIVEDEADPLAYERS("forgiveDeadPlayers", true), // JE only + KEEPINVENTORY("keepInventory", false), + LOGADMINCOMMANDS("logAdminCommands", true), // JE only + MAXCOMMANDCHAINLENGTH("maxCommandChainLength", 65536), + MAXENTITYCRAMMING("maxEntityCramming", 24), // JE only + MOBGRIEFING("mobGriefing", true), + NATURALREGENERATION("naturalRegeneration", true), + PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", 100), // JE only + RANDOMTICKSPEED("randomTickSpeed", 3), + REDUCEDDEBUGINFO("reducedDebugInfo", false), // JE only + SENDCOMMANDFEEDBACK("sendCommandFeedback", true), + SHOWDEATHMESSAGES("showDeathMessages", true), + SPAWNRADIUS("spawnRadius", 10), + SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", true), // JE only + UNIVERSALANGER("universalAnger", false); // JE only public static final GameRule[] VALUES = values(); @@ -78,48 +76,25 @@ public enum GameRule { @Getter private final Class type; - @Getter - private final Object defaultValue; + private final int defaultValue; - GameRule(String javaID, Class type) { - this(javaID, type, null); + GameRule(String javaID, boolean defaultValue) { + this.javaID = javaID; + this.type = Boolean.class; + this.defaultValue = defaultValue ? 1 : 0; } - GameRule(String javaID, Class type, Object defaultValue) { + GameRule(String javaID, int defaultValue) { this.javaID = javaID; - this.type = type; + this.type = Integer.class; this.defaultValue = defaultValue; } - /** - * Convert a string to an object of the correct type for the current gamerule - * - * @param value The string value to convert - * @return The converted and formatted value - */ - public Object convertValue(String value) { - if (type.equals(Boolean.class)) { - return Boolean.parseBoolean(value); - } else if (type.equals(Integer.class)) { - return Integer.parseInt(value); - } - - return null; + public boolean getDefaultBooleanValue() { + return defaultValue != 0; } - /** - * Fetch a game rule by the given Java ID - * - * @param id The ID of the gamerule - * @return A {@link GameRule} object representing the requested ID or {@link GameRule#UNKNOWN} - */ - public static GameRule fromJavaID(String id) { - for (GameRule gamerule : VALUES) { - if (gamerule.javaID.equals(id)) { - return gamerule; - } - } - - return UNKNOWN; + public int getDefaultIntValue() { + return defaultValue; } } 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 100917793..f19060c65 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.level; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -36,11 +34,8 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.ChunkCache; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; -import java.util.Locale; - public class GeyserWorldManager extends WorldManager { - - private static final Object2ObjectMap gameruleCache = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectMap gameruleCache = new Object2ObjectOpenHashMap<>(); @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { @@ -82,18 +77,18 @@ public class GeyserWorldManager extends WorldManager { @Override public void setGameRule(GeyserSession session, String name, Object value) { - session.sendCommand("gamerule " + name + " " + value); + super.setGameRule(session, name, value); gameruleCache.put(name, String.valueOf(value)); } @Override - public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { + public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { String value = gameruleCache.get(gameRule.getJavaID()); if (value != null) { return Boolean.parseBoolean(value); } - return gameRule.getDefaultValue() != null ? (Boolean) gameRule.getDefaultValue() : false; + return gameRule.getDefaultBooleanValue(); } @Override @@ -103,17 +98,7 @@ public class GeyserWorldManager extends WorldManager { return Integer.parseInt(value); } - return gameRule.getDefaultValue() != null ? (int) gameRule.getDefaultValue() : 0; - } - - @Override - public void setPlayerGameMode(GeyserSession session, GameMode gameMode) { - session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT)); - } - - @Override - public void setDifficulty(GeyserSession session, Difficulty difficulty) { - session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT)); + return gameRule.getDefaultIntValue(); } @Override 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 5f3c96b86..a3f6b55e4 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java @@ -27,7 +27,7 @@ package org.geysermc.geyser.level; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; -import org.geysermc.geyser.util.JavaCodecEntry; +import org.geysermc.geyser.util.JavaCodecUtil; import java.util.Map; @@ -39,7 +39,7 @@ import java.util.Map; public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale) { public static void load(CompoundTag tag, Map map) { - for (CompoundTag dimension : JavaCodecEntry.iterateAsTag(tag.get("minecraft:dimension_type"))) { + for (CompoundTag dimension : JavaCodecUtil.iterateAsTag(tag.get("minecraft:dimension_type"))) { CompoundTag elements = dimension.get("element"); int minY = ((IntTag) elements.get("min_y")).getValue(); int maxY = ((IntTag) elements.get("height")).getValue(); 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 69f5d5beb..e10981f4b 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -31,6 +31,9 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nullable; +import java.util.Locale; + /** * Class that manages or retrieves various information * from the world. Everything in this class should be @@ -105,7 +108,9 @@ public abstract class WorldManager { * @param name The gamerule to change * @param value The new value for the gamerule */ - public abstract void setGameRule(GeyserSession session, String name, Object value); + public void setGameRule(GeyserSession session, String name, Object value) { + session.sendCommand("gamerule " + name + " " + value); + } /** * Gets a gamerule value as a boolean @@ -114,7 +119,7 @@ public abstract class WorldManager { * @param gameRule The gamerule to fetch the value of * @return The boolean representation of the value */ - public abstract Boolean getGameRuleBool(GeyserSession session, GameRule gameRule); + public abstract boolean getGameRuleBool(GeyserSession session, GameRule gameRule); /** * Get a gamerule value as an integer @@ -131,7 +136,9 @@ public abstract class WorldManager { * @param session The session of the player to change the game mode of * @param gameMode The game mode to change the player to */ - public abstract void setPlayerGameMode(GeyserSession session, GameMode gameMode); + public void setPlayerGameMode(GeyserSession session, GameMode gameMode) { + session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT)); + } /** * Change the difficulty of the Java server @@ -139,7 +146,9 @@ public abstract class WorldManager { * @param session The session of the user that requested the change * @param difficulty The difficulty to change to */ - public abstract void setDifficulty(GeyserSession session, Difficulty difficulty); + public void setDifficulty(GeyserSession session, Difficulty difficulty) { + session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT)); + } /** * Checks if the given session's player has a permission @@ -149,4 +158,12 @@ public abstract class WorldManager { * @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. + */ + @Nullable + public String[] getBiomeIdentifiers(boolean withTags) { + return null; + } } diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java index d2f3e7c9e..d95767b97 100644 --- a/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java @@ -96,7 +96,7 @@ public enum BitArrayVersion { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); } else if (this == V0) { - return new SingletonBitArray(); + return SingletonBitArray.INSTANCE; } else { return new Pow2BitArray(this, size, words); } diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java index 6e37749df..ce25ead95 100644 --- a/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java @@ -26,13 +26,12 @@ package org.geysermc.geyser.level.chunk.bitarray; import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntArrays; public class SingletonBitArray implements BitArray { public static final SingletonBitArray INSTANCE = new SingletonBitArray(); - private static final int[] EMPTY_ARRAY = new int[0]; - - public SingletonBitArray() { + private SingletonBitArray() { } @Override @@ -56,7 +55,7 @@ public class SingletonBitArray implements BitArray { @Override public int[] getWords() { - return EMPTY_ARRAY; + return IntArrays.EMPTY_ARRAY; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java index 3bfbf118d..49fe6c42d 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.network; import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; +import com.nukkitx.protocol.bedrock.v554.Bedrock_v554; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.DefaultEventLoopGroup; @@ -84,14 +85,16 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { } } - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); + String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : ""; + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip)); return true; } @Override public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { - geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); + String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : ""; + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip)); } GeyserConfiguration config = geyser.getConfig(); @@ -122,13 +125,9 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setSubMotd(config.getBedrock().secondaryMotd()); } - if (config.isPassthroughPlayerCounts() && pingInfo != null) { - pong.setPlayerCount(pingInfo.getPlayers().getOnline()); - pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax()); - } else { - pong.setPlayerCount(geyser.getSessionManager().getSessions().size()); - pong.setMaximumPlayerCount(config.getMaxPlayers()); - } + // https://github.com/GeyserMC/Geyser/issues/3388 + pong.setMotd(pong.getMotd().replace(';', ':')); + pong.setSubMotd(pong.getSubMotd().replace(';', ':')); // Fallbacks to prevent errors and allow Bedrock to see the server if (pong.getMotd() == null || pong.getMotd().isBlank()) { @@ -157,6 +156,14 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { } } + if (config.isPassthroughPlayerCounts() && pingInfo != null) { + pong.setPlayerCount(pingInfo.getPlayers().getOnline()); + pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax()); + } else { + pong.setPlayerCount(geyser.getSessionManager().getSessions().size()); + pong.setMaximumPlayerCount(config.getMaxPlayers()); + } + //Bedrock will not even attempt a connection if the client thinks the server is full //so we have to fake it not being full if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { @@ -169,7 +176,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { try { - bedrockServerSession.setPacketCodec(GameProtocol.DEFAULT_BEDROCK_CODEC); + bedrockServerSession.setPacketCodec(Bedrock_v554.V554_CODEC); // Has the RequestNetworkSettingsPacket bedrockServerSession.setLogging(true); bedrockServerSession.setCompressionLevel(geyser.getConfig().getBedrock().getCompressionLevel()); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(geyser, new GeyserSession(geyser, bedrockServerSession, eventLoopGroup.next()))); 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 1d7ceaa00..6b46f8056 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -28,12 +28,14 @@ package org.geysermc.geyser.network; import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; -import com.nukkitx.protocol.bedrock.v534.Bedrock_v534; +import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; +import com.nukkitx.protocol.bedrock.v545.Bedrock_v545; +import com.nukkitx.protocol.bedrock.v554.Bedrock_v554; +import com.nukkitx.protocol.bedrock.v557.Bedrock_v557; +import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.StringJoiner; @@ -45,7 +47,7 @@ public final class GameProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v534.V534_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v560.V560_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -58,10 +60,19 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.V527_CODEC.toBuilder() - .minecraftVersion("1.19.0/1.19.2") + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v545.V545_CODEC.toBuilder() + .minecraftVersion("1.19.21/1.19.22") + .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v554.V554_CODEC.toBuilder() + .minecraftVersion("1.19.30/1.19.31") + .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.V557_CODEC.toBuilder() + .minecraftVersion("1.19.40/1.19.41") + .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.19.50/1.19.51") .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** @@ -80,8 +91,12 @@ public final class GameProtocol { /* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */ - public static boolean supports1_19_10(GeyserSession session) { - return session.getUpstream().getProtocolVersion() >= Bedrock_v534.V534_CODEC.getProtocolVersion(); + public static boolean supports1_19_30(GeyserSession session) { + return session.getUpstream().getProtocolVersion() >= Bedrock_v554.V554_CODEC.getProtocolVersion(); + } + + public static boolean supports1_19_50(GeyserSession session) { + return session.getUpstream().getProtocolVersion() >= Bedrock_v560.V560_CODEC.getProtocolVersion(); } /** @@ -99,7 +114,7 @@ public final class GameProtocol { * @return the supported Minecraft: Java Edition version names */ public static List getJavaVersions() { - return Collections.singletonList(DEFAULT_JAVA_CODEC.getMinecraftVersion()); + return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion()); } /** diff --git a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index b0b707ee0..8d2db081a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -856,4 +856,18 @@ public class LoggingPacketHandler implements BedrockPacketHandler { public boolean handle(FilterTextPacket packet) { return defaultHandler(packet); } + + // 1.19.0 new packet + + @Override + public boolean handle(RequestAbilityPacket packet) { + return defaultHandler(packet); + } + + // 1.19.30 new packet + + @Override + public boolean handle(RequestNetworkSettingsPacket packet) { + return defaultHandler(packet); + } } \ No newline at end of file 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 884504d44..17bf80c12 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.network; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.data.ExperimentData; +import com.nukkitx.protocol.bedrock.data.PacketCompressionAlgorithm; import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.GeyserImpl; @@ -45,9 +46,13 @@ import org.geysermc.geyser.util.MathUtils; import java.io.FileInputStream; import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.Deque; public class UpstreamPacketHandler extends LoggingPacketHandler { + private Deque packsToSent = new ArrayDeque<>(); + public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { super(geyser, session); } @@ -61,6 +66,46 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return translateAndDefault(packet); } + private boolean newProtocol = false; // TEMPORARY + + private boolean setCorrectCodec(int protocolVersion) { + BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion); + if (packetCodec == null) { + String supportedVersions = GameProtocol.getAllSupportedBedrockVersions(); + if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + // Too early to determine session locale + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); + return false; + } else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); + return false; + } + } + + session.getUpstream().getSession().setPacketCodec(packetCodec); + return true; + } + + @Override + public boolean handle(RequestNetworkSettingsPacket packet) { + if (setCorrectCodec(packet.getProtocolVersion())) { + newProtocol = true; + } else { + return true; + } + + // New since 1.19.30 - sent before login packet + PacketCompressionAlgorithm algorithm = PacketCompressionAlgorithm.ZLIB; + + NetworkSettingsPacket responsePacket = new NetworkSettingsPacket(); + responsePacket.setCompressionAlgorithm(algorithm); + responsePacket.setCompressionThreshold(512); + session.sendUpstreamPacketImmediately(responsePacket); + + session.getUpstream().getSession().setCompression(algorithm); + return true; + } + @Override public boolean handle(LoginPacket loginPacket) { if (geyser.isShuttingDown()) { @@ -69,21 +114,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return true; } - BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); - if (packetCodec == null) { - String supportedVersions = GameProtocol.getAllSupportedBedrockVersions(); - if (loginPacket.getProtocolVersion() > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - // Too early to determine session locale - session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); - return true; - } else if (loginPacket.getProtocolVersion() < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); + if (!newProtocol) { + if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20 return true; } } - session.getUpstream().getSession().setPacketCodec(packetCodec); - // Set the block translation based off of version session.setBlockMappings(BlockRegistries.BLOCKS.forVersion(loginPacket.getProtocolVersion())); session.setItemMappings(Registries.ITEMS.forVersion(loginPacket.getProtocolVersion())); @@ -106,7 +142,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), - "", "", "", false, false)); + resourcePack.getContentKey(), "", header.getUuid().toString(), false, false)); } resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks() || GeyserImpl.getInstance().getConfig().isAddCustomSkullBlocks()); session.sendUpstreamPacket(resourcePacksInfo); @@ -129,24 +165,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { break; case SEND_PACKS: - for(String id : packet.getPackIds()) { - ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); - String[] packID = id.split("_"); - ResourcePack pack = ResourcePack.PACKS.get(packID[0]); - ResourcePackManifest.Header header = pack.getManifest().getHeader(); - - data.setPackId(header.getUuid()); - int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE); - data.setChunkCount(chunkCount); - data.setCompressedPackSize(pack.getFile().length()); - data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); - data.setHash(pack.getSha256()); - data.setPackVersion(packID[1]); - data.setPremium(false); - data.setType(ResourcePackType.RESOURCE); - - session.sendUpstreamPacket(data); - } + packsToSent.addAll(packet.getPackIds()); + sendPackDataInfo(packsToSent.pop()); break; case HAVE_ALL_PACKS: @@ -239,7 +259,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { data.setPackId(packet.getPackId()); int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE; - byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length() - offset, 0, ResourcePack.CHUNK_SIZE)]; + long remainingSize = pack.getFile().length() - offset; + byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)]; try (InputStream inputStream = new FileInputStream(pack.getFile())) { inputStream.skip(offset); @@ -251,6 +272,31 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { data.setData(packData); session.sendUpstreamPacket(data); + + // Check if it is the last chunk and send next pack in queue when available. + if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) { + sendPackDataInfo(packsToSent.pop()); + } + return true; } + + private void sendPackDataInfo(String id) { + ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); + String[] packID = id.split("_"); + ResourcePack pack = ResourcePack.PACKS.get(packID[0]); + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + + data.setPackId(header.getUuid()); + int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE); + data.setChunkCount(chunkCount); + data.setCompressedPackSize(pack.getFile().length()); + data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); + data.setHash(pack.getSha256()); + data.setPackVersion(packID[1]); + data.setPremium(false); + data.setType(ResourcePackType.RESOURCE); + + session.sendUpstreamPacket(data); + } } diff --git a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java index 4b63095a9..6bc7d4456 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -25,17 +25,22 @@ package org.geysermc.geyser.pack; +import lombok.Getter; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; @@ -60,6 +65,9 @@ public class ResourcePack { private ResourcePackManifest manifest; private ResourcePackManifest.Version version; + @Getter + private String contentKey; + /** * Loop through the packs directory and locate valid resource pack files */ @@ -121,6 +129,11 @@ public class ResourcePack { } } }); + + // Check if a file exists with the same name as the resource pack suffixed by .key, + // and set this as content key. (e.g. test.zip, key file would be test.zip.key) + File keyFile = new File(file.getParentFile(), file.getName() + ".key"); + pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : ""; } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); e.printStackTrace(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java index fc4e3d022..70a3da88e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java @@ -29,8 +29,8 @@ import org.geysermc.geyser.registry.loader.RegistryLoader; import javax.annotation.Nullable; import java.util.Map; -import java.util.Optional; -import java.util.function.Function; +import java.util.OptionalInt; +import java.util.function.ToIntFunction; /** * An abstract registry holding a map of various registrations as defined by {@link M}. @@ -62,15 +62,14 @@ public abstract class AbstractMappedRegistry> extends * * @param key the key * @param mapper the mapper - * @param the type * @return the mapped value from the given key if present */ - public Optional map(K key, Function mapper) { + public OptionalInt map(K key, ToIntFunction mapper) { V value = this.get(key); if (value == null) { - return Optional.empty(); + return OptionalInt.empty(); } else { - return Optional.ofNullable(mapper.apply(value)); + return OptionalInt.of(mapper.applyAsInt(value)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 48740d743..2a1b595d8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; @@ -180,5 +181,18 @@ public final class Registries { // Create registries that require other registries to load first POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new); ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new); + + // Remove unneeded client generation data from NbtMapBuilder + NbtMapBuilder biomesNbt = NbtMap.builder(); + for (Map.Entry entry : BIOMES_NBT.get().entrySet()) { + String key = entry.getKey(); + NbtMapBuilder value = ((NbtMap) entry.getValue()).toBuilder(); + value.remove("minecraft:consolidated_features"); + value.remove("minecraft:multinoise_generation_rules"); + value.remove("minecraft:surface_material_adjustments"); + value.remove( "minecraft:surface_parameters"); + biomesNbt.put(key, value.build()); + } + BIOMES_NBT.set(biomesNbt.build()); } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 1c31901ff..3fe71dd06 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -28,11 +28,13 @@ package org.geysermc.geyser.registry.loader; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; @@ -47,10 +49,9 @@ import java.util.Map; */ public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>, Map, ProviderSupplier>> { - @SuppressWarnings("unchecked") @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { - providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) args[0])); + providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder()); @@ -58,6 +59,7 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder()); providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder()); + providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0])); return providers; } 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 058cf0661..f16258d68 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 @@ -31,7 +31,8 @@ import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; import com.nukkitx.protocol.bedrock.data.BlockPropertyData; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; -import com.nukkitx.protocol.bedrock.v534.Bedrock_v534; +import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; +import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; @@ -67,18 +68,7 @@ import java.util.zip.GZIPInputStream; /** * Populates the block registries. */ -public class BlockRegistryPopulator { - private static final ImmutableMap, BiFunction> BLOCK_MAPPERS; - private static final BiFunction EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null; - - static { - ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_19_0", Bedrock_v527.V527_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_19_0", Bedrock_v534.V534_CODEC.getProtocolVersion()), EMPTY_MAPPER); // Block palette hasn't changed, but the custom block nbt format has changed - - BLOCK_MAPPERS = stateMapperBuilder.build(); - } - +public final class BlockRegistryPopulator { /** * Stores the raw blocks JSON until it is no longer needed. */ @@ -198,10 +188,14 @@ public class BlockRegistryPopulator { } private static void registerBedrockBlocks() { - for (Map.Entry, BiFunction> palette : BLOCK_MAPPERS.entrySet()) { - BiFunction stateMapper = palette.getValue(); - int protocolVersion = palette.getKey().valueInt(); + BiFunction emptyMapper = (bedrockIdentifier, statesBuilder) -> null; + ImmutableMap, BiFunction> blockMappers = ImmutableMap., BiFunction>builder() + .put(ObjectIntPair.of("1_19_20", Bedrock_v544.V544_CODEC.getProtocolVersion()), emptyMapper) + .put(ObjectIntPair.of("1_19_50", Bedrock_v560.V560_CODEC.getProtocolVersion()), emptyMapper) + .build(); + for (Map.Entry, BiFunction> palette : blockMappers.entrySet()) { + int protocolVersion = palette.getKey().valueInt(); NbtList blocksTag; List blockStates; try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); @@ -234,7 +228,9 @@ public class BlockRegistryPopulator { // as we no longer send a block palette Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blockStates.size()); for (int i = 0; i < blockStates.size(); i++) { - NbtMap tag = blockStates.get(i); + NbtMapBuilder builder = blockStates.get(i).toBuilder(); + builder.remove("name_hash"); // Quick workaround - was added in 1.19.20 + NbtMap tag = builder.build(); if (blockStateOrderedMap.containsKey(tag)) { throw new AssertionError("Duplicate block states in Bedrock palette: " + tag); } @@ -263,6 +259,8 @@ public class BlockRegistryPopulator { int movingBlockRuntimeId = -1; Iterator> blocksIterator = BLOCKS_JSON.fields(); + BiFunction stateMapper = blockMappers.getOrDefault(palette.getKey(), emptyMapper); + int[] javaToBedrockBlocks = new int[BLOCKS_JSON.size()]; Map flowerPotBlocks = new Object2ObjectOpenHashMap<>(); @@ -468,7 +466,7 @@ public class BlockRegistryPopulator { BlockMapping.BlockMappingBuilder builder = BlockMapping.builder(); JsonNode hardnessNode = entry.getValue().get("block_hardness"); if (hardnessNode != null) { - builder.hardness(hardnessNode.doubleValue()); + builder.hardness(hardnessNode.floatValue()); } JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand"); 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 64543272e..e32030db6 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 @@ -30,11 +30,11 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import it.unimi.dsi.fastutil.objects.Object2IntMaps; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.ToolBreakSpeedsUtils; import org.geysermc.geyser.item.components.WearableSlot; @@ -43,6 +43,7 @@ import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import javax.annotation.Nullable; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.OptionalInt; @@ -65,6 +66,13 @@ public class CustomItemRegistryPopulator { if (!item.customItemOptions().hasCustomItemOptions()) { GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types"); } + String name = item.name(); + if (name.isEmpty()) { + GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); + } else if (Character.isDigit(name.charAt(0))) { + // As of 1.19.31 + GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); + } return true; } @@ -85,7 +93,7 @@ public class CustomItemRegistryPopulator { .maxDamage(customItemData.maxDamage()) .repairMaterials(customItemData.repairMaterials()) .hasSuspiciousStewEffect(false) - .customItemOptions(Object2IntMaps.emptyMap()) + .customItemOptions(Collections.emptyList()) .build(); NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, @@ -116,6 +124,33 @@ public class CustomItemRegistryPopulator { computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder); } + if (mapping.getFirstBlockRuntimeId() != null) { + computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); + } + + if (mapping.isEdible()) { + computeConsumableProperties(itemProperties, componentBuilder, 1, false); + } + + if (mapping.isEntityPlacer()) { + computeEntityPlacerProperties(componentBuilder); + } + + switch (mapping.getBedrockIdentifier()) { + case "minecraft:fire_charge", "minecraft:flint_and_steel" -> { + computeBlockItemProperties("minecraft:fire", componentBuilder); + } + case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> { + computeChargeableProperties(itemProperties, componentBuilder); + } + case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> { + computeConsumableProperties(itemProperties, componentBuilder, 2, true); + } + case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> { + computeThrowableProperties(componentBuilder); + } + } + computeRenderOffsets(false, customItemData, componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); @@ -171,7 +206,8 @@ public class CustomItemRegistryPopulator { itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); itemProperties.putBoolean("hand_equipped", isTool); itemProperties.putInt("max_stack_size", stackSize); - if (maxDamage > 0) { + // Ignore durability if the item's predicate requires that it be unbreakable + if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) { componentBuilder.putCompound("minecraft:durability", NbtMap.builder() .putCompound("damage_chance", NbtMap.builder() .putInt("max", 1) @@ -251,6 +287,48 @@ public class CustomItemRegistryPopulator { } } + private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { + // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here + // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot + // it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome + + // all block items registered should be given this component to prevent double placement + componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); + } + + private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + // setting high use_duration prevents the consume animation from playing + itemProperties.putInt("use_duration", Integer.MAX_VALUE); + // display item as tool (mainly for crossbow and bow) + itemProperties.putBoolean("hand_equipped", true); + // ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly) + componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build()); + } + + private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) { + // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks + itemProperties.putInt("use_duration", 32); + // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds + // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively + itemProperties.putInt("use_animation", useAnimation); + // this component is required to allow the eat animation to play + componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + } + + private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { + // all items registered that place entities should be given this component to prevent double placement + // it is okay that the entity here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); + } + + private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { + // allows item to be thrown when holding down right click (individual presses are required w/o this component) + componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); + // this must be set to something for the swing animation to play + // it is okay that the projectile here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); + } + private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { if (isHat) { componentBuilder.remove("minecraft:render_offsets"); 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 df7544f4b..ed9f08f36 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 @@ -37,8 +37,11 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; import it.unimi.dsi.fastutil.ints.*; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; +import com.nukkitx.protocol.bedrock.v534.Bedrock_v534; +import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -51,6 +54,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.mappings.MappingsConfigReader; @@ -75,7 +79,8 @@ public class ItemRegistryPopulator { public static void populate() { Map paletteVersions = new Object2ObjectOpenHashMap<>(); - paletteVersions.put("1_19_0", new PaletteVersion(Bedrock_v527.V527_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.V544_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.V560_CODEC.getProtocolVersion(), Collections.emptyMap())); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); @@ -91,7 +96,10 @@ public class ItemRegistryPopulator { boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); - Multimap customItems = MultimapBuilder.hashKeys().hashSetValues().build(); + // List values here is important compared to HashSet - we need to preserve the order of what's given to us + // (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom + // of the list first, then ascends. + Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); List nonVanillaCustomItems; MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); @@ -104,7 +112,7 @@ public class ItemRegistryPopulator { }); nonVanillaCustomItems = new ObjectArrayList<>(); - GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEvent(customItems, nonVanillaCustomItems) { + GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { @Override public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { if (CustomItemRegistryPopulator.initialCheck(identifier, customItemData, items)) { @@ -285,8 +293,7 @@ public class ItemRegistryPopulator { Set javaOnlyItems = new ObjectOpenHashSet<>(); Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg", - "minecraft:bundle"); + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:bundle"); if (!customItemsAllowed) { javaOnlyItems.add("minecraft:furnace_minecart"); } @@ -469,10 +476,10 @@ public class ItemRegistryPopulator { } // Add the custom item properties, if applicable - Object2IntMap customItemOptions; + List> customItemOptions; Collection customItemsToLoad = customItems.get(javaIdentifier); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { - customItemOptions = new Object2IntOpenHashMap<>(customItemsToLoad.size()); + customItemOptions = new ObjectArrayList<>(customItemsToLoad.size()); for (CustomItemData customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; @@ -492,12 +499,15 @@ public class ItemRegistryPopulator { entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry()); // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemOptions.put(customItem.customItemOptions(), customProtocolId); + customItemOptions.add(ObjectIntPair.of(customItem.customItemOptions(), customProtocolId)); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } + + // Important for later to find the best match and accurately replicate Java behavior + Collections.reverse(customItemOptions); } else { - customItemOptions = Object2IntMaps.emptyMap(); + customItemOptions = Collections.emptyList(); } mappingBuilder.customItemOptions(customItemOptions); @@ -551,7 +561,7 @@ public class ItemRegistryPopulator { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) - .customItemOptions(Object2IntMaps.emptyMap()) + .customItemOptions(Collections.emptyList()) .build(); if (customItemsAllowed) { @@ -568,6 +578,7 @@ public class ItemRegistryPopulator { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) + .customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java index f0a215f2a..6b6bfe9fe 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java @@ -33,6 +33,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -81,8 +82,6 @@ public class RecipeRegistryPopulator { Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), ++LAST_RECIPE_NET_ID))); craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPCLONING, Collections.singletonList(CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), ++LAST_RECIPE_NET_ID))); - craftingData.put(RecipeType.CRAFTING_SPECIAL_BANNERADDPATTERN, - Collections.singletonList(CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), ++LAST_RECIPE_NET_ID))); // https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php @@ -171,7 +170,7 @@ public class RecipeRegistryPopulator { /* Convert end */ return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId); } List inputs = new ObjectArrayList<>(); for (JsonNode entry : node.get("inputs")) { @@ -191,10 +190,10 @@ public class RecipeRegistryPopulator { if (type == 5) { // Shulker box return CraftingData.fromShulkerBox(uuid.toString(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId); } return CraftingData.fromShapeless(uuid.toString(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId); } private static ItemData getBedrockItemFromIdentifierJson(ItemMapping mapping, JsonNode itemNode) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java index cd91f64d1..34cde0acf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java @@ -45,7 +45,7 @@ public class BlockMapping { */ int javaBlockId; - double hardness; + float hardness; boolean canBreakWithHand; /** * The index of this collision in collision.json diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java index 6c65f1c34..480d1095d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java @@ -48,4 +48,6 @@ public class GeyserMappingItem { @JsonProperty("repair_materials") List repairMaterials; @JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false; @JsonProperty("dye_color") int dyeColor = -1; + @JsonProperty("is_edible") boolean edible = false; + @JsonProperty("is_entity_placer") boolean entityPlacer = false; } 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 12ba7d208..e3d34b0ca 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 @@ -25,15 +25,15 @@ package org.geysermc.geyser.registry.type; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.CustomItemOptions; -import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.registry.BlockRegistries; +import java.util.Collections; +import java.util.List; import java.util.Set; @Value @@ -41,8 +41,8 @@ import java.util.Set; @EqualsAndHashCode public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, - BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, Object2IntMaps.emptyMap(), 0, null, false); + 0, // Air is never sent in full over the network for this to serialize. + 64, null, null, null, Collections.emptyList(), 0, null, false); String javaIdentifier; String bedrockIdentifier; @@ -62,7 +62,8 @@ public class ItemMapping { String translationString; - Object2IntMap customItemOptions; + @NonNull + List> customItemOptions; int maxDamage; 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 45ae7eff2..fed3054b4 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java @@ -118,7 +118,7 @@ public final class ScoreboardUpdater extends Thread { FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; geyser.getLogger().info( - GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.name(), threshold, pps) + + GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.bedrockUsername(), threshold, pps) + GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) ); 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 2e927fd34..3cfaf3827 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -73,7 +73,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; import io.netty.channel.Channel; import io.netty.channel.EventLoop; -import it.unimi.dsi.fastutil.bytes.ByteArrays; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -91,6 +90,9 @@ import lombok.Setter; import lombok.experimental.Accessors; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.value.qual.IntRange; +import org.geysermc.api.util.BedrockPlatform; +import org.geysermc.api.util.InputMode; +import org.geysermc.api.util.UiProfile; import org.geysermc.common.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; @@ -116,7 +118,6 @@ import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.WorldManager; 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; @@ -126,9 +127,9 @@ import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.session.cache.*; import org.geysermc.geyser.skin.FloodgateSkinUploader; -import org.geysermc.geyser.text.ChatTypeEntry; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ChunkUtils; @@ -136,9 +137,7 @@ import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.MathUtils; -import javax.annotation.Nonnull; import java.net.ConnectException; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -151,13 +150,13 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter public class GeyserSession implements GeyserConnection, GeyserCommandSource { - private final @Nonnull GeyserImpl geyser; - private final @Nonnull UpstreamSession upstream; + private final @NonNull GeyserImpl geyser; + private final @NonNull UpstreamSession upstream; /** * The loop where all packets and ticking is processed to prevent concurrency issues. * If this is manually called, ensure that any exceptions are properly handled. */ - private final @Nonnull EventLoop eventLoop; + private final @NonNull EventLoop eventLoop; private TcpSession downstream; @Setter private AuthData authData; @@ -297,6 +296,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ @Setter private String worldName = null; + /** + * As of Java 1.19.3, the client only uses these for commands. + */ + @Setter + private String[] levels; private boolean sneaking; @@ -339,7 +343,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ private final Map dimensions = new Object2ObjectOpenHashMap<>(3); - private final Int2ObjectMap chatTypes = new Int2ObjectOpenHashMap<>(8); + private final Int2ObjectMap chatTypes = new Int2ObjectOpenHashMap<>(7); @Setter private int breakingBlock; @@ -562,8 +566,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.playerEntity = new SessionPlayerEntity(this); collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition()); - ChatTypeEntry.applyDefaults(chatTypes); - this.playerInventory = new PlayerInventory(); this.openInventory = null; this.craftingRecipes = new Int2ObjectOpenHashMap<>(); @@ -630,6 +632,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { creativePacket.setContents(this.itemMappings.getCreativeItems()); upstream.sendPacket(creativePacket); + // Potion mixes are registered by default, as they are needed to be able to put ingredients into the brewing stand. + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.get()); + upstream.sendPacket(craftingDataPacket); + PlayStatusPacket playStatusPacket = new PlayStatusPacket(); playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); upstream.sendPacket(playStatusPacket); @@ -737,7 +745,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { try { service.login(); } catch (RequestException e) { - geyser.getLogger().error("Error while attempting to use refresh token for " + name() + "!", e); + geyser.getLogger().error("Error while attempting to use refresh token for " + bedrockUsername() + "!", e); return Boolean.FALSE; } @@ -749,7 +757,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } protocol = new MinecraftProtocol(profile, service.getAccessToken()); - geyser.saveRefreshToken(name(), service.getRefreshToken()); + geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken()); return Boolean.TRUE; }).whenComplete((successful, ex) -> { if (this.closed) { @@ -840,7 +848,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { connectDownstream(); // Save our refresh token for later use - geyser.saveRefreshToken(name(), service.getRefreshToken()); + geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken()); return true; } } @@ -1043,7 +1051,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } else { // Downstream's disconnect will fire an event that prints a log message // Otherwise, we print a message here - InetAddress address = upstream.getAddress().getAddress(); + String address = geyser.getConfig().isLogPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : ""; geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, reason)); } if (!upstream.isClosed()) { @@ -1073,7 +1081,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { try { runnable.run(); } catch (Throwable e) { - geyser.getLogger().error("Error thrown in " + this.name() + "'s event loop!", e); + geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e); } }); } @@ -1086,7 +1094,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { try { runnable.run(); } catch (Throwable e) { - geyser.getLogger().error("Error thrown in " + this.name() + "'s event loop!", e); + geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e); } }, duration, timeUnit); } @@ -1312,7 +1320,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private boolean disableBlocking() { if (playerEntity.getFlag(EntityFlag.BLOCKING)) { ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, - Vector3i.ZERO, Direction.DOWN, worldCache.nextPredictionSequence()); + Vector3i.ZERO, Direction.DOWN, 0); sendDownstreamPacket(releaseItemPacket); playerEntity.setFlag(EntityFlag.BLOCKING, false); return true; @@ -1329,32 +1337,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public String name() { - return authData.name(); - } - - @Override - public UUID uuid() { - return authData.uuid(); - } - - @Override - public String xuid() { - return authData.xuid(); - } - - @SuppressWarnings("ConstantConditions") // Need to enforce the parameter annotations - @Override - public boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port) { - if (address == null || address.isBlank()) { - throw new IllegalArgumentException("Server address cannot be null or blank"); - } else if (port < 0 || port > 65535) { - throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port); - } - TransferPacket transferPacket = new TransferPacket(); - transferPacket.setAddress(address); - transferPacket.setPort(port); - sendUpstreamPacket(transferPacket); - return true; + return null; } @Override @@ -1384,14 +1367,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Sends a chat message to the Java server. */ public void sendChat(String message) { - sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, ByteArrays.EMPTY_ARRAY, false)); + sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, null, 0, new BitSet())); } /** * Sends a command to the Java server. */ public void sendCommand(String command) { - sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyMap(), false)); + sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyList(), 0, new BitSet())); } public void setServerRenderDistance(int renderDistance) { @@ -1407,12 +1390,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return this.upstream.getAddress(); } - public void sendForm(Form form) { + public boolean sendForm(@NonNull Form form) { formCache.showForm(form); + return true; } - public void sendForm(FormBuilder formBuilder) { + public boolean sendForm(@NonNull FormBuilder formBuilder) { formCache.showForm(formBuilder.build()); + return true; } /** @@ -1444,7 +1429,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(-1L); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); + startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(chunkCache.getBedrockDimension())); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); @@ -1496,6 +1481,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setPlayerPropertyData(NbtMap.EMPTY); startGamePacket.setWorldTemplateId(UUID.randomUUID()); + startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE); + SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); settings.setMovementMode(AuthoritativeMovementMode.CLIENT); settings.setRewindHistorySize(0); @@ -1649,68 +1636,40 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { boolean spectator = gameMode == GameMode.SPECTATOR; boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; - if (GameProtocol.supports1_19_10(this)) { - UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); - adventureSettingsPacket.setNoMvP(false); - adventureSettingsPacket.setNoPvM(false); - adventureSettingsPacket.setImmutableWorld(worldImmutable); - adventureSettingsPacket.setShowNameTags(false); - adventureSettingsPacket.setAutoJump(true); - sendUpstreamPacket(adventureSettingsPacket); + UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); + adventureSettingsPacket.setNoMvP(false); + adventureSettingsPacket.setNoPvM(false); + adventureSettingsPacket.setImmutableWorld(worldImmutable); + adventureSettingsPacket.setShowNameTags(false); + adventureSettingsPacket.setAutoJump(true); + sendUpstreamPacket(adventureSettingsPacket); - UpdateAbilitiesPacket updateAbilitiesPacket = new UpdateAbilitiesPacket(); - updateAbilitiesPacket.setUniqueEntityId(bedrockId); - updateAbilitiesPacket.setCommandPermission(commandPermission); - updateAbilitiesPacket.setPlayerPermission(playerPermission); + UpdateAbilitiesPacket updateAbilitiesPacket = new UpdateAbilitiesPacket(); + updateAbilitiesPacket.setUniqueEntityId(bedrockId); + updateAbilitiesPacket.setCommandPermission(commandPermission); + updateAbilitiesPacket.setPlayerPermission(playerPermission); - AbilityLayer abilityLayer = new AbilityLayer(); - Set abilities = abilityLayer.getAbilityValues(); - if (canFly || spectator) { - abilities.add(Ability.MAY_FLY); - } - - // Default stuff we have to fill in - abilities.add(Ability.BUILD); - abilities.add(Ability.MINE); - // Needed so you can drop items - abilities.add(Ability.DOORS_AND_SWITCHES); - if (gameMode == GameMode.CREATIVE) { - // Needed so the client doesn't attempt to take away items - abilities.add(Ability.INSTABUILD); - } - - if (flying || spectator) { - if (spectator && !flying) { - // We're "flying locked" in this gamemode - flying = true; - ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(true); - sendDownstreamPacket(abilitiesPacket); - } - abilities.add(Ability.FLYING); - } - - if (spectator) { - abilities.add(Ability.NO_CLIP); - } - - abilityLayer.setLayerType(AbilityLayer.Type.BASE); - abilityLayer.setFlySpeed(flySpeed); - abilityLayer.setWalkSpeed(walkSpeed); - Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES); - - updateAbilitiesPacket.getAbilityLayers().add(abilityLayer); - sendUpstreamPacket(updateAbilitiesPacket); - return; + AbilityLayer abilityLayer = new AbilityLayer(); + Set abilities = abilityLayer.getAbilityValues(); + if (canFly || spectator) { + abilities.add(Ability.MAY_FLY); } - AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); - adventureSettingsPacket.setUniqueEntityId(bedrockId); - adventureSettingsPacket.setCommandPermission(commandPermission); - adventureSettingsPacket.setPlayerPermission(playerPermission); + // Default stuff we have to fill in + abilities.add(Ability.BUILD); + abilities.add(Ability.MINE); + // Needed so you can drop items + abilities.add(Ability.DOORS_AND_SWITCHES); + if (gameMode == GameMode.CREATIVE) { + // Needed so the client doesn't attempt to take away items + abilities.add(Ability.INSTABUILD); + } - Set flags = adventureSettingsPacket.getSettings(); - if (canFly || spectator) { - flags.add(AdventureSetting.MAY_FLY); + if (commandPermission == CommandPermission.OPERATOR) { + // Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and + // a packet is not sent to the server. + // https://github.com/GeyserMC/Geyser/issues/3191 + abilities.add(Ability.OPERATOR_COMMANDS); } if (flying || spectator) { @@ -1720,20 +1679,21 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(true); sendDownstreamPacket(abilitiesPacket); } - flags.add(AdventureSetting.FLYING); - } - - if (worldImmutable) { - flags.add(AdventureSetting.WORLD_IMMUTABLE); + abilities.add(Ability.FLYING); } if (spectator) { - flags.add(AdventureSetting.NO_CLIP); + abilities.add(Ability.NO_CLIP); } - flags.add(AdventureSetting.AUTO_JUMP); + abilityLayer.setLayerType(AbilityLayer.Type.BASE); + abilityLayer.setFlySpeed(flySpeed); + // https://github.com/GeyserMC/Geyser/issues/3139 as of 1.19.10 + abilityLayer.setWalkSpeed(walkSpeed == 0f ? 0.01f : walkSpeed); + Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES); - sendUpstreamPacket(adventureSettingsPacket); + updateAbilitiesPacket.getAbilityLayers().add(abilityLayer); + sendUpstreamPacket(updateAbilitiesPacket); } private int getRenderDistance() { @@ -1762,7 +1722,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * * @param statistics Updated statistics values */ - public void updateStatistics(@Nonnull Object2IntMap statistics) { + public void updateStatistics(@NonNull Object2IntMap statistics) { if (this.statistics.isEmpty()) { // Initialize custom statistics to 0, so that they appear in the form for (CustomStatistic customStatistic : CustomStatistic.values()) { @@ -1848,4 +1808,69 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public MinecraftCodecHelper getCodecHelper() { return (MinecraftCodecHelper) this.downstream.getCodecHelper(); } + + @Override + public String bedrockUsername() { + return authData.name(); + } + + @Override + public @MonotonicNonNull String javaUsername() { + return playerEntity.getUsername(); + } + + @Override + public UUID javaUuid() { + return playerEntity.getUuid(); + } + + @Override + public String xuid() { + return authData.xuid(); + } + + @Override + public @NonNull String version() { + return clientData.getGameVersion(); + } + + @Override + public @NonNull BedrockPlatform platform() { + return BedrockPlatform.values()[clientData.getDeviceOs().ordinal()]; //todo + } + + @Override + public @NonNull String languageCode() { + return locale(); + } + + @Override + public @NonNull UiProfile uiProfile() { + return UiProfile.values()[clientData.getUiProfile().ordinal()]; //todo + } + + @Override + public @NonNull InputMode inputMode() { + return InputMode.values()[clientData.getCurrentInputMode().ordinal()]; //todo + } + + @Override + public boolean isLinked() { + return false; //todo + } + + @SuppressWarnings("ConstantConditions") // Need to enforce the parameter annotations + @Override + public boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port) { + if (address == null || address.isBlank()) { + throw new IllegalArgumentException("Server address cannot be null or blank"); + } else if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port); + } + TransferPacket transferPacket = new TransferPacket(); + transferPacket.setAddress(address); + transferPacket.setPort(port); + sendUpstreamPacket(transferPacket); + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java index fc6c37356..7e5982ab5 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -30,6 +30,7 @@ import lombok.AccessLevel; import lombok.Getter; import org.geysermc.geyser.text.GeyserLocale; +import javax.annotation.Nonnull; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -61,12 +62,23 @@ public final class SessionManager { } public void removeSession(GeyserSession session) { - if (sessions.remove(session.getPlayerEntity().getUuid()) == null) { + UUID uuid = session.getPlayerEntity().getUuid(); + if (uuid == null || sessions.remove(uuid) == null) { // Connection was likely pending pendingSessions.remove(session); } } + public GeyserSession sessionByXuid(@Nonnull String xuid) { + Objects.requireNonNull(xuid); + for (GeyserSession session : sessions.values()) { + if (session.xuid().equals(xuid)) { + return session; + } + } + return null; + } + /** * Creates a new, immutable list containing all pending and active sessions. */ diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index ac0c93204..9cd5b2ef6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -89,7 +89,7 @@ public class TagCache { boolean emulatePost1_18Logic = convertableToMud != null && convertableToMud.length != 0; session.setEmulatePost1_18Logic(emulatePost1_18Logic); if (logger.isDebug()) { - logger.debug("Emulating post 1.18 block predication logic for " + session.name() + "? " + emulatePost1_18Logic); + logger.debug("Emulating post 1.18 block predication logic for " + session.bedrockUsername() + "? " + emulatePost1_18Logic); } Map itemTags = packet.getTags().get("minecraft:item"); @@ -104,7 +104,7 @@ public class TagCache { boolean emulatePost1_13Logic = itemTags.get("minecraft:signs").length > 1; session.setEmulatePost1_13Logic(emulatePost1_13Logic); if (logger.isDebug()) { - logger.debug("Emulating post 1.13 villager logic for " + session.name() + "? " + emulatePost1_13Logic); + logger.debug("Emulating post 1.13 villager logic for " + session.bedrockUsername() + "? " + emulatePost1_13Logic); } } 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 239f5c865..b3d0518b3 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -28,7 +28,9 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.scoreboard.Scoreboard; @@ -37,7 +39,6 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.ChunkUtils; import java.util.Iterator; -import java.util.Map; public final class WorldCache { private final GeyserSession session; @@ -58,7 +59,7 @@ public final class WorldCache { private int trueTitleFadeOutTime; private int currentSequence; - private final Map unverifiedPredictions = new Object2ObjectOpenHashMap<>(1); + private final Object2IntMap unverifiedPredictions = new Object2IntOpenHashMap<>(1); public WorldCache(GeyserSession session) { this.session = session; @@ -134,30 +135,33 @@ public final class WorldCache { /* Code to support the prediction structure introduced in Java Edition 1.19.0 Blocks can be rolled back if invalid, but this requires some client-side information storage. */ + /** + * This does not need to be called for all player action packets (as of 1.19.2) and can be set to 0 if blocks aren't + * changed in the action. + */ public int nextPredictionSequence() { return ++currentSequence; } /** - * Stores a record of a block at a certain position to rollback in the event it is incorrect. + * Stores a note that this position may need to be rolled back at a future point in time. */ - public void addServerCorrectBlockState(Vector3i position, int blockState) { + public void markPositionInSequence(Vector3i position) { if (session.isEmulatePost1_18Logic()) { // Cheap hack // On non-Bukkit platforms, ViaVersion will always confirm the sequence before the block is updated, // meaning we'd send two block updates after (ChunkUtils.updateBlockClientSide in endPredictionsUpTo // and the packet updating from the client) - this.unverifiedPredictions.compute(position, ($, serverVerifiedState) -> serverVerifiedState == null - ? new ServerVerifiedState(currentSequence, blockState) : serverVerifiedState.setData(currentSequence, blockState)); + this.unverifiedPredictions.put(position, currentSequence); } } - public void updateServerCorrectBlockState(Vector3i position) { - if (this.unverifiedPredictions.isEmpty()) { - return; + public void updateServerCorrectBlockState(Vector3i position, int blockState) { + if (!this.unverifiedPredictions.isEmpty()) { + this.unverifiedPredictions.removeInt(position); } - this.unverifiedPredictions.remove(position); + ChunkUtils.updateBlock(session, blockState, position); } public void endPredictionsUpTo(int sequence) { @@ -165,40 +169,16 @@ public final class WorldCache { return; } - Iterator> it = this.unverifiedPredictions.entrySet().iterator(); + Iterator> it = Object2IntMaps.fastIterator(this.unverifiedPredictions); while (it.hasNext()) { - Map.Entry entry = it.next(); - ServerVerifiedState serverVerifiedState = entry.getValue(); - if (serverVerifiedState.sequence <= sequence) { + Object2IntMap.Entry entry = it.next(); + if (entry.getIntValue() <= sequence) { // This block may be out of sync with the server // In 1.19.0 Java, you can verify this by trying to mine in spawn protection - ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey()); + Vector3i position = entry.getKey(); + ChunkUtils.updateBlockClientSide(session, session.getGeyser().getWorldManager().getBlockAt(session, position), position); it.remove(); } } } - - private static class ServerVerifiedState { - private int sequence; - private int blockState; - - ServerVerifiedState(int sequence, int blockState) { - this.sequence = sequence; - this.blockState = blockState; - } - - ServerVerifiedState setData(int sequence, int blockState) { - this.sequence = sequence; - this.blockState = blockState; - return this; - } - - @Override - public String toString() { - return "ServerVerifiedState{" + - "sequence=" + sequence + - ", blockState=" + blockState + - '}'; - } - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index 4d0e98444..7b6dacd16 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -114,7 +114,7 @@ public final class FloodgateSkinUploader { if (session != null) { if (!node.get("success").asBoolean()) { - logger.info("Failed to upload skin for " + session.name()); + logger.info("Failed to upload skin for " + session.bedrockUsername()); return; } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 9e04104dc..089f14e57 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -258,21 +258,26 @@ public class SkinManager { JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); - if (textures != null) { - JsonNode skinTexture = textures.get("SKIN"); - String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); - - boolean isAlex = skinTexture.has("metadata"); - - String capeUrl = null; - JsonNode capeTexture = textures.get("CAPE"); - if (capeTexture != null) { - capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); - } - - return new GameProfileData(skinUrl, capeUrl, isAlex); + if (textures == null) { + return null; } - return null; + + JsonNode skinTexture = textures.get("SKIN"); + if (skinTexture == null) { + return null; + } + + String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); + + boolean isAlex = skinTexture.has("metadata"); + + String capeUrl = null; + JsonNode capeTexture = textures.get("CAPE"); + if (capeTexture != null) { + capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); + } + + return new GameProfileData(skinUrl, capeUrl, isAlex); } /** diff --git a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java index 27b277c72..69dbb558e 100644 --- a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java +++ b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java @@ -43,6 +43,8 @@ import java.util.Optional; public class AsteriskSerializer extends StdSerializer implements ContextualSerializer { + public static final String[] NON_SENSITIVE_ADDRESSES = {"", "0.0.0.0", "localhost", "127.0.0.1", "auto", "unknown"}; + public static boolean showSensitive = false; @Target({ElementType.FIELD}) @@ -91,11 +93,11 @@ public class AsteriskSerializer extends StdSerializer implements Context } private boolean isSensitiveIp(String ip) { - if (ip.equalsIgnoreCase("localhost") || ip.equalsIgnoreCase("auto")) { - // `auto` should not be shown unless there is an obscure issue with setting the localhost address - return false; + for (String address : NON_SENSITIVE_ADDRESSES) { + if (address.equalsIgnoreCase(ip)) { + return false; + } } - - return !ip.isEmpty() && !ip.equals("0.0.0.0") && !ip.equals("127.0.0.1"); + return true; } } 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 d39c0d696..49178f033 100644 --- a/core/src/main/java/org/geysermc/geyser/text/ChatColor.java +++ b/core/src/main/java/org/geysermc/geyser/text/ChatColor.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.text; public class ChatColor { + public static final String ANSI_RESET = (char) 0x1b + "[0m"; public static final char ESCAPE = '§'; public static final String BLACK = ESCAPE + "0"; @@ -64,7 +65,7 @@ public class ChatColor { string = string.replace(ITALIC, (char) 0x1b + "[3m"); string = string.replace(UNDERLINE, (char) 0x1b + "[4m"); string = string.replace(STRIKETHROUGH, (char) 0x1b + "[9m"); - string = string.replace(RESET, (char) 0x1b + "[0m"); + string = string.replace(RESET, ANSI_RESET); string = string.replace(BLACK, (char) 0x1b + "[0;30m"); string = string.replace(DARK_BLUE, (char) 0x1b + "[0;34m"); string = string.replace(DARK_GREEN, (char) 0x1b + "[0;32m"); @@ -83,19 +84,4 @@ public class ChatColor { string = string.replace(WHITE, (char) 0x1b + "[37;1m"); return string; } - - public String translateAlternateColorCodes(char color, String message) { - return message.replace(color, ESCAPE); - } - - /** - * Remove all colour formatting tags from a message - * - * @param message Message to remove colour tags from - * - * @return The sanitised message - */ - public static String stripColors(String message) { - return message = message.replaceAll("(&([a-fk-or0-9]))","").replaceAll("(§([a-fk-or0-9]))","").replaceAll("s/\\x1b\\[[0-9;]*[a-zA-Z]//g",""); - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/text/ChatTypeEntry.java b/core/src/main/java/org/geysermc/geyser/text/ChatTypeEntry.java index ad2514e09..af965ba8a 100644 --- a/core/src/main/java/org/geysermc/geyser/text/ChatTypeEntry.java +++ b/core/src/main/java/org/geysermc/geyser/text/ChatTypeEntry.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.text; -import com.github.steveice10.mc.protocol.data.game.BuiltinChatType; +import com.github.steveice10.mc.protocol.data.game.chat.BuiltinChatType; import com.nukkitx.protocol.bedrock.packet.TextPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -34,9 +34,7 @@ import javax.annotation.Nullable; public record ChatTypeEntry(@Nonnull TextPacket.Type bedrockChatType, @Nullable TextDecoration textDecoration) { private static final ChatTypeEntry CHAT = new ChatTypeEntry(TextPacket.Type.CHAT, null); - private static final ChatTypeEntry SYSTEM = new ChatTypeEntry(TextPacket.Type.CHAT, null); - private static final ChatTypeEntry TIP = new ChatTypeEntry(TextPacket.Type.CHAT, null); - private static final ChatTypeEntry RAW = new ChatTypeEntry(TextPacket.Type.CHAT, null); + private static final ChatTypeEntry RAW = new ChatTypeEntry(TextPacket.Type.RAW, null); /** * Apply defaults to a map so it isn't empty in the event a chat message is sent before the login packet. @@ -46,12 +44,11 @@ public record ChatTypeEntry(@Nonnull TextPacket.Type bedrockChatType, @Nullable // But, the only way this happens is if a chat message is sent to us before the login packet, which is rare. // So we'll just make sure chat ends up in the right place. chatTypes.put(BuiltinChatType.CHAT.ordinal(), CHAT); - chatTypes.put(BuiltinChatType.SYSTEM.ordinal(), SYSTEM); - chatTypes.put(BuiltinChatType.GAME_INFO.ordinal(), TIP); chatTypes.put(BuiltinChatType.SAY_COMMAND.ordinal(), RAW); - chatTypes.put(BuiltinChatType.MSG_COMMAND.ordinal(), RAW); - chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND.ordinal(), RAW); + chatTypes.put(BuiltinChatType.MSG_COMMAND_INCOMING.ordinal(), RAW); + chatTypes.put(BuiltinChatType.MSG_COMMAND_OUTGOING.ordinal(), RAW); + chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND_INCOMING.ordinal(), RAW); + chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND_OUTGOING.ordinal(), RAW); chatTypes.put(BuiltinChatType.EMOTE_COMMAND.ordinal(), RAW); - chatTypes.put(BuiltinChatType.TELLRAW_COMMAND.ordinal(), RAW); } } 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 86e015c0f..340674119 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.text; +import it.unimi.dsi.fastutil.objects.ObjectArrays; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -173,6 +174,16 @@ public class GeyserLocale { return localeProp.isEmpty() ? null : locale; } + /** + * Get a formatted language string with the default locale for Geyser + * + * @param key Language string to translate + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getLocaleStringLog(String key) { + return getLocaleStringLog(key, ObjectArrays.EMPTY_ARRAY); + } + /** * Get a formatted language string with the default locale for Geyser * @@ -184,6 +195,17 @@ public class GeyserLocale { return getPlayerLocaleString(key, getDefaultLocale(), values); } + /** + * Get a formatted language string with the given locale for Geyser + * + * @param key Language string to translate + * @param locale Locale to translate to + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getPlayerLocaleString(String key, String locale) { + return getPlayerLocaleString(key, locale, ObjectArrays.EMPTY_ARRAY); + } + /** * Get a formatted language string with the given locale for Geyser * diff --git a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java index 296cacaf5..121e1b2b9 100644 --- a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java +++ b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java @@ -46,14 +46,16 @@ public final class TextDecoration { CompoundTag styleTag = tag.get("style"); Style.Builder builder = Style.style(); - StringTag color = styleTag.get("color"); - if (color != null) { - builder.color(NamedTextColor.NAMES.value(color.getValue())); - } - //TODO implement the rest - Tag italic = styleTag.get("italic"); - if (italic != null && ((Number) italic.getValue()).byteValue() == (byte) 1) { - builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); + if (styleTag != null) { + StringTag color = styleTag.get("color"); + if (color != null) { + builder.color(NamedTextColor.NAMES.value(color.getValue())); + } + //TODO implement the rest + Tag italic = styleTag.get("italic"); + if (italic != null && ((Number) italic.getValue()).byteValue() == (byte) 1) { + builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); + } } style = builder.build(); @@ -88,6 +90,6 @@ public final class TextDecoration { public enum Parameter { CONTENT, SENDER, - TEAM_NAME + TARGET } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java index c1fabcf0f..a24178161 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java @@ -65,8 +65,8 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - holder.prepareInventory(this, session, inventory); + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return holder.prepareInventory(this, session, inventory); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java index 956fdeae0..52e542b7b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java @@ -59,10 +59,12 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { CraftRecipeOptionalStackRequestActionData data = (CraftRecipeOptionalStackRequestActionData) request.getActions()[0]; AnvilContainer container = (AnvilContainer) inventory; - // Required as of 1.18.30 - FilterTextPackets no longer appear to be sent - String name = request.getFilterStrings()[data.getFilteredStringIndex()]; - if (!Objects.equals(name, container.getNewName())) { - container.checkForRename(session, name); + if (request.getFilterStrings().length != 0) { + // Required as of 1.18.30 - FilterTextPackets no longer appear to be sent + String name = request.getFilterStrings()[data.getFilteredStringIndex()]; + if (!Objects.equals(name, container.getNewName())) { // TODO is this still necessary after pre-1.19.50 support is dropped? + container.checkForRename(session, name); + } } return super.translateRequest(session, inventory, request); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java index 4dac5e86f..304b8ef00 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java @@ -62,7 +62,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator @Override public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { if (!((BeaconContainer) inventory).isUsingRealBlock()) { - InventoryUtils.closeInventory(session, inventory.getId(), false); + InventoryUtils.closeInventory(session, inventory.getJavaId(), false); return; } super.openInventory(translator, session, inventory); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java index 60e0e6973..69ad41f97 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java @@ -43,7 +43,7 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator public void openInventory(GeyserSession session, Inventory inventory) { super.openInventory(session, inventory); ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setWindowId((byte) inventory.getBedrockId()); dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL); dataPacket.setValue(20); session.sendUpstreamPacket(dataPacket); @@ -52,7 +52,7 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator @Override public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setWindowId((byte) inventory.getBedrockId()); switch (key) { case 0: dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_BREW_TIME); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java index 800b35901..97946b59c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java @@ -127,7 +127,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla // Slot should be determined as 0, 1, or 2 return rejectRequest(request); } - ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot); + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), javaSlot); session.sendDownstreamPacket(packet); return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet())); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java index df7796d0c..3ca8f165f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java @@ -52,7 +52,7 @@ public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTransla @Override public void openInventory(GeyserSession session, Inventory inventory) { ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) inventory.getId()); + containerOpenPacket.setId((byte) inventory.getBedrockId()); // Required for opening the real block - otherwise, if the container type is incorrect, it refuses to open containerOpenPacket.setType(((Generic3X3Container) inventory).isDropper() ? com.nukkitx.protocol.bedrock.data.inventory.ContainerType.DROPPER : com.nukkitx.protocol.bedrock.data.inventory.ContainerType.DISPENSER); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); 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 6f4ca7ee4..e6cc010f5 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 @@ -101,7 +101,7 @@ public abstract class InventoryTranslator { public final int size; - public abstract void prepareInventory(GeyserSession session, Inventory inventory); + public abstract boolean prepareInventory(GeyserSession session, Inventory inventory); public abstract void openInventory(GeyserSession session, Inventory inventory); public abstract void closeInventory(GeyserSession session, Inventory inventory); public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value); @@ -201,7 +201,7 @@ public abstract class InventoryTranslator { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { if (session.getGeyser().getConfig().isDebugMode()) { - session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name()); + session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.bedrockUsername()); dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); } return rejectRequest(request); @@ -292,7 +292,7 @@ public abstract class InventoryTranslator { if (!(checkNetId(session, inventory, source) && checkNetId(session, inventory, destination))) { if (session.getGeyser().getConfig().isDebugMode()) { - session.getGeyser().getLogger().error("DEBUG: About to reject SWAP request made by " + session.name()); + session.getGeyser().getLogger().error("DEBUG: About to reject SWAP request made by " + session.bedrockUsername()); dumpStackRequestDetails(session, inventory, source, destination); } return rejectRequest(request); @@ -804,7 +804,7 @@ public abstract class InventoryTranslator { */ //TODO: compatibility for simulated inventory (ClickPlan) private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) { - int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable temp slot + int offset = inventory.getJavaId() == 0 ? 1 : 0; //offhand is not a viable temp slot HashSet itemBlacklist = new HashSet<>(slotBlacklist.length + 1); itemBlacklist.add(item); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java index e08065a22..59fe81751 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java @@ -55,7 +55,8 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return true; } @Override @@ -99,10 +100,10 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { LecternContainer lecternContainer = (LecternContainer) inventory; if (session.isDroppingLecternBook()) { // We have to enter the inventory GUI to eject the book - ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), 3); + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), 3); session.sendDownstreamPacket(packet); session.setDroppingLecternBook(false); - InventoryUtils.closeInventory(session, inventory.getId(), false); + InventoryUtils.closeInventory(session, inventory.getJavaId(), false); } else if (lecternContainer.getBlockEntityTag() == null) { CompoundTag tag = book.getNbt(); // Position has to be the last interacted position... right? @@ -150,9 +151,9 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); session.getLecternCache().add(position); // Close the window - we will reopen it once the client has this data synced - ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(lecternContainer.getId()); + ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(lecternContainer.getJavaId()); session.sendDownstreamPacket(closeWindowPacket); - InventoryUtils.closeInventory(session, inventory.getId(), false); + InventoryUtils.closeInventory(session, inventory.getJavaId(), false); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java index 5a237b72a..d44ff589a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java @@ -147,7 +147,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { // Java's formula: 4 * row + col // And the Java loom window has a fixed row/width of four // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :) - ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), index); + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), index); session.sendDownstreamPacket(packet); GeyserItemStack inputCopy = inventory.getItem(0).copy(1); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java index 5e9c99ae9..857b96e55 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java @@ -94,7 +94,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { MerchantContainer merchantInventory = (MerchantContainer) inventory; if (merchantInventory.getVillager() == null) { long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); @@ -117,6 +117,8 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { merchantInventory.setVillager(villager); } + + return true; } @Override 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 e2349e5a5..8432b0253 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 @@ -371,7 +371,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } } default -> { - session.getGeyser().getLogger().error("Unknown crafting state induced by " + session.name()); + session.getGeyser().getLogger().error("Unknown crafting state induced by " + session.bedrockUsername()); return rejectRequest(request); } } @@ -514,7 +514,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return true; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java index e0e2e27bd..1668e3a93 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java @@ -68,7 +68,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl ItemStack javaOutput = craftingData.output(); // Getting the index of the item in the Java stonecutter list - ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), button); + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), button); session.sendDownstreamPacket(packet); container.setStonecutterButton(button); if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java index 94873eaf8..fa20e6dbb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java @@ -40,6 +40,7 @@ import org.geysermc.geyser.level.block.DoubleChestValue; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator; +import org.geysermc.geyser.util.InventoryUtils; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { private final int defaultJavaBlockState; @@ -50,7 +51,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { // See BlockInventoryHolder - same concept there except we're also dealing with a specific block state if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); @@ -76,11 +77,16 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { dataPacket.setData(tag.build()); dataPacket.setBlockPosition(session.getLastInteractionBlockPosition()); session.sendUpstreamPacket(dataPacket); - return; + + return true; } } - Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); + Vector3i position = InventoryUtils.findAvailableWorldSpace(session); + if (position == null) { + return false; + } + Vector3i pairPosition = position.add(Vector3i.UNIT_X); int bedrockBlockId = session.getBlockMappings().getBedrockBlockId(defaultJavaBlockState); @@ -125,12 +131,14 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { session.sendUpstreamPacket(dataPacket); inventory.setHolderPosition(position); + + return true; } @Override public void openInventory(GeyserSession session, Inventory inventory) { ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) inventory.getId()); + containerOpenPacket.setId((byte) inventory.getBedrockId()); containerOpenPacket.setType(ContainerType.CONTAINER); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); @@ -143,7 +151,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { // No need to reset a block since we didn't change any blocks // But send a container close packet because we aren't destroying the original. ContainerClosePacket packet = new ContainerClosePacket(); - packet.setId((byte) inventory.getId()); + packet.setId((byte) inventory.getBedrockId()); packet.setUnknownBool0(true); //TODO needs to be changed in Protocol to "server-side" or something session.sendUpstreamPacket(packet); return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java index 41e7bfb9f..ae914ed8c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java @@ -52,8 +52,8 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - holder.prepareInventory(this, session, inventory); + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return holder.prepareInventory(this, session, inventory); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java index 19e02ee4e..764ab0a33 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java @@ -43,7 +43,7 @@ public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockIn @Override public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setWindowId((byte) inventory.getBedrockId()); switch (key) { case 0: dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java index 0ad6ba137..538133e0e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java @@ -40,7 +40,8 @@ public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTran } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return true; } @Override 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 6fee82e18..4930c6b60 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 @@ -105,7 +105,7 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven } InventoryContentPacket horseContentsPacket = new InventoryContentPacket(); - horseContentsPacket.setContainerId(inventory.getId()); + horseContentsPacket.setContainerId(inventory.getBedrockId()); horseContentsPacket.setContents(Arrays.asList(horseItems)); session.sendUpstreamPacket(horseContentsPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java new file mode 100644 index 000000000..82a8c9de1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java @@ -0,0 +1,113 @@ +/* + * 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.translator.inventory.item; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.List; +import java.util.OptionalInt; + +/** + * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. + */ +final class CustomItemTranslator { + + static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { + if (nbt == null) { + return -1; + } + List> customMappings = mapping.getCustomItemOptions(); + if (customMappings.isEmpty()) { + return -1; + } + + int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; + boolean checkDamage = mapping.getMaxDamage() > 0; + int damage = !checkDamage ? 0 : nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; + boolean unbreakable = checkDamage && !isDamaged(nbt, damage); + + for (ObjectIntPair mappingTypes : customMappings) { + CustomItemOptions options = mappingTypes.key(); + + // Code note: there may be two or more conditions that a custom item must follow, hence the "continues" + // here with the return at the end. + + // Implementation details: Java's predicate system works exclusively on comparing float numbers. + // A value doesn't necessarily have to match 100%; it just has to be the first to meet all predicate conditions. + // This is also why the order of iteration is important as the first to match will be the chosen display item. + // For example, if CustomModelData is set to 2f as the requirement, then the NBT can be any number greater or equal (2, 3, 4...) + // The same behavior exists for Damage (in fraction form instead of whole numbers), + // and Damaged/Unbreakable handles no damage as 0f and damaged as 1f. + + if (checkDamage) { + if (unbreakable && options.unbreakable() == TriState.FALSE) { + continue; + } + + OptionalInt damagePredicate = options.damagePredicate(); + if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { + continue; + } + } else { + if (options.unbreakable() != TriState.NOT_SET || options.damagePredicate().isPresent()) { + // These will never match on this item. 1.19.2 behavior + // Maybe move this to CustomItemRegistryPopulator since it'll be the same for every item? If so, add a test. + continue; + } + } + + OptionalInt customModelDataOption = options.customModelData(); + if (customModelDataOption.isPresent() && customModelData < customModelDataOption.getAsInt()) { + continue; + } + + return mappingTypes.valueInt(); + } + return -1; + } + + /* These two functions are based off their Mojmap equivalents from 1.19.2 */ + + private static boolean isDamaged(CompoundTag nbt, int damage) { + return isDamagableItem(nbt) && damage > 0; + } + + private static boolean isDamagableItem(CompoundTag nbt) { + // mapping.getMaxDamage > 0 should also be checked (return false if not true) but we already check prior to this function + Tag unbreakableTag = nbt.get("Unbreakable"); + // Tag must either not be present or be set to false + return unbreakableTag == null || !(unbreakableTag.getValue() instanceof Number number) || number.byteValue() == 0; + } + + private CustomItemTranslator() { + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 1a8062c18..0f83444ba 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -34,7 +34,6 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; @@ -185,13 +184,15 @@ public abstract class ItemTranslator { if (nbt != null) { // Translate the canDestroy and canPlaceOn Java NBT ListTag canDestroy = nbt.get("CanDestroy"); - String[] canBreak = new String[0]; ListTag canPlaceOn = nbt.get("CanPlaceOn"); - String[] canPlace = new String[0]; - canBreak = getCanModify(canDestroy, canBreak); - canPlace = getCanModify(canPlaceOn, canPlace); - builder.canBreak(canBreak); - builder.canPlace(canPlace); + String[] canBreak = getCanModify(canDestroy); + String[] canPlace = getCanModify(canPlaceOn); + if (canBreak != null) { + builder.canBreak(canBreak); + } + if (canPlace != null) { + builder.canPlace(canPlace); + } } return builder.build(); @@ -253,12 +254,11 @@ public abstract class ItemTranslator { * In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself. * * @param canModifyJava the list of items in Java - * @param canModifyBedrock the empty list of items in Bedrock * @return the new list of items in Bedrock */ - private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) { + private static String[] getCanModify(ListTag canModifyJava) { if (canModifyJava != null && canModifyJava.size() > 0) { - canModifyBedrock = new String[canModifyJava.size()]; + String[] canModifyBedrock = new String[canModifyJava.size()]; for (int i = 0; i < canModifyBedrock.length; i++) { // Get the Java identifier of the block that can be placed String block = ((StringTag) canModifyJava.get(i)).getValue(); @@ -268,8 +268,9 @@ public abstract class ItemTranslator { // This will unfortunately be limited - for example, beds and banners will be translated weirdly canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", ""); } + return canModifyBedrock; } - return canModifyBedrock; + return null; } /** @@ -291,7 +292,7 @@ public abstract class ItemTranslator { } } - int customItemId = getCustomItem(itemStack.getNbt(), mapping); + int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping); if (customItemId == -1) { // No custom item return itemId; @@ -344,66 +345,26 @@ public abstract class ItemTranslator { } protected NbtMap translateNbtToBedrock(CompoundTag tag) { - NbtMapBuilder builder = NbtMap.builder(); - if (tag.getValue() != null && !tag.getValue().isEmpty()) { - for (String str : tag.getValue().keySet()) { - Tag javaTag = tag.get(str); + if (!tag.getValue().isEmpty()) { + NbtMapBuilder builder = NbtMap.builder(); + for (Tag javaTag : tag.values()) { Object translatedTag = translateToBedrockNBT(javaTag); if (translatedTag == null) continue; builder.put(javaTag.getName(), translatedTag); } + return builder.build(); } - return builder.build(); + return NbtMap.EMPTY; } private Object translateToBedrockNBT(Tag tag) { - if (tag instanceof ByteArrayTag) { - return ((ByteArrayTag) tag).getValue(); - } - - if (tag instanceof ByteTag) { - return ((ByteTag) tag).getValue(); - } - - if (tag instanceof DoubleTag) { - return ((DoubleTag) tag).getValue(); - } - - if (tag instanceof FloatTag) { - return ((FloatTag) tag).getValue(); - } - - if (tag instanceof IntArrayTag) { - return ((IntArrayTag) tag).getValue(); - } - - if (tag instanceof IntTag) { - return ((IntTag) tag).getValue(); - } - - if (tag instanceof LongArrayTag) { - //Long array tag does not exist in BE - //LongArrayTag longArrayTag = (LongArrayTag) tag; - //return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); - return null; - } - - if (tag instanceof LongTag) { - return ((LongTag) tag).getValue(); - } - - if (tag instanceof ShortTag) { - return ((ShortTag) tag).getValue(); - } - - if (tag instanceof StringTag) { - return ((StringTag) tag).getValue(); + if (tag instanceof CompoundTag compoundTag) { + return translateNbtToBedrock(compoundTag); } if (tag instanceof ListTag listTag) { - List tagList = new ArrayList<>(); for (Tag value : listTag) { tagList.add(translateToBedrockNBT(value)); @@ -415,11 +376,14 @@ public abstract class ItemTranslator { return new NbtList(type, tagList); } - if (tag instanceof CompoundTag compoundTag) { - return translateNbtToBedrock(compoundTag); + if (tag instanceof LongArrayTag) { + //Long array tag does not exist in BE + //LongArrayTag longArrayTag = (LongArrayTag) tag; + //return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + return null; } - return null; + return tag.getValue(); } private CompoundTag translateToJavaNBT(String name, NbtMap tag) { @@ -559,7 +523,7 @@ public abstract class ItemTranslator { * Translates the custom model data of an item */ private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) { - int bedrockId = getCustomItem(nbt, mapping); + int bedrockId = CustomItemTranslator.getCustomItem(nbt, mapping); if (bedrockId != -1) { builder.id(bedrockId); builder.blockRuntimeId(0); @@ -596,81 +560,34 @@ public abstract class ItemTranslator { } } - private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { - if (nbt == null) { - return -1; - } - Object2IntMap customMappings = mapping.getCustomItemOptions(); - if (customMappings.isEmpty()) { - return -1; - } - int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; - TriState unbreakable = TriState.fromBoolean(nbt.get("Unbreakable") instanceof ByteTag unbreakableTag && unbreakableTag.getValue() == 1); - int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; - for (Object2IntMap.Entry mappingTypes : customMappings.object2IntEntrySet()) { - CustomItemOptions options = mappingTypes.getKey(); - - TriState unbreakableOption = options.unbreakable(); - if (unbreakableOption == unbreakable) { // Implementation note: if the option is NOT_SET then this comparison will always be false because of how the item unbreaking TriState is created - return mappingTypes.getIntValue(); + private static CustomSkull getCustomSkull(GeyserSession session, CompoundTag nbt) { + if (nbt != null && nbt.contains("SkullOwner")) { + if (!(nbt.get("SkullOwner") instanceof CompoundTag skullOwner)) { + // It's a username give up d: + return null; + } + SkinManager.GameProfileData data = SkinManager.GameProfileData.from(skullOwner); + if (data == null) { + session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt); + return null; } - OptionalInt customModelDataOption = options.customModelData(); - if (customModelDataOption.isPresent() && customModelDataOption.getAsInt() == customModelData) { - return mappingTypes.getIntValue(); - } - - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damagePredicate.getAsInt() == damage) { - return mappingTypes.getIntValue(); - } + String skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1); + return BlockRegistries.CUSTOM_SKULLS.get(skinHash); } - return -1; + return null; } - /** - * Checks if an {@link ItemStack} is equal to another item stack - * - * @param itemStack the item stack to check - * @param equalsItemStack the item stack to check if equal to - * @param checkAmount if the amount should be taken into account - * @param trueIfAmountIsGreater if this should return true if the amount of the - * first item stack is greater than that of the second - * @param checkNbt if NBT data should be checked - * @return if an item stack is equal to another item stack - */ - public boolean equals(ItemStack itemStack, ItemStack equalsItemStack, boolean checkAmount, boolean trueIfAmountIsGreater, boolean checkNbt) { - if (itemStack.getId() != equalsItemStack.getId()) { - return false; - } - if (checkAmount) { - if (trueIfAmountIsGreater) { - if (itemStack.getAmount() < equalsItemStack.getAmount()) { - return false; - } - } else { - if (itemStack.getAmount() != equalsItemStack.getAmount()) { - return false; - } - } - } + private static void translatePlayerHead(GeyserSession session, CompoundTag nbt, ItemData.Builder builder) { + CustomSkull customSkull = getCustomSkull(session, nbt); + if (customSkull != null) { + CustomBlockData customBlockData = customSkull.getCustomBlockData(); + int itemId = session.getItemMappings().getCustomBlockItemIds().getInt(customBlockData); + int blockRuntimeId = session.getBlockMappings().getCustomBlockStateIds().getInt(customBlockData.defaultBlockState()); - if (!checkNbt) { - return true; + builder.id(itemId); + builder.blockRuntimeId(blockRuntimeId); } - if ((itemStack.getNbt() == null || itemStack.getNbt().isEmpty()) && (equalsItemStack.getNbt() != null && !equalsItemStack.getNbt().isEmpty())) { - return false; - } - - if ((itemStack.getNbt() != null && !itemStack.getNbt().isEmpty() && (equalsItemStack.getNbt() == null || !equalsItemStack.getNbt().isEmpty()))) { - return false; - } - - if (itemStack.getNbt() != null && equalsItemStack.getNbt() != null) { - return itemStack.getNbt().equals(equalsItemStack.getNbt()); - } - - return true; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java index 0a4ca0686..642a43123 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java @@ -39,8 +39,8 @@ public class CrossbowTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { - if (itemTag.get("ChargedProjectiles") != null) { - ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); + ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); + if (chargedProjectiles != null) { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java index 2fb5ec6cb..5e5920b4a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java @@ -32,13 +32,12 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import java.util.Arrays; import java.util.List; @ItemRemapper public class LeatherArmorTranslator extends NbtItemStackTranslator { - private static final List ITEMS = Arrays.asList("minecraft:leather_helmet", "minecraft:leather_chestplate", + private static final List ITEMS = List.of("minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots", "minecraft:leather_horse_armor"); @Override @@ -47,10 +46,9 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator { if (displayTag == null) { return; } - IntTag color = displayTag.get("color"); + IntTag color = displayTag.remove("color"); if (color != null) { itemTag.put(new IntTag("customColor", color.getValue())); - displayTag.remove("color"); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java index 3e47bfc37..04b39deeb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java @@ -45,7 +45,7 @@ import org.geysermc.geyser.level.chunk.bitarray.BitArrayVersion; import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.util.JavaCodecEntry; +import org.geysermc.geyser.util.JavaCodecUtil; import org.geysermc.geyser.util.MathUtils; // Array index formula by https://wiki.vg/Chunk_Format @@ -59,7 +59,7 @@ public class BiomeTranslator { ListTag serverBiomes = worldGen.get("value"); session.setBiomeGlobalPalette(MathUtils.getGlobalPaletteForSize(serverBiomes.size())); - for (CompoundTag biomeTag : JavaCodecEntry.iterateAsTag(worldGen)) { + for (CompoundTag biomeTag : JavaCodecUtil.iterateAsTag(worldGen)) { String javaIdentifier = ((StringTag) biomeTag.get("name")).getValue(); int bedrockId = Registries.BIOME_IDENTIFIERS.get().getOrDefault(javaIdentifier, 0); int javaId = ((IntTag) biomeTag.get("id")).getValue(); 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 6ec0effca..e36ad2d22 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 @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.geyser.network.GameProtocol; @@ -40,7 +41,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { ListTag items = tag.get("Items"); int i = 1; - for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { + for (Tag itemTag : items.getValue()) { builder.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java index bd3f96836..1b4fd6a10 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java @@ -32,7 +32,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.SignUtils; -@BlockEntity(type = BlockEntityType.SIGN) +@BlockEntity(type = {BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN}) public class SignBlockEntityTranslator extends BlockEntityTranslator { /** * Maps a color stored in a sign's Color tag to its ARGB value. @@ -88,6 +88,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signWidth += SignUtils.getCharacterWidth(c); } + // todo 1.20: update for hanging signs (smaller width). Currently OK because bedrock sees hanging signs as normal signs if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { finalSignLine.append(c); } else { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java index 3d11d5ced..2a4711e26 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.geyser.entity.EntityDefinition; @@ -68,16 +69,18 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { CompoundTag spawnData = tag.get("SpawnData"); if (spawnData != null) { - String entityID = (String) ((CompoundTag) spawnData.get("entity")) - .get("id") - .getValue(); - builder.put("EntityIdentifier", entityID); + StringTag idTag = ((CompoundTag) spawnData.get("entity")).get("id"); + if (idTag != null) { + // As of 1.19.3, spawners can be empty + String entityId = idTag.getValue(); + builder.put("EntityIdentifier", entityId); - EntityDefinition definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityID); - if (definition != null) { - builder.put("DisplayEntityWidth", definition.width()); - builder.put("DisplayEntityHeight", definition.height()); - builder.put("DisplayEntityScale", 1.0f); + EntityDefinition definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityId); + if (definition != null) { + builder.put("DisplayEntityWidth", definition.width()); + builder.put("DisplayEntityHeight", definition.height()); + builder.put("DisplayEntityScale", 1.0f); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java index 641161127..aabc39e12 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java @@ -25,10 +25,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; import com.nukkitx.protocol.bedrock.data.AdventureSetting; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -40,19 +37,6 @@ public class BedrockAdventureSettingsTranslator extends PacketTranslator SignUtils.JAVA_CHARACTER_WIDTH_MAX) { // We need to apply some more logic if we went over the character width max 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 3301f7b9f..24fc8396f 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 @@ -29,6 +29,7 @@ import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -38,16 +39,14 @@ public class BedrockCommandRequestTranslator extends PacketTranslator currentJavaPage) { for (int i = currentJavaPage; i < newJavaPage; i++) { - ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getId(), 2); + ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getJavaId(), 2); session.sendDownstreamPacket(clickButtonPacket); } } else { for (int i = currentJavaPage; i > newJavaPage; i--) { - ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getId(), 1); + ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getJavaId(), 1); session.sendDownstreamPacket(clickButtonPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java new file mode 100644 index 000000000..fe8150d40 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java @@ -0,0 +1,73 @@ +/* + * 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.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.nukkitx.protocol.bedrock.data.Ability; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.RequestAbilityPacket; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +/** + * Replaces the AdventureSettingsPacket completely in 1.19.30. + */ +@Translator(packet = RequestAbilityPacket.class) +public class BedrockRequestAbilityTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, RequestAbilityPacket packet) { + // Gatekeep to 1.19.30 so older versions don't fire twice + if (!GameProtocol.supports1_19_30(session)) { + return; + } + + if (packet.getAbility() == Ability.FLYING) { + handle(session, packet.isBoolValue()); + } + } + + //FIXME remove after pre-1.19.30 support is dropped and merge into main method + static void handle(GeyserSession session, boolean isFlying) { + if (!isFlying && session.getGameMode() == GameMode.SPECTATOR) { + // We should always be flying in spectator mode + session.sendAdventureSettings(); + return; + } else if (isFlying && 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 + session.sendAdventureSettings(); + return; + } + + session.setFlying(isFlying); + ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(isFlying); + session.sendDownstreamPacket(abilitiesPacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index 8b86be69b..121bfd065 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java @@ -43,8 +43,8 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat if (session.remoteServer().authType() == AuthType.ONLINE) { if (!session.isLoggedIn()) { - if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.name())) { - if (session.getGeyser().refreshTokenFor(session.name()) == null) { + if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) { + if (session.getGeyser().refreshTokenFor(session.bedrockUsername()) == null) { LoginEncryptionUtils.buildAndShowConsentWindow(session); } else { // If the refresh token is not null and we're here, then the refresh token expired diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java index e52fac371..00eb95455 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -37,21 +37,7 @@ public class BedrockTextTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, TextPacket packet) { - String message = packet.getMessage(); - - // The order here is important - strip out illegal characters first, then check if it's blank - // (in case the message is blank after removing) - if (message.indexOf(ChatColor.ESCAPE) != -1) { - // Filter out all escape characters - Java doesn't let you type these - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < message.length(); i++) { - char c = message.charAt(i); - if (c != ChatColor.ESCAPE) { - builder.append(c); - } - } - message = builder.toString(); - } + String message = MessageTranslator.convertToPlainText(packet.getMessage()); if (message.isBlank()) { // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 5001fc2d2..c728390d6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -41,6 +41,7 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -74,6 +75,9 @@ public class BedrockActionTranslator extends PacketTranslator { if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) { // Activate the workaround - we should trigger the offhand now ServerboundPlayerActionPacket swapHandsPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, Vector3i.ZERO, - Direction.DOWN, session.getWorldCache().nextPredictionSequence()); + Direction.DOWN, 0); session.sendDownstreamPacket(swapHandsPacket); if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { 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 b9f593961..6078b7ebd 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 @@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.math.vector.Vector3d; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; @@ -49,17 +48,6 @@ public class BedrockMovePlayerTranslator extends PacketTranslator 300) { - session.getGeyser().getLogger().debug(ChatColor.RED + session.name() + " moved too quickly." + + session.getGeyser().getLogger().debug(ChatColor.RED + session.bedrockUsername() + " moved too quickly." + " current position: " + currentPosition + ", new position: " + newPosition); return false; 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 54ffa0e6d..11311b63c 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 @@ -44,6 +44,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import lombok.Getter; import lombok.ToString; import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent; import org.geysermc.geyser.command.GeyserCommandManager; @@ -132,12 +133,12 @@ public class JavaCommandsTranslator extends PacketTranslator= 1) { for (int childIndex : node.getChildIndices()) { - commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]); + commandArgs.computeIfAbsent(nodeIndex, ($) -> new ArrayList<>()).add(nodes[childIndex]); } } @@ -198,7 +199,7 @@ public class JavaCommandsTranslator extends PacketTranslator= 1) { // Create the root param node and build all the children ParamInfo rootParam = new ParamInfo(commandNode, null); - rootParam.buildChildren(session, allNodes); + rootParam.buildChildren(new CommandBuilderContext(session), allNodes); List treeData = rootParam.getTree(); @@ -211,11 +212,11 @@ public class JavaCommandsTranslator extends PacketTranslator CommandParam.FILE_PATH; case BOOL -> ENUM_BOOLEAN; case OPERATION -> CommandParam.OPERATOR; // ">=", "==", etc - case BLOCK_STATE -> BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.get().keySet().toArray(new String[0]); - case ITEM_STACK -> session.getItemMappings().getItemNames(); - case ITEM_ENCHANTMENT -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; - case ENTITY_SUMMON -> Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]); + case BLOCK_STATE -> context.getBlockStates(); + case ITEM_STACK -> context.session.getItemMappings().getItemNames(); case COLOR -> VALID_COLORS; case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS; - case MOB_EFFECT -> ALL_EFFECT_IDENTIFIERS; - case RESOURCE, RESOURCE_OR_TAG -> { - String resource = ((ResourceProperties) node.getProperties()).getRegistryKey(); - if (resource.equals("minecraft:attribute")) { - yield ATTRIBUTES; - } else { - yield CommandParam.STRING; - } - } + case RESOURCE -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false); + case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true); + case DIMENSION -> context.session.getLevels(); + default -> CommandParam.STRING; + }; + } + + private static Object handleResource(CommandBuilderContext context, String resource, boolean tags) { + return switch (resource) { + case "minecraft:attribute" -> ATTRIBUTES; + case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; + case "minecraft:entity_type" -> context.getEntityTypes(); + case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS; + case "minecraft:worldgen/biome" -> tags ? context.getBiomesWithTags() : context.getBiomes(); default -> CommandParam.STRING; }; } @@ -254,7 +258,55 @@ public class JavaCommandsTranslator extends PacketTranslator diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java index 2c52ac5a5..aaedfa443 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -72,12 +72,19 @@ public class JavaCustomPayloadTranslator extends PacketTranslator { - byte[] raw = response.getBytes(StandardCharsets.UTF_8); - byte[] finalData = new byte[raw.length + 2]; + byte[] finalData; + if (response == null) { + // Response data can be null as of 1.19.20 (same behaviour as empty response data) + // Only need to send the form id + finalData = new byte[]{data[1], data[2]}; + } else { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + finalData = new byte[raw.length + 2]; - finalData[0] = data[1]; - finalData[1] = data[2]; - System.arraycopy(raw, 0, finalData, 2, raw.length); + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + } session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData)); }); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java similarity index 62% rename from core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java index 00894bd8b..2ad45fe52 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java @@ -23,27 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.translator.protocol.java.level; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundCustomSoundPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDisguisedChatPacket; 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.SoundUtils; +import org.geysermc.geyser.translator.text.MessageTranslator; -@Translator(packet = ClientboundCustomSoundPacket.class) -public class JavaCustomSoundTranslator extends PacketTranslator { +@Translator(packet = ClientboundDisguisedChatPacket.class) +public class JavaDisguisedChatTranslator extends PacketTranslator { @Override - public void translate(GeyserSession session, ClientboundCustomSoundPacket packet) { - PlaySoundPacket playSoundPacket = new PlaySoundPacket(); - playSoundPacket.setSound(SoundUtils.translatePlaySound(packet.getSound())); - playSoundPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - playSoundPacket.setVolume(packet.getVolume()); - playSoundPacket.setPitch(packet.getPitch()); - - session.sendUpstreamPacket(playSoundPacket); + public void translate(GeyserSession session, ClientboundDisguisedChatPacket packet) { + MessageTranslator.handleChatPacket(session, packet.getMessage(), packet.getChatType(), packet.getTargetName(), packet.getName()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index 356fe645b..0720963fb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -46,10 +46,30 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator children = component.children(); for (int i = 0; i < children.size(); i++) { if (children.get(i) instanceof TextComponent child && child.content().startsWith("Outdated server!")) { // Reproduced on Paper 1.17.1 - isOutdatedMessage = true; - break; + return true; } } } } } + return false; + } - String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale()); - String disconnectMessage; - if (isOutdatedMessage) { - String locale = session.locale(); - PlatformType platform = session.getGeyser().getPlatformType(); - String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY) ? - "geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server"; - disconnectMessage = GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n' - + GeyserLocale.getPlayerLocaleString("geyser.network.remote.original_disconnect_message", locale, serverDisconnectMessage); - } else { - disconnectMessage = serverDisconnectMessage; - } - - // The client doesn't manually get disconnected so we have to do it ourselves - session.disconnect(disconnectMessage); + private boolean testForMissingProfilePublicKey(Component disconnectReason) { + return disconnectReason instanceof TranslatableComponent component && "multiplayer.disconnect.missing_public_key".equals(component.key()); } @Override 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 b042eae2a..09d117087 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 @@ -25,32 +25,28 @@ package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.data.game.BuiltinChatType; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; -import com.nukkitx.protocol.bedrock.packet.TextPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 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.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatTypeEntry; import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; 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.JavaCodecEntry; +import org.geysermc.geyser.util.JavaCodecUtil; import org.geysermc.geyser.util.PluginMessageUtils; import java.util.Map; @@ -68,30 +64,18 @@ public class JavaLoginTranslator extends PacketTranslator chatTypes = session.getChatTypes(); + Int2ObjectMap chatTypes = session.getChatTypes(); chatTypes.clear(); - for (CompoundTag tag : JavaCodecEntry.iterateAsTag(packet.getRegistry().get("minecraft:chat_type"))) { + for (CompoundTag tag : JavaCodecUtil.iterateAsTag(packet.getRegistry().get("minecraft:chat_type"))) { // The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla. int id = ((IntTag) tag.get("id")).getValue(); CompoundTag element = tag.get("element"); CompoundTag chat = element.get("chat"); TextDecoration textDecoration = null; if (chat != null) { - CompoundTag decorationTag = chat.get("decoration"); - if (decorationTag != null) { - textDecoration = new TextDecoration(decorationTag); - } + textDecoration = new TextDecoration(chat); } - BuiltinChatType type = BuiltinChatType.from(((StringTag) tag.get("name")).getValue()); - // TODO new types? - // The built-in type can be null if custom plugins/mods add in new types - TextPacket.Type bedrockType = type != null ? switch (type) { - case CHAT -> TextPacket.Type.CHAT; - case SYSTEM -> TextPacket.Type.SYSTEM; - case GAME_INFO -> TextPacket.Type.TIP; - default -> TextPacket.Type.RAW; - } : TextPacket.Type.RAW; - chatTypes.put(id, new ChatTypeEntry(bedrockType, textDecoration)); + chatTypes.put(id, textDecoration); } // If the player is already initialized and a join game packet is sent, they @@ -103,6 +87,7 @@ public class JavaLoginTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) { - ChatTypeEntry entry = session.getChatTypes().get(packet.getTypeId()); - - TextPacket textPacket = new TextPacket(); - textPacket.setPlatformChatId(""); - textPacket.setSourceName(""); - textPacket.setXuid(session.getAuthData().xuid()); - textPacket.setType(entry.bedrockChatType()); - - textPacket.setNeedsTranslation(false); - Component message = packet.getUnsignedContent() == null ? packet.getSignedContent() : packet.getUnsignedContent(); - - TextDecoration decoration = entry.textDecoration(); - if (decoration != null) { - // As of 1.19 - do this to apply all the styling for signed messages - // Though, Bedrock cannot care about the signed stuff. - TranslatableComponent.Builder withDecoration = Component.translatable() - .key(decoration.translationKey()) - .style(decoration.style()); - Set parameters = decoration.parameters(); - List args = new ArrayList<>(3); - if (parameters.contains(TextDecoration.Parameter.TEAM_NAME)) { - args.add(packet.getSenderTeamName()); - } - if (parameters.contains(TextDecoration.Parameter.SENDER)) { - args.add(packet.getSenderName()); - } - if (parameters.contains(TextDecoration.Parameter.CONTENT)) { - args.add(message); - } - withDecoration.args(args); - textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.locale())); - } else { - textPacket.setMessage(MessageTranslator.convertMessage(message, session.locale())); - } - - session.sendUpstreamPacket(textPacket); + Component message = packet.getUnsignedContent() == null ? Component.text(packet.getContent()) : packet.getUnsignedContent(); + MessageTranslator.handleChatPacket(session, message, packet.getChatType(), packet.getTargetName(), packet.getName()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java index 3471ce576..45db499f1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java @@ -83,18 +83,14 @@ public class JavaRespawnTranslator extends PacketTranslator, IntSet> squashedOptions = new HashMap<>(); + private ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredient[] ingredients) { + Map, IntSet> squashedOptions = new HashMap<>(); for (int i = 0; i < ingredients.length; i++) { if (ingredients[i].getOptions().length == 0) { - squashedOptions.computeIfAbsent(Collections.singleton(ItemData.AIR), k -> new IntOpenHashSet()).add(i); + squashedOptions.computeIfAbsent(Collections.singleton(ItemDescriptorWithCount.EMPTY), k -> new IntOpenHashSet()).add(i); continue; } Ingredient ingredient = ingredients[i]; - Map> groupedByIds = Arrays.stream(ingredient.getOptions()) - .map(item -> ItemTranslator.translateToBedrock(session, item)) - .collect(Collectors.groupingBy(item -> new GroupedItem(item.getId(), item.getCount(), item.getTag()))); - Set optionSet = new HashSet<>(groupedByIds.size()); - for (Map.Entry> entry : groupedByIds.entrySet()) { + Map> groupedByIds = Arrays.stream(ingredient.getOptions()) + .map(item -> ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, item))) + .collect(Collectors.groupingBy(item -> item == ItemDescriptorWithCount.EMPTY ? new GroupedItem(0, 0) : new GroupedItem(((DefaultDescriptor) item.getDescriptor()).getItemId(), item.getCount()))); + Set optionSet = new HashSet<>(groupedByIds.size()); + for (Map.Entry> entry : groupedByIds.entrySet()) { if (entry.getValue().size() > 1) { GroupedItem groupedItem = entry.getKey(); int idCount = 0; @@ -234,46 +233,42 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator new IntOpenHashSet()).add(i); } int totalCombinations = 1; - for (Set optionSet : squashedOptions.keySet()) { + for (Set optionSet : squashedOptions.keySet()) { totalCombinations *= optionSet.size(); } if (totalCombinations > 500) { - ItemData[] translatedItems = new ItemData[ingredients.length]; + ItemDescriptorWithCount[] translatedItems = new ItemDescriptorWithCount[ingredients.length]; for (int i = 0; i < ingredients.length; i++) { if (ingredients[i].getOptions().length > 0) { - translatedItems[i] = ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]); + translatedItems[i] = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0])); } else { - translatedItems[i] = ItemData.AIR; + translatedItems[i] = ItemDescriptorWithCount.EMPTY; } } - return new ItemData[][]{translatedItems}; + return new ItemDescriptorWithCount[][]{translatedItems}; } - List> sortedSets = new ArrayList<>(squashedOptions.keySet()); + List> sortedSets = new ArrayList<>(squashedOptions.keySet()); sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder())); - ItemData[][] combinations = new ItemData[totalCombinations][ingredients.length]; + ItemDescriptorWithCount[][] combinations = new ItemDescriptorWithCount[totalCombinations][ingredients.length]; int x = 1; - for (Set set : sortedSets) { + for (Set set : sortedSets) { IntSet slotSet = squashedOptions.get(set); int i = 0; - for (ItemData item : set) { + for (ItemDescriptorWithCount item : set) { for (int j = 0; j < totalCombinations / set.size(); j++) { final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x); - for (int slot : slotSet) { - combinations[comboIndex][slot] = item; + for (IntIterator it = slotSet.iterator(); it.hasNext(); ) { + combinations[comboIndex][it.nextInt()] = item; } } i++; @@ -288,6 +283,5 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, ClientboundPlayerInfoRemovePacket packet) { + PlayerListPacket translate = new PlayerListPacket(); + translate.setAction(PlayerListPacket.Action.REMOVE); + + for (UUID id : packet.getProfileIds()) { + // As the player entity is no longer present, we can remove the entry + PlayerEntity entity = session.getEntityCache().removePlayerEntity(id); + if (entity != null) { + // Just remove the entity's player list status + // Don't despawn the entity - the Java server will also take care of that. + entity.setPlayerList(false); + } + if (entity == session.getPlayerEntity()) { + // If removing ourself we use our AuthData UUID + translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid())); + } else { + translate.getEntries().add(new PlayerListPacket.Entry(id)); + } + } + + session.sendUpstreamPacket(translate); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java deleted file mode 100644 index 1cefb9731..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java +++ /dev/null @@ -1,125 +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.translator.protocol.java.entity.player; - -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; -import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.entity.type.player.PlayerEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.skin.SkinManager; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - -@Translator(packet = ClientboundPlayerInfoPacket.class) -public class JavaPlayerInfoTranslator extends PacketTranslator { - @Override - public void translate(GeyserSession session, ClientboundPlayerInfoPacket packet) { - if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) - return; - - PlayerListPacket translate = new PlayerListPacket(); - translate.setAction(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Action.ADD : PlayerListPacket.Action.REMOVE); - - for (PlayerListEntry entry : packet.getEntries()) { - switch (packet.getAction()) { - case ADD_PLAYER -> { - GameProfile profile = entry.getProfile(); - PlayerEntity playerEntity; - boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); - - if (self) { - // Entity is ourself - playerEntity = session.getPlayerEntity(); - } else { - playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); - } - - GameProfile.Property textures = profile.getProperty("textures"); - String texturesProperty = textures == null ? null : textures.getValue(); - - if (playerEntity == null) { - // It's a new player - playerEntity = new PlayerEntity( - session, - -1, - session.getEntityCache().getNextEntityId().incrementAndGet(), - profile.getId(), - Vector3f.ZERO, - Vector3f.ZERO, - 0, 0, 0, - profile.getName(), - texturesProperty - ); - - session.getEntityCache().addPlayerEntity(playerEntity); - } else { - playerEntity.setUsername(profile.getName()); - playerEntity.setTexturesProperty(texturesProperty); - } - - playerEntity.setPlayerList(true); - - // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape - // But we need to send other player's entries so they show up in the player list - // without processing their skin information - that'll be processed when they spawn in - if (self) { - SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> - GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); - } else { - playerEntity.setValid(true); - PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - - translate.getEntries().add(playerListEntry); - } - } - case REMOVE_PLAYER -> { - // As the player entity is no longer present, we can remove the entry - PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); - if (entity != null) { - // Just remove the entity's player list status - // Don't despawn the entity - the Java server will also take care of that. - entity.setPlayerList(false); - } - if (entity == session.getPlayerEntity()) { - // If removing ourself we use our AuthData UUID - translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid())); - } else { - translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); - } - } - } - } - - if (!translate.getEntries().isEmpty()) { - session.sendUpstreamPacket(translate); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java new file mode 100644 index 000000000..a69c03169 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java @@ -0,0 +1,107 @@ +/* + * 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.translator.protocol.java.entity.player; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoUpdatePacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.SkinManager; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = ClientboundPlayerInfoUpdatePacket.class) +public class JavaPlayerInfoUpdateTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, ClientboundPlayerInfoUpdatePacket packet) { + if (!packet.getActions().contains(PlayerListEntryAction.ADD_PLAYER)) { + return; + } + + PlayerListPacket translate = new PlayerListPacket(); + translate.setAction(PlayerListPacket.Action.ADD); + + for (PlayerListEntry entry : packet.getEntries()) { + GameProfile profile = entry.getProfile(); + PlayerEntity playerEntity; + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); + + if (self) { + // Entity is ourself + playerEntity = session.getPlayerEntity(); + } else { + playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); + } + + GameProfile.Property textures = profile.getProperty("textures"); + String texturesProperty = textures == null ? null : textures.getValue(); + + if (playerEntity == null) { + // It's a new player + playerEntity = new PlayerEntity( + session, + -1, + session.getEntityCache().getNextEntityId().incrementAndGet(), + profile.getId(), + Vector3f.ZERO, + Vector3f.ZERO, + 0, 0, 0, + profile.getName(), + texturesProperty + ); + + session.getEntityCache().addPlayerEntity(playerEntity); + } else { + playerEntity.setUsername(profile.getName()); + playerEntity.setTexturesProperty(texturesProperty); + } + + playerEntity.setPlayerList(true); + + // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape + // But we need to send other player's entries so they show up in the player list + // without processing their skin information - that'll be processed when they spawn in + if (self) { + SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> + GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); + } else { + playerEntity.setValid(true); + PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); + + translate.getEntries().add(playerListEntry); + } + } + + if (!translate.getEntries().isEmpty()) { + session.sendUpstreamPacket(translate); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java index 934ee882d..9f687f046 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java @@ -38,6 +38,6 @@ public class JavaContainerCloseTranslator extends PacketTranslator inventorySize) { + if (i >= inventorySize) { GeyserImpl geyser = session.getGeyser(); - geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.name() + geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.bedrockUsername() + " that exceeds inventory size!"); if (geyser.getConfig().isDebugMode()) { geyser.getLogger().debug(packet); geyser.getLogger().debug(inventory); } - InventoryTranslator translator = session.getInventoryTranslator(); - if (translator != null) { - translator.updateInventory(session, inventory); - } + updateInventory(session, inventory, packet.getContainerId()); // 1.18.1 behavior: the previous items will be correctly set, but the state ID and carried item will not // as this produces a stack trace on the client. // If Java processes this correctly in the future, we can revert this behavior @@ -68,10 +66,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator 0 || stateId != inventory.getStateId()); @@ -80,4 +75,14 @@ public class JavaContainerSetContentTranslator extends PacketTranslator= inventory.getSize()) { GeyserImpl geyser = session.getGeyser(); - geyser.getLogger().warning("ClientboundContainerSetSlotPacket sent to " + session.name() + geyser.getLogger().warning("ClientboundContainerSetSlotPacket sent to " + session.bedrockUsername() + " that exceeds inventory size!"); if (geyser.getConfig().isDebugMode()) { geyser.getLogger().debug(packet); @@ -186,7 +187,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator { @@ -45,7 +44,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator= 2.0f ? LevelEventType.PARTICLE_HUGE_EXPLODE : LevelEventType.PARTICLE_EXPLOSION); - levelEventPacket.setData(0); - levelEventPacket.setPosition(pos); + levelEventPacket.setTag(builder.build()); session.sendUpstreamPacket(levelEventPacket); + Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); levelSoundEventPacket.setRelativeVolumeDisabled(false); levelSoundEventPacket.setBabySound(false); 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 528606bd8..9e9b2abd8 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 @@ -39,7 +39,6 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -296,6 +295,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4; + // Estimate chunk size int size = 0; for (int i = 0; i < sectionCount; i++) { @@ -306,9 +308,8 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4; int dimensionOffset = bedrockDimension.minY() >> 4; for (int i = 0; i < biomeCount; i++) { int biomeYOffset = dimensionOffset + i; @@ -343,7 +342,6 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator 0) { - levelEventPacket.setEventId(2037); + levelEventPacket.setEventId(2037/*LevelEventType.SCULK_CHARGE*/); levelEventPacket.setTag( NbtMap.builder() .putInt("x", packet.getPosition().getX()) @@ -274,7 +274,7 @@ public class JavaLevelEventTranslator extends PacketTranslator { LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket(); - levelEventPacket.setEventId(2035); + levelEventPacket.setEventId(2035/*LevelEventType.PARTICLE_SCULK_SHRIEK*/); levelEventPacket.setTag( NbtMap.builder() .putInt("originX", packet.getPosition().getX()) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java index c77caadee..94466a1ab 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java @@ -157,7 +157,7 @@ public class JavaLevelParticlesTranslator extends PacketTranslator { LevelEventGenericPacket packet = new LevelEventGenericPacket(); - packet.setEventId(2027); + packet.setEventId(2027/*LevelEventType.PARTICLE_VIBRATION_SIGNAL*/); packet.setTag( NbtMap.builder() .putCompound("origin", buildVec3PositionTag(position)) 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 d92eee50f..3a2d1a050 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 @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.level; import com.github.steveice10.mc.protocol.data.game.level.map.MapData; import com.github.steveice10.mc.protocol.data.game.level.map.MapIcon; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundMapItemDataPacket; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.MapDecoration; import com.nukkitx.protocol.bedrock.data.MapTrackedObject; import org.geysermc.geyser.level.BedrockMapIcon; @@ -48,7 +49,10 @@ public class JavaMapItemDataTranslator extends PacketTranslator { @@ -38,7 +37,7 @@ public class JavaSectionBlocksUpdateTranslator extends PacketTranslator parameters = decoration.parameters(); + List args = new ArrayList<>(3); + if (parameters.contains(TextDecoration.Parameter.TARGET)) { + args.add(targetName); + } + if (parameters.contains(TextDecoration.Parameter.SENDER)) { + args.add(sender); + } + if (parameters.contains(TextDecoration.Parameter.CONTENT)) { + args.add(message); + } + withDecoration.args(args); + textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.locale())); + } else { + session.getGeyser().getLogger().debug("Likely illegal chat type detection found."); + if (session.getGeyser().getConfig().isDebugMode()) { + Thread.dumpStack(); + } + textPacket.setMessage(MessageTranslator.convertMessage(message, session.locale())); + } + + session.sendUpstreamPacket(textPacket); + } + /** * Convert a team color to a chat color * 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 4c4da5c75..b1ebd877b 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.IntLists; import lombok.experimental.UtilityClass; @@ -55,10 +56,6 @@ public class ChunkUtils { * An empty subchunk. */ public static final byte[] SERIALIZED_CHUNK_DATA; - /** - * An empty chunk that can be safely passed on to a LevelChunkPacket with subcounts set to 0. - */ - public static final byte[] EMPTY_CHUNK_DATA; public static final byte[] EMPTY_BIOME_DATA; static { @@ -82,20 +79,6 @@ public class ChunkUtils { } finally { byteBuf.release(); } - - byteBuf = Unpooled.buffer(); - try { - for (int i = 0; i < 32; i++) { - byteBuf.writeBytes(EMPTY_BIOME_DATA); - } - - byteBuf.writeByte(0); // Border - - EMPTY_CHUNK_DATA = new byte[byteBuf.readableBytes()]; - byteBuf.readBytes(EMPTY_CHUNK_DATA); - } finally { - byteBuf.release(); - } } public static int indexYZXtoXZY(int yzx) { @@ -126,7 +109,6 @@ public class ChunkUtils { public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { updateBlockClientSide(session, blockState, position); session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState); - session.getWorldCache().updateServerCorrectBlockState(position); } /** @@ -193,11 +175,32 @@ public class ChunkUtils { } public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) { + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int bedrockSubChunkCount = bedrockDimension.height() >> 4; + + byte[] payload; + + // Allocate output buffer + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(ChunkUtils.EMPTY_BIOME_DATA.length * bedrockSubChunkCount + 1); // Consists only of biome data and border blocks + try { + byteBuf.writeBytes(EMPTY_BIOME_DATA); + for (int i = 1; i < bedrockSubChunkCount; i++) { + byteBuf.writeByte((127 << 1) | 1); + } + + byteBuf.writeByte(0); // Border blocks - Edu edition only + + payload = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(payload); + } finally { + byteBuf.release(); + } + LevelChunkPacket data = new LevelChunkPacket(); data.setChunkX(chunkX); data.setChunkZ(chunkZ); data.setSubChunksLength(0); - data.setData(EMPTY_CHUNK_DATA); + data.setData(payload); data.setCachingEnabled(false); session.sendUpstreamPacket(data); @@ -226,7 +229,8 @@ 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.getDimensionType(); + JavaDimension dimension = session.getDimensions().get(session.getDimension()); + session.setDimensionType(dimension); int minY = dimension.minY(); int maxY = dimension.maxY(); @@ -237,13 +241,7 @@ public class ChunkUtils { throw new RuntimeException("Maximum Y must be a multiple of 16!"); } - BedrockDimension bedrockDimension = switch (session.getDimension()) { - case DimensionUtils.THE_END -> BedrockDimension.THE_END; - case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER; - default -> BedrockDimension.OVERWORLD; - }; - session.getChunkCache().setBedrockDimension(bedrockDimension); - + BedrockDimension bedrockDimension = session.getChunkCache().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. :/ ) 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 7e5d65a97..5963da703 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -27,11 +27,16 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.entity.Effect; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.PlayerActionType; import com.nukkitx.protocol.bedrock.packet.ChangeDimensionPacket; import com.nukkitx.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; import com.nukkitx.protocol.bedrock.packet.MobEffectPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; import com.nukkitx.protocol.bedrock.packet.StopSoundPacket; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import java.util.Set; @@ -93,7 +98,10 @@ public class DimensionUtils { changeDimensionPacket.setRespawn(true); changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); + session.setDimension(javaDimension); + setBedrockDimension(session, javaDimension); + player.setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); @@ -115,6 +123,19 @@ public class DimensionUtils { stopSoundPacket.setSoundName(""); session.sendUpstreamPacket(stopSoundPacket); + // Kind of silly but Bedrock 1.19.50 requires an acknowledgement after the + // initial chunks are sent, prior to the client acknowledgement + if (GameProtocol.supports1_19_50(session)) { + // Note: send this before chunks are sent. Fixed https://github.com/GeyserMC/Geyser/issues/3421 + PlayerActionPacket ackPacket = new PlayerActionPacket(); + ackPacket.setRuntimeEntityId(player.getGeyserId()); + ackPacket.setAction(PlayerActionType.DIMENSION_CHANGE_SUCCESS); + ackPacket.setBlockPosition(Vector3i.ZERO); + ackPacket.setResultPosition(Vector3i.ZERO); + ackPacket.setFace(0); + session.sendUpstreamPacket(ackPacket); + } + // 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); @@ -131,6 +152,24 @@ public class DimensionUtils { } } + public static void setBedrockDimension(GeyserSession session, String 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 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 * @@ -169,7 +208,9 @@ public class DimensionUtils { // Prevents rare instances of Bedrock locking up return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER; } - return currentDimension.equals(OVERWORLD) ? NETHER : OVERWORLD; + // 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; } public static boolean isCustomBedrockNetherId() { 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 d128989a8..8f92eaf3f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -190,11 +190,8 @@ public final class EntityUtils { if (passenger.getDefinition().entityType() == EntityType.FALLING_BLOCK) { yOffset += 0.5f; } - if (mount.getDefinition().entityType() == EntityType.ARMOR_STAND) { - ArmorStandEntity armorStand = (ArmorStandEntity) mount; - if (armorStand.isPositionRequiresOffset()) { - yOffset -= EntityDefinitions.ARMOR_STAND.height() * (armorStand.isSmall() ? 0.55d : 1d); - } + if (mount instanceof ArmorStandEntity armorStand) { + yOffset -= armorStand.getYOffset(); } Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); passenger.setRiderSeatPosition(offset); 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 5c2905d93..1f31d917b 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; @@ -38,6 +39,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; @@ -46,6 +48,7 @@ import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; @@ -85,12 +88,11 @@ public class InventoryUtils { public static void displayInventory(GeyserSession session, Inventory inventory) { InventoryTranslator translator = session.getInventoryTranslator(); - if (translator != null) { - translator.prepareInventory(session, inventory); + if (translator != null && translator.prepareInventory(session, inventory)) { if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { session.scheduleInEventLoop(() -> { Inventory openInv = session.getOpenInventory(); - if (openInv != null && openInv.getId() == inventory.getId()) { + if (openInv != null && openInv.getJavaId() == inventory.getJavaId()) { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); } else if (openInv != null && openInv.isPending()) { @@ -103,16 +105,15 @@ public class InventoryUtils { translator.updateInventory(session, inventory); } } else { - // Precaution - as of 1.16 every inventory should be translated so this shouldn't happen session.setOpenInventory(null); } } - public static void closeInventory(GeyserSession session, int windowId, boolean confirm) { + public static void closeInventory(GeyserSession session, int javaId, boolean confirm) { session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); updateCursor(session); - Inventory inventory = getInventory(session, windowId); + Inventory inventory = getInventory(session, javaId); if (inventory != null) { InventoryTranslator translator = session.getInventoryTranslator(); translator.closeInventory(session, inventory); @@ -124,18 +125,40 @@ public class InventoryUtils { session.setOpenInventory(null); } - public static Inventory getInventory(GeyserSession session, int windowId) { - if (windowId == 0) { + public static Inventory getInventory(GeyserSession session, int javaId) { + if (javaId == 0) { return session.getPlayerInventory(); } else { Inventory openInventory = session.getOpenInventory(); - if (openInventory != null && windowId == openInventory.getId()) { + if (openInventory != null && javaId == openInventory.getJavaId()) { return openInventory; } return null; } } + /** + * Finds a usable block space in the world to place a fake inventory block, and returns the position. + */ + @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(); + int minY = dimension.minY(), maxY = minY + dimension.height(); + Vector3i flatPlayerPosition = session.getPlayerEntity().getPosition().toInt(); + Vector3i position = flatPlayerPosition.add(Vector3i.UP); + if (position.getY() < minY) { + return null; + } + if (position.getY() >= maxY) { + position = flatPlayerPosition.sub(0, 4, 0); + if (position.getY() >= maxY) { + return null; + } + } + return position; + } + public static void updateCursor(GeyserSession session) { InventorySlotPacket cursorPacket = new InventorySlotPacket(); cursorPacket.setContainerId(ContainerId.UI); @@ -150,18 +173,6 @@ public class InventoryUtils { return item1.getJavaId() == item2.getJavaId() && Objects.equals(item1.getNbt(), item2.getNbt()); } - public static boolean canStack(ItemStack item1, ItemStack item2) { - if (item1 == null || item2 == null) - return false; - return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt()); - } - - public static boolean canStack(ItemData item1, ItemData item2) { - if (item1 == null || item2 == null) - return false; - return item1.equals(item2, false, true, true); - } - /** * Checks to see if an item stack represents air or has no count. */ @@ -186,11 +197,22 @@ public class InventoryUtils { root.put("display", display.build()); return protocolVersion -> ItemData.builder() - .id(Registries.ITEMS.forVersion(protocolVersion).getStoredItems().barrier().getBedrockId()) + .id(getUnusableSpaceBlockID(protocolVersion)) .count(1) .tag(root.build()).build(); } + private static int getUnusableSpaceBlockID(int protocolVersion) { + String unusableSpaceBlock = GeyserImpl.getInstance().getConfig().getUnusableSpaceBlock(); + ItemMapping unusableSpaceBlockID = Registries.ITEMS.forVersion(protocolVersion).getMapping(unusableSpaceBlock); + if (unusableSpaceBlockID != null) { + return unusableSpaceBlockID.getBedrockId(); + } else { + GeyserImpl.getInstance().getLogger().error("Invalid value" + unusableSpaceBlock + ". Resorting to barrier block."); + return Registries.ITEMS.forVersion(protocolVersion).getStoredItems().barrier().getBedrockId(); + } + } + /** * See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}. * diff --git a/core/src/main/java/org/geysermc/geyser/util/JavaCodecEntry.java b/core/src/main/java/org/geysermc/geyser/util/JavaCodecUtil.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/util/JavaCodecEntry.java rename to core/src/main/java/org/geysermc/geyser/util/JavaCodecUtil.java index 45be5bf99..f0b8ee6bc 100644 --- a/core/src/main/java/org/geysermc/geyser/util/JavaCodecEntry.java +++ b/core/src/main/java/org/geysermc/geyser/util/JavaCodecUtil.java @@ -32,7 +32,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import javax.annotation.Nonnull; import java.util.Iterator; -public record JavaCodecEntry() { +public final class JavaCodecUtil { /** * Iterate over a Java Edition codec and return each entry as a CompoundTag @@ -58,4 +58,7 @@ public record JavaCodecEntry() { } }; } + + private JavaCodecUtil() { + } } 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 c8d6e42d7..8d832f8fa 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -245,13 +245,7 @@ public class LoginEncryptionUtils { } if (response.clickedButtonId() == 1) { - if (isPasswordAuthEnabled) { - session.setMicrosoftAccount(true); - buildAndShowMicrosoftAuthenticationWindow(session); - } else { - // Just show the OAuth code - session.authenticateWithMicrosoftCode(); - } + session.authenticateWithMicrosoftCode(); return; } @@ -315,39 +309,10 @@ public class LoginEncryptionUtils { .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") .input("geyser.auth.login.form.details.pass", "123456", "") .invalidResultHandler(() -> buildAndShowLoginDetailsWindow(session)) - .closedResultHandler(() -> { - if (session.isMicrosoftAccount()) { - buildAndShowMicrosoftAuthenticationWindow(session); - } else { - buildAndShowLoginWindow(session); - } - }) + .closedResultHandler(() -> buildAndShowLoginWindow(session)) .validResultHandler((response) -> session.authenticate(response.next(), response.next()))); } - /** - * Prompts the user between either OAuth code login or manual password authentication - */ - public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { - session.sendForm( - SimpleForm.builder() - .translator(GeyserLocale::getPlayerLocaleString, session.locale()) - .title("geyser.auth.login.form.notice.btn_login.microsoft") - .button("geyser.auth.login.method.browser") - .button("geyser.auth.login.method.password") - .button("geyser.auth.login.form.notice.btn_disconnect") - .closedOrInvalidResultHandler(() -> buildAndShowLoginWindow(session)) - .validResultHandler((response) -> { - if (response.clickedButtonId() == 0) { - session.authenticateWithMicrosoftCode(); - } else if (response.clickedButtonId() == 1) { - buildAndShowLoginDetailsWindow(session); - } else { - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); - } - })); - } - /** * Shows the code that a user must input into their browser */ @@ -374,7 +339,7 @@ public class LoginEncryptionUtils { .content(message.toString()) .button1("%gui.done") .button2("%menu.disconnect") - .closedOrInvalidResultHandler(() -> buildAndShowMicrosoftAuthenticationWindow(session)) + .closedOrInvalidResultHandler(() -> buildAndShowLoginWindow(session)) .validResultHandler((response) -> { if (response.clickedButtonId() == 1) { session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", locale)); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java b/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java similarity index 52% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java rename to core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java index 6679c8950..bb92e6cd1 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java @@ -23,8 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.standalone; +package org.geysermc.geyser.util; +import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; @@ -32,32 +33,47 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; -public class LoopbackUtil { - private static final String checkExemption = "powershell -Command \"CheckNetIsolation LoopbackExempt -s\""; // Java's Exec feature runs as CMD, NetIsolation is only accessible from PowerShell. - private static final String loopbackCommand = "powershell -Command \"CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'\""; +public final class LoopbackUtil { + private static final String checkExemption = "CheckNetIsolation LoopbackExempt -s"; + private static final String loopbackCommand = "CheckNetIsolation LoopbackExempt -a -n=Microsoft.MinecraftUWP_8wekyb3d8bbwe"; + /** + * This string needs to be checked in the event Minecraft is not installed - no Minecraft string will be present in the checkExemption command. + */ + private static final String minecraftApplication = "S-1-15-2-1958404141-86561845-1752920682-3514627264-368642714-62675701-733520436"; private static final String startScript = "powershell -Command \"Start-Process 'cmd' -ArgumentList /c,%temp%/loopback_minecraft.bat -Verb runAs\""; - public static void checkLoopback(GeyserStandaloneLogger geyserLogger) { - if (System.getProperty("os.name").equalsIgnoreCase("Windows 10")) { + /** + * @return true if loopback is not addressed properly. + */ + public static boolean needsLoopback(GeyserLogger logger) { + String os = System.getProperty("os.name"); + if (os.equalsIgnoreCase("Windows 10") || os.equalsIgnoreCase("Windows 11")) { try { Process process = Runtime.getRuntime().exec(checkExemption); InputStream is = process.getInputStream(); + + int data; StringBuilder sb = new StringBuilder(); - - while (process.isAlive()) { - if (is.available() != 0) { - sb.append((char) is.read()); - } + while ((data = is.read()) != -1) { + sb.append((char) data); } - String result = sb.toString(); + return !sb.toString().contains(minecraftApplication); + } catch (Exception e) { + logger.error("Couldn't detect if loopback has been added on Windows!", e); + return true; + } + } + return false; + } - if (!result.contains("minecraftuwp")) { - Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes()); - Runtime.getRuntime().exec(startScript); + public static void checkAndApplyLoopback(GeyserLogger geyserLogger) { + if (needsLoopback(geyserLogger)) { + try { + Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes()); + Runtime.getRuntime().exec(startScript); - geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added")); - } + geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added")); } catch (Exception e) { e.printStackTrace(); @@ -66,4 +82,6 @@ public class LoopbackUtil { } } + private LoopbackUtil() { + } } 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 483a4372f..5957fb9d9 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -100,11 +100,7 @@ public class SettingsUtils { .translator(MinecraftLocale::getLocaleString); // we need translate gamerules next WorldManager worldManager = GeyserImpl.getInstance().getWorldManager(); - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; - } - + for (GameRule gamerule : GameRule.VALUES) { // Add the relevant form item based on the gamerule type if (Boolean.class.equals(gamerule.getType())) { builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule)); @@ -146,10 +142,6 @@ public class SettingsUtils { if (showGamerules) { for (GameRule gamerule : GameRule.VALUES) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; - } - if (Boolean.class.equals(gamerule.getType())) { boolean value = response.next(); if (value != session.getGeyser().getWorldManager().getGameRuleBool(session, gamerule)) { diff --git a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java index 15348939c..b80a9afe7 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.util; -import com.github.steveice10.mc.protocol.data.game.level.sound.BuiltinSound; -import com.github.steveice10.mc.protocol.data.game.level.sound.CustomSound; import com.github.steveice10.mc.protocol.data.game.level.sound.Sound; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -63,30 +61,20 @@ public final class SoundUtils { /** * Translates a Java Custom or Builtin Sound to its Bedrock equivalent * - * @param sound the sound to translate + * @param javaIdentifier the sound to translate * @return a Bedrock sound */ - public static String translatePlaySound(Sound sound) { - String packetSound; - if (sound instanceof BuiltinSound builtinSound) { - packetSound = builtinSound.getName(); - } else if (sound instanceof CustomSound customSound) { - packetSound = customSound.getName(); - } else { - GeyserImpl.getInstance().getLogger().debug("Unknown sound, we were unable to map this. " + sound); - return ""; - } - + public static String translatePlaySound(String javaIdentifier) { // Drop the Minecraft namespace if applicable - if (packetSound.startsWith("minecraft:")) { - packetSound = packetSound.substring("minecraft:".length()); + if (javaIdentifier.startsWith("minecraft:")) { + javaIdentifier = javaIdentifier.substring("minecraft:".length()); } - SoundMapping soundMapping = Registries.SOUNDS.get(packetSound); + SoundMapping soundMapping = Registries.SOUNDS.get(javaIdentifier); if (soundMapping == null || soundMapping.getPlaysound() == null) { // no mapping - GeyserImpl.getInstance().getLogger().debug("[PlaySound] Defaulting to sound server gave us for " + sound); - return packetSound; + GeyserImpl.getInstance().getLogger().debug("[PlaySound] Defaulting to sound server gave us for " + javaIdentifier); + return javaIdentifier; } return soundMapping.getPlaysound(); } @@ -99,7 +87,7 @@ public final class SoundUtils { * @param position the position * @param pitch the pitch */ - public static void playBuiltinSound(GeyserSession session, BuiltinSound javaSound, Vector3f position, float volume, float pitch) { + public static void playSound(GeyserSession session, Sound javaSound, Vector3f position, float volume, float pitch) { String packetSound = javaSound.getName(); SoundMapping soundMapping = Registries.SOUNDS.get(packetSound); diff --git a/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java b/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java index 7e1f6e7b7..d46a759fe 100644 --- a/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java @@ -26,17 +26,17 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.statistic.StatisticFormat; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.util.EnumMap; import java.util.Locale; import java.util.Map; import java.util.function.IntFunction; -public class StatisticFormatters { +public final class StatisticFormatters { - private static final Map> FORMATTERS = new Object2ObjectOpenHashMap<>(); + private static final Map> FORMATTERS = new EnumMap<>(StatisticFormat.class); private static final DecimalFormat FORMAT = new DecimalFormat("###,###,##0.00"); public static final IntFunction INTEGER = NumberFormat.getIntegerInstance(Locale.US)::format; @@ -78,4 +78,7 @@ public class StatisticFormatters { public static IntFunction get(StatisticFormat format) { return FORMATTERS.getOrDefault(format, INTEGER); } + + private StatisticFormatters() { + } } 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 f01670106..58e0b131a 100644 --- a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.statistic.*; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.geysermc.cumulus.form.SimpleForm; import org.geysermc.cumulus.util.FormImage; import org.geysermc.geyser.registry.BlockRegistries; @@ -37,7 +38,6 @@ import org.geysermc.geyser.text.MinecraftLocale; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.function.IntFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -79,23 +79,23 @@ public class StatisticsUtils { case 0: builder.title("stat.generalButton"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof CustomStatistic statistic) { String statName = statistic.name().toLowerCase(Locale.ROOT); IntFunction formatter = StatisticFormatters.get(statistic.getFormat()); - content.add("stat.minecraft." + statName + ": " + formatter.apply(entry.getValue())); + content.add("stat.minecraft." + statName + ": " + formatter.apply(entry.getIntValue())); } } break; case 1: builder.title("stat.itemsButton - stat_type.minecraft.mined"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof BreakBlockStatistic statistic) { String identifier = BlockRegistries.CLEAN_JAVA_IDENTIFIERS.get(statistic.getId()); if (identifier != null) { String block = identifier.replace("minecraft:", "block.minecraft."); - content.add(block + ": " + entry.getValue()); + content.add(block + ": " + entry.getIntValue()); } } } @@ -103,71 +103,70 @@ public class StatisticsUtils { case 2: builder.title("stat.itemsButton - stat_type.minecraft.broken"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof BreakItemStatistic statistic) { String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); - content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); + content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } break; case 3: builder.title("stat.itemsButton - stat_type.minecraft.crafted"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof CraftItemStatistic statistic) { String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); - content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); + content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } break; case 4: builder.title("stat.itemsButton - stat_type.minecraft.used"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof UseItemStatistic statistic) { String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); - content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); + content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } break; case 5: builder.title("stat.itemsButton - stat_type.minecraft.picked_up"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof PickupItemStatistic statistic) { String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); - content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); + content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } break; case 6: builder.title("stat.itemsButton - stat_type.minecraft.dropped"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof DropItemStatistic statistic) { String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); - content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); + content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue()); } } break; case 7: builder.title("stat.mobsButton - geyser.statistics.killed"); - for (Map.Entry entry : session.getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof KillEntityStatistic statistic) { String entityName = statistic.getEntity().name().toLowerCase(Locale.ROOT); - content.add("entity.minecraft." + entityName + ": " + entry.getValue()); + content.add("entity.minecraft." + entityName + ": " + entry.getIntValue()); } } break; case 8: builder.title("stat.mobsButton - geyser.statistics.killed_by"); - for (Map.Entry entry : session - .getStatistics().entrySet()) { + for (Object2IntMap.Entry entry : session.getStatistics().object2IntEntrySet()) { if (entry.getKey() instanceof KilledByEntityStatistic statistic) { String entityName = statistic.getEntity().name().toLowerCase(Locale.ROOT); - content.add("entity.minecraft." + entityName + ": " + entry.getValue()); + content.add("entity.minecraft." + entityName + ": " + entry.getIntValue()); } } break; diff --git a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java index 934680ce1..049d78619 100644 --- a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java @@ -25,10 +25,22 @@ package org.geysermc.geyser.util; +import com.fasterxml.jackson.databind.JsonNode; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.geysermc.geyser.Constants; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.GeyserLocale; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + public final class VersionCheckUtils { public static void checkForOutdatedFloodgate(GeyserLogger logger) { @@ -42,6 +54,41 @@ public final class VersionCheckUtils { } } + public static void checkForGeyserUpdate(Supplier recipient) { + CompletableFuture.runAsync(() -> { + try { + JsonNode json = WebUtils.getJson("https://api.geysermc.org/v2/versions/geyser"); + JsonNode bedrock = json.get("bedrock").get("protocol"); + int protocolVersion = bedrock.get("id").asInt(); + if (GameProtocol.getBedrockCodec(protocolVersion) != null) { + // We support the latest version! No need to print a message. + return; + } + + final String newBedrockVersion = bedrock.get("name").asText(); + + // Delayed for two reasons: save unnecessary processing, and wait to load locale if this is on join. + GeyserCommandSource sender = recipient.get(); + + // Overarching component is green - geyser.version.new component cannot be green or else the link blue is overshadowed + Component message = Component.text().color(NamedTextColor.GREEN) + .append(Component.text(GeyserLocale.getPlayerLocaleString("geyser.version.new", sender.locale(), newBedrockVersion)) + .replaceText(TextReplacementConfig.builder() + .match("\\{1\\}") // Replace "Download here: {1}" so we can use fancy text component yesyes + .replacement(Component.text() + .content(Constants.GEYSER_DOWNLOAD_LOCATION) + .color(NamedTextColor.BLUE) + .decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE) + .clickEvent(ClickEvent.openUrl(Constants.GEYSER_DOWNLOAD_LOCATION))) + .build())) + .build(); + sender.sendMessage(message); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Error whilst checking for Geyser update!", e); + } + }); + } + private VersionCheckUtils() { } } diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java index fe479363f..c0889f1c5 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -28,6 +28,9 @@ package org.geysermc.geyser.util; import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.GeyserImpl; +import javax.annotation.Nullable; +import javax.naming.directory.Attribute; +import javax.naming.directory.InitialDirContext; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; @@ -70,6 +73,8 @@ public class WebUtils { public static JsonNode getJson(String reqURL) throws IOException { HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection(); con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream()); } @@ -170,4 +175,23 @@ public class WebUtils { return connectionToString(con); } + + @Nullable + public static String[] findSrvRecord(GeyserImpl geyser, String remoteAddress) { + try { + // Searches for a server address and a port from a SRV record of the specified host name + InitialDirContext ctx = new InitialDirContext(); + Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV"); + // size > 0 = SRV entry found + if (attr != null && attr.size() > 0) { + return ((String) attr.get(0)).split(" "); + } + } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().debug("Exception while trying to find an SRV record for the remote host."); + ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record + } + } + return null; + } } diff --git a/core/src/main/resources/bedrock/biome_definitions.dat b/core/src/main/resources/bedrock/biome_definitions.dat index 0520c61e2..47b19ab44 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_19_0.nbt b/core/src/main/resources/bedrock/block_palette.1_19_0.nbt deleted file mode 100644 index 8c095ad02..000000000 Binary files a/core/src/main/resources/bedrock/block_palette.1_19_0.nbt and /dev/null differ diff --git a/core/src/main/resources/bedrock/block_palette.1_19_20.nbt b/core/src/main/resources/bedrock/block_palette.1_19_20.nbt new file mode 100644 index 000000000..75d84b6a7 Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_19_20.nbt differ diff --git a/core/src/main/resources/bedrock/block_palette.1_19_50.nbt b/core/src/main/resources/bedrock/block_palette.1_19_50.nbt new file mode 100644 index 000000000..8b53566c5 Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_19_50.nbt differ diff --git a/core/src/main/resources/bedrock/creative_items.1_19_0.json b/core/src/main/resources/bedrock/creative_items.1_19_20.json similarity index 90% rename from core/src/main/resources/bedrock/creative_items.1_19_0.json rename to core/src/main/resources/bedrock/creative_items.1_19_20.json index 3a356a802..98d9e007a 100644 --- a/core/src/main/resources/bedrock/creative_items.1_19_0.json +++ b/core/src/main/resources/bedrock/creative_items.1_19_20.json @@ -1,13 +1,5 @@ { "items" : [ - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6071 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6072 - }, { "id" : "minecraft:planks", "blockRuntimeId" : 6073 @@ -24,25 +16,25 @@ "id" : "minecraft:planks", "blockRuntimeId" : 6076 }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6077 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6078 + }, { "id" : "minecraft:mangrove_planks", - "blockRuntimeId" : 947 + "blockRuntimeId" : 949 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 4850 + "blockRuntimeId" : 4852 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 920 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1182 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1183 + "blockRuntimeId" : 922 }, { "id" : "minecraft:cobblestone_wall", @@ -62,7 +54,7 @@ }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1194 + "blockRuntimeId" : 1188 }, { "id" : "minecraft:cobblestone_wall", @@ -70,11 +62,7 @@ }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1190 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1188 + "blockRuntimeId" : 1196 }, { "id" : "minecraft:cobblestone_wall", @@ -82,55 +70,59 @@ }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1195 + "blockRuntimeId" : 1192 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1192 + "blockRuntimeId" : 1190 }, { "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1193 }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1197 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1194 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1195 + }, { "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 3930 + "blockRuntimeId" : 3932 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6724 + "blockRuntimeId" : 6726 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 971 + "blockRuntimeId" : 973 }, { "id" : "minecraft:cobbled_deepslate_wall", - "blockRuntimeId" : 8082 + "blockRuntimeId" : 8084 }, { "id" : "minecraft:deepslate_tile_wall", - "blockRuntimeId" : 5071 + "blockRuntimeId" : 5073 }, { "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 7817 + "blockRuntimeId" : 7819 }, { "id" : "minecraft:deepslate_brick_wall", - "blockRuntimeId" : 429 + "blockRuntimeId" : 431 }, { "id" : "minecraft:mud_brick_wall", - "blockRuntimeId" : 730 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7364 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7365 + "blockRuntimeId" : 732 }, { "id" : "minecraft:fence", @@ -148,21 +140,29 @@ "id" : "minecraft:fence", "blockRuntimeId" : 7369 }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7370 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7371 + }, { "id" : "minecraft:mangrove_fence", - "blockRuntimeId" : 6633 + "blockRuntimeId" : 6635 }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 4290 + "blockRuntimeId" : 4292 }, { "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 7996 + "blockRuntimeId" : 7998 }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 5853 + "blockRuntimeId" : 5855 }, { "id" : "minecraft:fence_gate", @@ -170,47 +170,47 @@ }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 6584 + "blockRuntimeId" : 6586 }, { "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 3777 + "blockRuntimeId" : 3779 }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5365 + "blockRuntimeId" : 5367 }, { "id" : "minecraft:acacia_fence_gate", - "blockRuntimeId" : 7586 + "blockRuntimeId" : 7588 }, { "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 4173 + "blockRuntimeId" : 4175 }, { "id" : "minecraft:mangrove_fence_gate", - "blockRuntimeId" : 4625 + "blockRuntimeId" : 4627 }, { "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 4661 + "blockRuntimeId" : 4663 }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 5399 + "blockRuntimeId" : 5401 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 633 + "blockRuntimeId" : 635 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 3708 + "blockRuntimeId" : 3710 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 4092 + "blockRuntimeId" : 4094 }, { "id" : "minecraft:oak_stairs", @@ -222,75 +222,75 @@ }, { "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 7003 + "blockRuntimeId" : 7005 }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 6967 + "blockRuntimeId" : 6969 }, { "id" : "minecraft:acacia_stairs", - "blockRuntimeId" : 6200 + "blockRuntimeId" : 6202 }, { "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 5063 + "blockRuntimeId" : 5065 }, { "id" : "minecraft:mangrove_stairs", - "blockRuntimeId" : 4595 + "blockRuntimeId" : 4597 }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 931 + "blockRuntimeId" : 933 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5883 + "blockRuntimeId" : 5885 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 3587 + "blockRuntimeId" : 3589 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 3627 + "blockRuntimeId" : 3629 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 5350 + "blockRuntimeId" : 5352 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 5546 + "blockRuntimeId" : 5548 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 3537 + "blockRuntimeId" : 3539 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 4150 + "blockRuntimeId" : 4152 }, { "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 4391 + "blockRuntimeId" : 4393 }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6714 + "blockRuntimeId" : 6716 }, { "id" : "minecraft:andesite_stairs", - "blockRuntimeId" : 5308 + "blockRuntimeId" : 5310 }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 7028 + "blockRuntimeId" : 7030 }, { "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 6530 + "blockRuntimeId" : 6532 }, { "id" : "minecraft:nether_brick_stairs", @@ -298,31 +298,31 @@ }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6602 + "blockRuntimeId" : 6604 }, { "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 6382 + "blockRuntimeId" : 6384 }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 4767 + "blockRuntimeId" : 4769 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 7700 + "blockRuntimeId" : 7702 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 7755 + "blockRuntimeId" : 7757 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 7263 + "blockRuntimeId" : 7265 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 7430 + "blockRuntimeId" : 7432 }, { "id" : "minecraft:prismarine_bricks_stairs", @@ -330,55 +330,55 @@ }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 6280 + "blockRuntimeId" : 6282 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 3718 + "blockRuntimeId" : 3720 }, { "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 7019 + "blockRuntimeId" : 7021 }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 4297 + "blockRuntimeId" : 4299 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 4477 + "blockRuntimeId" : 4479 }, { "id" : "minecraft:cut_copper_stairs", - "blockRuntimeId" : 4604 + "blockRuntimeId" : 4606 }, { "id" : "minecraft:exposed_cut_copper_stairs", - "blockRuntimeId" : 4587 + "blockRuntimeId" : 4589 }, { "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 4305 + "blockRuntimeId" : 4307 }, { "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 351 + "blockRuntimeId" : 353 }, { "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 393 + "blockRuntimeId" : 395 }, { "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 3902 + "blockRuntimeId" : 3904 }, { "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 6167 + "blockRuntimeId" : 6169 }, { "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 5840 + "blockRuntimeId" : 5842 }, { "id" : "minecraft:cobbled_deepslate_stairs", @@ -386,7 +386,7 @@ }, { "id" : "minecraft:deepslate_tile_stairs", - "blockRuntimeId" : 4653 + "blockRuntimeId" : 4655 }, { "id" : "minecraft:polished_deepslate_stairs", @@ -394,11 +394,11 @@ }, { "id" : "minecraft:deepslate_brick_stairs", - "blockRuntimeId" : 7422 + "blockRuntimeId" : 7424 }, { "id" : "minecraft:mud_brick_stairs", - "blockRuntimeId" : 5522 + "blockRuntimeId" : 5524 }, { "id" : "minecraft:wooden_door" @@ -436,27 +436,27 @@ }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 6552 + "blockRuntimeId" : 6554 }, { "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 6650 + "blockRuntimeId" : 6652 }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5381 + "blockRuntimeId" : 5383 }, { "id" : "minecraft:acacia_trapdoor", - "blockRuntimeId" : 5589 + "blockRuntimeId" : 5591 }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 7502 + "blockRuntimeId" : 7504 }, { "id" : "minecraft:mangrove_trapdoor", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 4487 }, { "id" : "minecraft:iron_trapdoor", @@ -464,27 +464,51 @@ }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 4333 + "blockRuntimeId" : 4335 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 4733 + "blockRuntimeId" : 4735 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 4801 + "blockRuntimeId" : 4803 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 6164 + "blockRuntimeId" : 6166 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1133 + "blockRuntimeId" : 1135 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1141 + "blockRuntimeId" : 1143 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1142 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1150 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1147 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1149 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1136 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1139 }, { "id" : "minecraft:stained_glass", @@ -496,19 +520,7 @@ }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1145 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1147 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1134 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1137 + "blockRuntimeId" : 1144 }, { "id" : "minecraft:stained_glass", @@ -520,43 +532,55 @@ }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1142 + "blockRuntimeId" : 1145 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1136 + "blockRuntimeId" : 1137 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1144 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1143 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1135 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1139 + "blockRuntimeId" : 1141 }, { "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 5975 + "blockRuntimeId" : 5977 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 5233 + "blockRuntimeId" : 5235 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4852 + "blockRuntimeId" : 4854 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4860 + "blockRuntimeId" : 4862 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4861 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4869 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4866 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4868 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4855 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4858 }, { "id" : "minecraft:stained_glass_pane", @@ -568,19 +592,7 @@ }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4864 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4866 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4853 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4856 + "blockRuntimeId" : 4863 }, { "id" : "minecraft:stained_glass_pane", @@ -592,59 +604,39 @@ }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4861 + "blockRuntimeId" : 4864 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4855 + "blockRuntimeId" : 4856 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4863 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4862 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4854 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4858 + "blockRuntimeId" : 4860 }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 8262 + "blockRuntimeId" : 8264 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 3571 + "blockRuntimeId" : 3573 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4270 + "blockRuntimeId" : 4272 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 5824 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4273 + "blockRuntimeId" : 4275 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5793 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5270 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5271 + "blockRuntimeId" : 5795 }, { "id" : "minecraft:wooden_slab", @@ -662,41 +654,57 @@ "id" : "minecraft:wooden_slab", "blockRuntimeId" : 5275 }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5276 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5277 + }, { "id" : "minecraft:mangrove_slab", - "blockRuntimeId" : 1149 + "blockRuntimeId" : 1151 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4275 + "blockRuntimeId" : 4277 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5820 + "blockRuntimeId" : 5822 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4271 + "blockRuntimeId" : 4273 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5823 + "blockRuntimeId" : 5825 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5794 + "blockRuntimeId" : 5796 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5788 + "blockRuntimeId" : 5790 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5824 + "blockRuntimeId" : 5826 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5805 + "blockRuntimeId" : 5807 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5812 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5813 }, { "id" : "minecraft:stone_block_slab3", @@ -706,17 +714,25 @@ "id" : "minecraft:stone_block_slab3", "blockRuntimeId" : 5811 }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5808 - }, { "id" : "minecraft:stone_block_slab3", "blockRuntimeId" : 5809 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5807 + "blockRuntimeId" : 5808 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4276 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4279 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5797 }, { "id" : "minecraft:stone_block_slab3", @@ -724,35 +740,11 @@ }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4274 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4277 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5795 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5804 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4276 + "blockRuntimeId" : 4278 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5821 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5789 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5790 + "blockRuntimeId" : 5823 }, { "id" : "minecraft:stone_block_slab2", @@ -762,45 +754,53 @@ "id" : "minecraft:stone_block_slab2", "blockRuntimeId" : 5792 }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5793 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5794 + }, { "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 5900 + "blockRuntimeId" : 5902 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 6484 + "blockRuntimeId" : 6486 }, { "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 910 + "blockRuntimeId" : 912 }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6018 + "blockRuntimeId" : 6020 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 4192 + "blockRuntimeId" : 4194 }, { "id" : "minecraft:cut_copper_slab", - "blockRuntimeId" : 5235 + "blockRuntimeId" : 5237 }, { "id" : "minecraft:exposed_cut_copper_slab", - "blockRuntimeId" : 6600 + "blockRuntimeId" : 6602 }, { "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 6053 + "blockRuntimeId" : 6055 }, { "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5282 + "blockRuntimeId" : 5284 }, { "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7815 + "blockRuntimeId" : 7817 }, { "id" : "minecraft:waxed_exposed_cut_copper_slab", @@ -808,15 +808,15 @@ }, { "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 6545 + "blockRuntimeId" : 6547 }, { "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 708 + "blockRuntimeId" : 710 }, { "id" : "minecraft:cobbled_deepslate_slab", - "blockRuntimeId" : 7310 + "blockRuntimeId" : 7312 }, { "id" : "minecraft:polished_deepslate_slab", @@ -824,39 +824,31 @@ }, { "id" : "minecraft:deepslate_tile_slab", - "blockRuntimeId" : 4291 + "blockRuntimeId" : 4293 }, { "id" : "minecraft:deepslate_brick_slab", - "blockRuntimeId" : 3716 + "blockRuntimeId" : 3718 }, { "id" : "minecraft:mud_brick_slab", - "blockRuntimeId" : 3910 + "blockRuntimeId" : 3912 }, { "id" : "minecraft:brick_block", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 4767 }, { "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 7249 + "blockRuntimeId" : 7251 }, { "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 4552 + "blockRuntimeId" : 4554 }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6351 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6547 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6548 + "blockRuntimeId" : 6353 }, { "id" : "minecraft:stonebrick", @@ -866,53 +858,61 @@ "id" : "minecraft:stonebrick", "blockRuntimeId" : 6550 }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6551 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6552 + }, { "id" : "minecraft:end_bricks", "blockRuntimeId" : 281 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6087 + "blockRuntimeId" : 6089 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 4680 + "blockRuntimeId" : 4682 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 7214 + "blockRuntimeId" : 7216 }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4586 + "blockRuntimeId" : 4588 }, { "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 5062 + "blockRuntimeId" : 5064 }, { "id" : "minecraft:deepslate_tiles", - "blockRuntimeId" : 4581 + "blockRuntimeId" : 4583 }, { "id" : "minecraft:cracked_deepslate_tiles", - "blockRuntimeId" : 4160 + "blockRuntimeId" : 4162 }, { "id" : "minecraft:deepslate_bricks", - "blockRuntimeId" : 5464 + "blockRuntimeId" : 5466 }, { "id" : "minecraft:cracked_deepslate_bricks", - "blockRuntimeId" : 5364 + "blockRuntimeId" : 5366 }, { "id" : "minecraft:chiseled_deepslate", - "blockRuntimeId" : 5234 + "blockRuntimeId" : 5236 }, { "id" : "minecraft:cobblestone", - "blockRuntimeId" : 3615 + "blockRuntimeId" : 3617 }, { "id" : "minecraft:mossy_cobblestone", @@ -920,19 +920,11 @@ }, { "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 6670 + "blockRuntimeId" : 6672 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 4582 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3653 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3654 + "blockRuntimeId" : 4584 }, { "id" : "minecraft:sandstone", @@ -943,12 +935,12 @@ "blockRuntimeId" : 3656 }, { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6580 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3657 }, { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6581 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3658 }, { "id" : "minecraft:red_sandstone", @@ -958,13 +950,21 @@ "id" : "minecraft:red_sandstone", "blockRuntimeId" : 6583 }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6584 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6585 + }, { "id" : "minecraft:coal_block", - "blockRuntimeId" : 5398 + "blockRuntimeId" : 5400 }, { "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 7979 + "blockRuntimeId" : 7981 }, { "id" : "minecraft:gold_block", @@ -972,67 +972,67 @@ }, { "id" : "minecraft:iron_block", - "blockRuntimeId" : 8261 + "blockRuntimeId" : 8263 }, { "id" : "minecraft:copper_block", - "blockRuntimeId" : 4651 + "blockRuntimeId" : 4653 }, { "id" : "minecraft:exposed_copper", - "blockRuntimeId" : 593 + "blockRuntimeId" : 595 }, { "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 8246 + "blockRuntimeId" : 8248 }, { "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 3553 + "blockRuntimeId" : 3555 }, { "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7734 + "blockRuntimeId" : 7736 }, { "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 694 + "blockRuntimeId" : 696 }, { "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 707 + "blockRuntimeId" : 709 }, { "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7542 + "blockRuntimeId" : 7544 }, { "id" : "minecraft:cut_copper", - "blockRuntimeId" : 4689 + "blockRuntimeId" : 4691 }, { "id" : "minecraft:exposed_cut_copper", - "blockRuntimeId" : 6166 + "blockRuntimeId" : 6168 }, { "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7197 + "blockRuntimeId" : 7199 }, { "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5478 + "blockRuntimeId" : 5480 }, { "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7293 + "blockRuntimeId" : 7295 }, { "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 3809 + "blockRuntimeId" : 3811 }, { "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 4851 + "blockRuntimeId" : 4853 }, { "id" : "minecraft:waxed_oxidized_cut_copper", @@ -1040,7 +1040,7 @@ }, { "id" : "minecraft:emerald_block", - "blockRuntimeId" : 1159 + "blockRuntimeId" : 1161 }, { "id" : "minecraft:diamond_block", @@ -1048,23 +1048,19 @@ }, { "id" : "minecraft:lapis_block", - "blockRuntimeId" : 4286 + "blockRuntimeId" : 4288 }, { "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 8260 + "blockRuntimeId" : 8262 }, { "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 5269 + "blockRuntimeId" : 5271 }, { "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 361 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3696 + "blockRuntimeId" : 363 }, { "id" : "minecraft:quartz_block", @@ -1072,43 +1068,47 @@ }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3697 + "blockRuntimeId" : 3700 }, { "id" : "minecraft:quartz_block", "blockRuntimeId" : 3699 }, { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6085 + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3701 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6086 + "blockRuntimeId" : 6087 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6088 }, { "id" : "minecraft:slime", - "blockRuntimeId" : 4233 + "blockRuntimeId" : 4235 }, { "id" : "minecraft:honey_block", - "blockRuntimeId" : 892 + "blockRuntimeId" : 894 }, { "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 4476 + "blockRuntimeId" : 4478 }, { "id" : "minecraft:hay_block", - "blockRuntimeId" : 695 + "blockRuntimeId" : 697 }, { "id" : "minecraft:bone_block", - "blockRuntimeId" : 4234 + "blockRuntimeId" : 4236 }, { "id" : "minecraft:nether_brick", - "blockRuntimeId" : 7272 + "blockRuntimeId" : 7274 }, { "id" : "minecraft:red_nether_brick", @@ -1116,19 +1116,43 @@ }, { "id" : "minecraft:netherite_block", - "blockRuntimeId" : 3775 + "blockRuntimeId" : 3777 }, { "id" : "minecraft:lodestone", - "blockRuntimeId" : 8259 + "blockRuntimeId" : 8261 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3458 + "blockRuntimeId" : 3460 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3466 + "blockRuntimeId" : 3468 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3467 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3475 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3472 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3474 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3461 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3464 }, { "id" : "minecraft:wool", @@ -1140,19 +1164,7 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3470 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3472 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3459 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3462 + "blockRuntimeId" : 3469 }, { "id" : "minecraft:wool", @@ -1164,35 +1176,47 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3467 + "blockRuntimeId" : 3470 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3461 + "blockRuntimeId" : 3462 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3469 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3468 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3460 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3464 + "blockRuntimeId" : 3466 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 949 + "blockRuntimeId" : 951 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 957 + "blockRuntimeId" : 959 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 958 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 966 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 963 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 965 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 952 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 955 }, { "id" : "minecraft:carpet", @@ -1204,19 +1228,7 @@ }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 961 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 963 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 950 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 953 + "blockRuntimeId" : 960 }, { "id" : "minecraft:carpet", @@ -1228,35 +1240,47 @@ }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 958 + "blockRuntimeId" : 961 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 952 + "blockRuntimeId" : 953 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 960 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 959 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 951 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 955 + "blockRuntimeId" : 957 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6264 + "blockRuntimeId" : 6266 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6272 + "blockRuntimeId" : 6274 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6273 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6281 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6278 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6280 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6267 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6270 }, { "id" : "minecraft:concrete_powder", @@ -1268,19 +1292,7 @@ }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6276 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6278 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6265 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6268 + "blockRuntimeId" : 6275 }, { "id" : "minecraft:concrete_powder", @@ -1292,35 +1304,47 @@ }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6273 + "blockRuntimeId" : 6276 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6267 + "blockRuntimeId" : 6268 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6275 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6274 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6266 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6270 + "blockRuntimeId" : 6272 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 660 + "blockRuntimeId" : 662 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 668 + "blockRuntimeId" : 670 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 669 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 677 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 674 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 676 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 663 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 666 }, { "id" : "minecraft:concrete", @@ -1332,19 +1356,7 @@ }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 672 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 674 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 661 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 664 + "blockRuntimeId" : 671 }, { "id" : "minecraft:concrete", @@ -1356,43 +1368,55 @@ }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 669 + "blockRuntimeId" : 672 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 663 + "blockRuntimeId" : 664 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 671 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 670 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 662 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 666 + "blockRuntimeId" : 668 }, { "id" : "minecraft:clay", - "blockRuntimeId" : 7124 + "blockRuntimeId" : 7126 }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 641 + "blockRuntimeId" : 643 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6176 + "blockRuntimeId" : 6178 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6184 + "blockRuntimeId" : 6186 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6185 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6193 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6190 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6192 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6179 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6182 }, { "id" : "minecraft:stained_hardened_clay", @@ -1404,19 +1428,7 @@ }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6188 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6190 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6177 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6180 + "blockRuntimeId" : 6187 }, { "id" : "minecraft:stained_hardened_clay", @@ -1428,59 +1440,47 @@ }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6185 + "blockRuntimeId" : 6188 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6179 + "blockRuntimeId" : 6180 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6187 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6186 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6178 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6182 + "blockRuntimeId" : 6184 }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 5573 + "blockRuntimeId" : 5575 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 3531 + "blockRuntimeId" : 3533 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 8253 + "blockRuntimeId" : 8255 }, { "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 5834 + "blockRuntimeId" : 5836 }, { "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 3547 + "blockRuntimeId" : 3549 }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 4167 + "blockRuntimeId" : 4169 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 1151 + "blockRuntimeId" : 1153 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 913 + "blockRuntimeId" : 915 }, { "id" : "minecraft:lime_glazed_terracotta", @@ -1488,71 +1488,71 @@ }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 6610 + "blockRuntimeId" : 6612 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 5358 + "blockRuntimeId" : 5360 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5471 + "blockRuntimeId" : 5473 }, { "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 5465 + "blockRuntimeId" : 5467 }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 7011 + "blockRuntimeId" : 7013 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 965 + "blockRuntimeId" : 967 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 6539 - }, - { - "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7714 + "blockRuntimeId" : 6541 }, { "id" : "minecraft:purpur_block", "blockRuntimeId" : 7716 }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 7718 + }, { "id" : "minecraft:packed_mud", "blockRuntimeId" : 283 }, { "id" : "minecraft:mud_bricks", - "blockRuntimeId" : 6889 + "blockRuntimeId" : 6891 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 4293 + "blockRuntimeId" : 4295 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 5905 + "blockRuntimeId" : 5907 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 5061 + "blockRuntimeId" : 5063 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 4189 + "blockRuntimeId" : 4191 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 6349 + "blockRuntimeId" : 6351 }, { "id" : "minecraft:basalt", - "blockRuntimeId" : 4349 + "blockRuntimeId" : 4351 }, { "id" : "minecraft:polished_basalt", @@ -1560,83 +1560,83 @@ }, { "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 1157 + "blockRuntimeId" : 1159 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 5830 + "blockRuntimeId" : 5832 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 5751 + "blockRuntimeId" : 5753 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 5752 + "blockRuntimeId" : 5754 }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 3912 + "blockRuntimeId" : 3914 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 6975 + "blockRuntimeId" : 6977 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 8081 + "blockRuntimeId" : 8083 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 4650 + "blockRuntimeId" : 4652 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 3683 + "blockRuntimeId" : 3685 }, { "id" : "minecraft:mud", - "blockRuntimeId" : 6684 + "blockRuntimeId" : 6686 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 653 + "blockRuntimeId" : 655 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 4690 + "blockRuntimeId" : 4692 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 912 + "blockRuntimeId" : 914 }, { "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 4361 + "blockRuntimeId" : 4363 }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 7699 + "blockRuntimeId" : 7701 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 4289 + "blockRuntimeId" : 4291 }, { "id" : "minecraft:coal_ore", - "blockRuntimeId" : 4287 + "blockRuntimeId" : 4289 }, { "id" : "minecraft:copper_ore", - "blockRuntimeId" : 3554 + "blockRuntimeId" : 3556 }, { "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 7347 + "blockRuntimeId" : 7349 }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 4501 + "blockRuntimeId" : 4503 }, { "id" : "minecraft:nether_gold_ore", @@ -1644,35 +1644,35 @@ }, { "id" : "minecraft:ancient_debris", - "blockRuntimeId" : 6107 + "blockRuntimeId" : 6109 }, { "id" : "minecraft:deepslate_iron_ore", - "blockRuntimeId" : 7273 + "blockRuntimeId" : 7275 }, { "id" : "minecraft:deepslate_gold_ore", - "blockRuntimeId" : 6106 + "blockRuntimeId" : 6108 }, { "id" : "minecraft:deepslate_diamond_ore", - "blockRuntimeId" : 8038 + "blockRuntimeId" : 8040 }, { "id" : "minecraft:deepslate_lapis_ore", - "blockRuntimeId" : 7262 + "blockRuntimeId" : 7264 }, { "id" : "minecraft:deepslate_redstone_ore", - "blockRuntimeId" : 6616 + "blockRuntimeId" : 6618 }, { "id" : "minecraft:deepslate_emerald_ore", - "blockRuntimeId" : 6350 + "blockRuntimeId" : 6352 }, { "id" : "minecraft:deepslate_coal_ore", - "blockRuntimeId" : 7196 + "blockRuntimeId" : 7198 }, { "id" : "minecraft:deepslate_copper_ore", @@ -1680,11 +1680,7 @@ }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 8287 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 654 + "blockRuntimeId" : 8289 }, { "id" : "minecraft:stone", @@ -1694,18 +1690,18 @@ "id" : "minecraft:stone", "blockRuntimeId" : 658 }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 660 + }, { "id" : "minecraft:blackstone", - "blockRuntimeId" : 7585 + "blockRuntimeId" : 7587 }, { "id" : "minecraft:deepslate", "blockRuntimeId" : 253 }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 655 - }, { "id" : "minecraft:stone", "blockRuntimeId" : 657 @@ -1714,69 +1710,73 @@ "id" : "minecraft:stone", "blockRuntimeId" : 659 }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 661 + }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 3682 + "blockRuntimeId" : 3684 }, { "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 7754 + "blockRuntimeId" : 7756 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 4195 + "blockRuntimeId" : 4197 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 4196 + "blockRuntimeId" : 4198 }, { "id" : "minecraft:cactus", - "blockRuntimeId" : 6986 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6672 - }, - { - "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7543 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6673 - }, - { - "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6288 + "blockRuntimeId" : 6988 }, { "id" : "minecraft:log", "blockRuntimeId" : 6674 }, { - "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 5972 + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 7545 }, { "id" : "minecraft:log", "blockRuntimeId" : 6675 }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 6290 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6676 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 5974 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6677 + }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 642 + "blockRuntimeId" : 644 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 3830 + "blockRuntimeId" : 3832 }, { "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 5848 + "blockRuntimeId" : 5850 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 3831 + "blockRuntimeId" : 3833 }, { "id" : "minecraft:stripped_dark_oak_log", @@ -1784,43 +1784,27 @@ }, { "id" : "minecraft:mangrove_log", - "blockRuntimeId" : 348 + "blockRuntimeId" : 350 }, { "id" : "minecraft:stripped_mangrove_log", - "blockRuntimeId" : 8284 + "blockRuntimeId" : 8286 }, { "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 5897 + "blockRuntimeId" : 5899 }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 6948 + "blockRuntimeId" : 6950 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 6486 + "blockRuntimeId" : 6488 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7400 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3474 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3480 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3475 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3481 + "blockRuntimeId" : 7402 }, { "id" : "minecraft:wood", @@ -1854,37 +1838,45 @@ "id" : "minecraft:wood", "blockRuntimeId" : 3485 }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3480 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3486 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3481 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3487 + }, { "id" : "minecraft:mangrove_wood", - "blockRuntimeId" : 4161 + "blockRuntimeId" : 4163 }, { "id" : "minecraft:stripped_mangrove_wood", - "blockRuntimeId" : 4229 + "blockRuntimeId" : 4231 }, { "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 4294 + "blockRuntimeId" : 4296 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 6499 + "blockRuntimeId" : 6501 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 5902 + "blockRuntimeId" : 5904 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 5579 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6090 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6091 + "blockRuntimeId" : 5581 }, { "id" : "minecraft:leaves", @@ -1895,32 +1887,32 @@ "blockRuntimeId" : 6093 }, { - "id" : "minecraft:leaves2", - "blockRuntimeId" : 4353 + "id" : "minecraft:leaves", + "blockRuntimeId" : 6094 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6095 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4354 + "blockRuntimeId" : 4355 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 4356 }, { "id" : "minecraft:mangrove_leaves", - "blockRuntimeId" : 6666 + "blockRuntimeId" : 6668 }, { "id" : "minecraft:azalea_leaves", - "blockRuntimeId" : 7710 + "blockRuntimeId" : 7712 }, { "id" : "minecraft:azalea_leaves_flowered", - "blockRuntimeId" : 6339 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 712 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 713 + "blockRuntimeId" : 6341 }, { "id" : "minecraft:sapling", @@ -1938,13 +1930,21 @@ "id" : "minecraft:sapling", "blockRuntimeId" : 717 }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 718 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 719 + }, { "id" : "minecraft:mangrove_propagule", - "blockRuntimeId" : 6976 + "blockRuntimeId" : 6978 }, { "id" : "minecraft:bee_nest", - "blockRuntimeId" : 5754 + "blockRuntimeId" : 5756 }, { "id" : "minecraft:wheat_seeds" @@ -1987,7 +1987,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 392 + "blockRuntimeId" : 394 }, { "id" : "minecraft:melon_slice" @@ -2003,85 +2003,89 @@ }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 4577 + "blockRuntimeId" : 4579 }, { "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 7378 + "blockRuntimeId" : 7380 }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 6685 + "blockRuntimeId" : 6687 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 929 + "blockRuntimeId" : 931 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5455 + "blockRuntimeId" : 5457 }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 928 + "blockRuntimeId" : 930 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5454 + "blockRuntimeId" : 5456 }, { "id" : "minecraft:nether_sprouts" }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6492 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6490 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6491 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6489 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6493 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6497 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6495 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6496 - }, { "id" : "minecraft:coral", "blockRuntimeId" : 6494 }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6492 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6493 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6491 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6495 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6499 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6497 + }, { "id" : "minecraft:coral", "blockRuntimeId" : 6498 }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6496 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6500 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4618 + }, { "id" : "minecraft:coral_fan", "blockRuntimeId" : 4616 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4614 + "blockRuntimeId" : 4617 }, { "id" : "minecraft:coral_fan", @@ -2089,11 +2093,7 @@ }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4613 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4617 + "blockRuntimeId" : 4619 }, { "id" : "minecraft:coral_fan_dead", @@ -2124,24 +2124,16 @@ }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 7573 + "blockRuntimeId" : 7575 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 4362 + "blockRuntimeId" : 4364 }, { "id" : "minecraft:yellow_flower", "blockRuntimeId" : 302 }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3616 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3617 - }, { "id" : "minecraft:red_flower", "blockRuntimeId" : 3618 @@ -2179,24 +2171,32 @@ "blockRuntimeId" : 3626 }, { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5452 + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3627 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3628 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5453 + "blockRuntimeId" : 5454 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5456 + "blockRuntimeId" : 5455 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5457 + "blockRuntimeId" : 5458 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5459 }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 6165 + "blockRuntimeId" : 6167 }, { "id" : "minecraft:white_dye" @@ -2263,35 +2263,35 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 894 + "blockRuntimeId" : 896 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 5479 + "blockRuntimeId" : 5481 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 5691 + "blockRuntimeId" : 5693 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 1158 + "blockRuntimeId" : 1160 }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 4677 + "blockRuntimeId" : 4679 }, { "id" : "minecraft:bamboo", - "blockRuntimeId" : 3684 + "blockRuntimeId" : 3686 }, { "id" : "minecraft:snow", - "blockRuntimeId" : 4194 + "blockRuntimeId" : 4196 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 6689 + "blockRuntimeId" : 6691 }, { "id" : "minecraft:packed_ice", @@ -2299,7 +2299,7 @@ }, { "id" : "minecraft:blue_ice", - "blockRuntimeId" : 7027 + "blockRuntimeId" : 7029 }, { "id" : "minecraft:snow_layer", @@ -2307,11 +2307,11 @@ }, { "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 7416 + "blockRuntimeId" : 7418 }, { "id" : "minecraft:dripstone_block", - "blockRuntimeId" : 893 + "blockRuntimeId" : 895 }, { "id" : "minecraft:moss_carpet", @@ -2319,11 +2319,11 @@ }, { "id" : "minecraft:moss_block", - "blockRuntimeId" : 6538 + "blockRuntimeId" : 6540 }, { "id" : "minecraft:dirt_with_roots", - "blockRuntimeId" : 5397 + "blockRuntimeId" : 5399 }, { "id" : "minecraft:hanging_roots", @@ -2331,7 +2331,7 @@ }, { "id" : "minecraft:mangrove_roots", - "blockRuntimeId" : 6175 + "blockRuntimeId" : 6177 }, { "id" : "minecraft:muddy_mangrove_roots", @@ -2339,27 +2339,27 @@ }, { "id" : "minecraft:big_dripleaf", - "blockRuntimeId" : 5980 + "blockRuntimeId" : 5982 }, { "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 4320 + "blockRuntimeId" : 4322 }, { "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 7312 + "blockRuntimeId" : 7314 }, { "id" : "minecraft:azalea", - "blockRuntimeId" : 6888 + "blockRuntimeId" : 6890 }, { "id" : "minecraft:flowering_azalea", - "blockRuntimeId" : 5477 + "blockRuntimeId" : 5479 }, { "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 5686 }, { "id" : "minecraft:amethyst_block", @@ -2367,19 +2367,19 @@ }, { "id" : "minecraft:budding_amethyst", - "blockRuntimeId" : 7002 + "blockRuntimeId" : 7004 }, { "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 7810 + "blockRuntimeId" : 7812 }, { "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 4728 + "blockRuntimeId" : 4730 }, { "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 4376 + "blockRuntimeId" : 4378 }, { "id" : "minecraft:small_amethyst_bud", @@ -2387,7 +2387,7 @@ }, { "id" : "minecraft:tuff", - "blockRuntimeId" : 347 + "blockRuntimeId" : 349 }, { "id" : "minecraft:calcite", @@ -2422,15 +2422,15 @@ }, { "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 3546 + "blockRuntimeId" : 3548 }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 4585 + "blockRuntimeId" : 4587 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 7753 + "blockRuntimeId" : 7755 }, { "id" : "minecraft:warped_fungus", @@ -2438,19 +2438,19 @@ }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7362 + "blockRuntimeId" : 7364 }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 3611 + "blockRuntimeId" : 3613 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7363 + "blockRuntimeId" : 7365 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7348 + "blockRuntimeId" : 7350 }, { "id" : "minecraft:egg" @@ -2469,22 +2469,14 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 6713 + "blockRuntimeId" : 6715 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 401 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4144 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4145 + "blockRuntimeId" : 403 }, { "id" : "minecraft:monster_egg", @@ -2502,33 +2494,41 @@ "id" : "minecraft:monster_egg", "blockRuntimeId" : 4149 }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4150 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4151 + }, { "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 4641 + "blockRuntimeId" : 4643 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 7271 + "blockRuntimeId" : 7273 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7997 + "blockRuntimeId" : 7999 }, { "id" : "minecraft:frog_spawn", - "blockRuntimeId" : 4399 + "blockRuntimeId" : 4401 }, { "id" : "minecraft:pearlescent_froglight", - "blockRuntimeId" : 6435 + "blockRuntimeId" : 6437 }, { "id" : "minecraft:verdant_froglight", - "blockRuntimeId" : 6481 + "blockRuntimeId" : 6483 }, { "id" : "minecraft:ochre_froglight", - "blockRuntimeId" : 3510 + "blockRuntimeId" : 3512 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2704,6 +2704,9 @@ { "id" : "minecraft:tadpole_spawn_egg" }, + { + "id" : "minecraft:trader_llama_spawn_egg" + }, { "id" : "minecraft:ghast_spawn_egg" }, @@ -2742,42 +2745,42 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 428 + "blockRuntimeId" : 430 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 6722 + "blockRuntimeId" : 6724 }, { "id" : "minecraft:bedrock", - "blockRuntimeId" : 7017 + "blockRuntimeId" : 7019 }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 5831 + "blockRuntimeId" : 5833 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 7037 + "blockRuntimeId" : 7039 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 8009 + "blockRuntimeId" : 8011 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 3836 + "blockRuntimeId" : 3838 }, { "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 4530 + "blockRuntimeId" : 4532 }, { "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 5505 + "blockRuntimeId" : 5507 }, { "id" : "minecraft:chorus_fruit" @@ -2787,19 +2790,11 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 629 + "blockRuntimeId" : 631 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 630 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5237 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5238 + "blockRuntimeId" : 632 }, { "id" : "minecraft:coral_block", @@ -2833,17 +2828,25 @@ "id" : "minecraft:coral_block", "blockRuntimeId" : 5246 }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5247 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5248 + }, { "id" : "minecraft:sculk", - "blockRuntimeId" : 7036 + "blockRuntimeId" : 7038 }, { "id" : "minecraft:sculk_vein", - "blockRuntimeId" : 7132 + "blockRuntimeId" : 7134 }, { "id" : "minecraft:sculk_catalyst", - "blockRuntimeId" : 3613 + "blockRuntimeId" : 3615 }, { "id" : "minecraft:sculk_shrieker", @@ -2851,11 +2854,11 @@ }, { "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 4389 + "blockRuntimeId" : 4391 }, { "id" : "minecraft:reinforced_deepslate", - "blockRuntimeId" : 5832 + "blockRuntimeId" : 5834 }, { "id" : "minecraft:leather_helmet" @@ -3916,86 +3919,86 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 724 + "blockRuntimeId" : 726 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 4644 + "blockRuntimeId" : 4646 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 5855 + "blockRuntimeId" : 5857 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 7074 + "blockRuntimeId" : 7076 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 5749 + "blockRuntimeId" : 5751 }, { "id" : "minecraft:candle", - "blockRuntimeId" : 7403 + "blockRuntimeId" : 7405 }, { "id" : "minecraft:white_candle", - "blockRuntimeId" : 5300 + "blockRuntimeId" : 5302 }, { "id" : "minecraft:orange_candle", - "blockRuntimeId" : 362 + "blockRuntimeId" : 364 }, { "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 418 + "blockRuntimeId" : 420 }, { "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 4569 + "blockRuntimeId" : 4571 }, { "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 6192 + "blockRuntimeId" : 6194 }, { "id" : "minecraft:lime_candle", - "blockRuntimeId" : 6368 + "blockRuntimeId" : 6370 }, { "id" : "minecraft:pink_candle", - "blockRuntimeId" : 7370 + "blockRuntimeId" : 7372 }, { "id" : "minecraft:gray_candle", - "blockRuntimeId" : 939 + "blockRuntimeId" : 941 }, { "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 6224 + "blockRuntimeId" : 6226 }, { "id" : "minecraft:cyan_candle", - "blockRuntimeId" : 7726 + "blockRuntimeId" : 7728 }, { "id" : "minecraft:purple_candle", - "blockRuntimeId" : 7038 + "blockRuntimeId" : 7040 }, { "id" : "minecraft:blue_candle" }, { "id" : "minecraft:brown_candle", - "blockRuntimeId" : 5875 + "blockRuntimeId" : 5877 }, { "id" : "minecraft:green_candle", - "blockRuntimeId" : 686 + "blockRuntimeId" : 688 }, { "id" : "minecraft:red_candle", - "blockRuntimeId" : 4681 + "blockRuntimeId" : 4683 }, { "id" : "minecraft:black_candle", @@ -4003,23 +4006,23 @@ }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 5854 + "blockRuntimeId" : 5856 }, { "id" : "minecraft:cartography_table", - "blockRuntimeId" : 8288 + "blockRuntimeId" : 8290 }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 5833 + "blockRuntimeId" : 5835 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 3726 + "blockRuntimeId" : 3728 }, { "id" : "minecraft:beehive", - "blockRuntimeId" : 6108 + "blockRuntimeId" : 6110 }, { "id" : "minecraft:campfire" @@ -4029,85 +4032,109 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 7802 + "blockRuntimeId" : 7804 }, { "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 7567 + "blockRuntimeId" : 7569 }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 647 + "blockRuntimeId" : 649 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 681 + "blockRuntimeId" : 683 }, { "id" : "minecraft:brewing_stand" }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6634 + "blockRuntimeId" : 6636 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6638 + "blockRuntimeId" : 6640 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6642 + "blockRuntimeId" : 6644 }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 8039 + "blockRuntimeId" : 8041 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 6723 + "blockRuntimeId" : 6725 }, { "id" : "minecraft:bookshelf", - "blockRuntimeId" : 6671 + "blockRuntimeId" : 6673 }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 6940 + "blockRuntimeId" : 6942 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 5415 + "blockRuntimeId" : 5417 }, { "id" : "minecraft:chest", - "blockRuntimeId" : 7115 + "blockRuntimeId" : 7117 }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 5583 + "blockRuntimeId" : 5585 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4369 + "blockRuntimeId" : 4371 }, { "id" : "minecraft:barrel", - "blockRuntimeId" : 4518 + "blockRuntimeId" : 4520 }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 3681 + "blockRuntimeId" : 3683 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5316 + "blockRuntimeId" : 5318 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5324 + "blockRuntimeId" : 5326 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5325 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5333 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5330 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5332 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5319 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5322 }, { "id" : "minecraft:shulker_box", @@ -4119,19 +4146,7 @@ }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5328 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5330 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5317 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5320 + "blockRuntimeId" : 5327 }, { "id" : "minecraft:shulker_box", @@ -4143,38 +4158,26 @@ }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5325 + "blockRuntimeId" : 5328 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5319 + "blockRuntimeId" : 5320 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5327 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5326 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5318 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5322 + "blockRuntimeId" : 5324 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 346 + "blockRuntimeId" : 348 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 4874 + "blockRuntimeId" : 4876 }, { "id" : "minecraft:music_disc_13" @@ -4229,7 +4232,7 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 3885 + "blockRuntimeId" : 3887 }, { "id" : "minecraft:redstone_lamp", @@ -4237,7 +4240,7 @@ }, { "id" : "minecraft:sea_lantern", - "blockRuntimeId" : 7546 + "blockRuntimeId" : 7548 }, { "id" : "minecraft:oak_sign" @@ -4346,19 +4349,19 @@ }, { "id" : "minecraft:bell", - "blockRuntimeId" : 6908 + "blockRuntimeId" : 6910 }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 4232 + "blockRuntimeId" : 4234 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7574 + "blockRuntimeId" : 7576 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 6077 + "blockRuntimeId" : 6079 }, { "id" : "minecraft:coal" @@ -4497,11 +4500,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 5891 + "blockRuntimeId" : 5893 }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 1176 + "blockRuntimeId" : 1178 }, { "id" : "minecraft:end_crystal" @@ -4999,15 +5002,15 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 3920 + "blockRuntimeId" : 3922 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 5332 + "blockRuntimeId" : 5334 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 4132 + "blockRuntimeId" : 4134 }, { "id" : "minecraft:activator_rail", @@ -5030,27 +5033,27 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 3776 + "blockRuntimeId" : 3778 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 3525 + "blockRuntimeId" : 3527 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 6514 + "blockRuntimeId" : 6516 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 6391 + "blockRuntimeId" : 6393 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 4321 + "blockRuntimeId" : 4323 }, { "id" : "minecraft:birch_button", - "blockRuntimeId" : 7766 + "blockRuntimeId" : 7768 }, { "id" : "minecraft:jungle_button", @@ -5058,7 +5061,7 @@ }, { "id" : "minecraft:acacia_button", - "blockRuntimeId" : 7231 + "blockRuntimeId" : 7233 }, { "id" : "minecraft:dark_oak_button", @@ -5066,59 +5069,59 @@ }, { "id" : "minecraft:mangrove_button", - "blockRuntimeId" : 7062 + "blockRuntimeId" : 7064 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 596 + "blockRuntimeId" : 598 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 4432 + "blockRuntimeId" : 4434 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 7250 + "blockRuntimeId" : 7252 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 7790 + "blockRuntimeId" : 7792 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 5914 + "blockRuntimeId" : 5916 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 8063 + "blockRuntimeId" : 8065 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 3759 + "blockRuntimeId" : 3761 }, { "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 3555 + "blockRuntimeId" : 3557 }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 3635 + "blockRuntimeId" : 3637 }, { "id" : "minecraft:acacia_pressure_plate", - "blockRuntimeId" : 5247 + "blockRuntimeId" : 5249 }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 5956 + "blockRuntimeId" : 5958 }, { "id" : "minecraft:mangrove_pressure_plate", - "blockRuntimeId" : 3869 + "blockRuntimeId" : 3871 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 8268 + "blockRuntimeId" : 8270 }, { "id" : "minecraft:warped_pressure_plate", @@ -5126,27 +5129,27 @@ }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 3886 + "blockRuntimeId" : 3888 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 3665 + "blockRuntimeId" : 3667 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 1160 + "blockRuntimeId" : 1162 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6232 + "blockRuntimeId" : 6234 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 3513 + "blockRuntimeId" : 3515 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 4197 + "blockRuntimeId" : 4199 }, { "id" : "minecraft:repeater" @@ -5159,30 +5162,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 7385 + "blockRuntimeId" : 7387 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 8013 + "blockRuntimeId" : 8015 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 922 + "blockRuntimeId" : 924 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 4364 + "blockRuntimeId" : 4366 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 6707 + "blockRuntimeId" : 6709 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 3826 + "blockRuntimeId" : 3828 }, { "id" : "minecraft:banner" @@ -5428,7 +5431,7 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 6390 + "blockRuntimeId" : 6392 }, { "id" : "minecraft:lodestone_compass" diff --git a/core/src/main/resources/bedrock/creative_items.1_19_50.json b/core/src/main/resources/bedrock/creative_items.1_19_50.json new file mode 100644 index 000000000..4ed5f8194 --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_19_50.json @@ -0,0 +1,5533 @@ +{ + "items" : [ + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9805 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9806 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9807 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9808 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9809 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9810 + }, + { + "id" : "minecraft:mangrove_planks", + "blockRuntimeId" : 1570 + }, + { + "id" : "minecraft:bamboo_planks", + "blockRuntimeId" : 8202 + }, + { + "id" : "minecraft:bamboo_mosaic", + "blockRuntimeId" : 12438 + }, + { + "id" : "minecraft:crimson_planks", + "blockRuntimeId" : 7399 + }, + { + "id" : "minecraft:warped_planks", + "blockRuntimeId" : 1543 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1805 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1806 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1807 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1808 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1809 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1810 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1817 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1812 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1813 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1811 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1814 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1818 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1815 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1816 + }, + { + "id" : "minecraft:blackstone_wall", + "blockRuntimeId" : 5707 + }, + { + "id" : "minecraft:polished_blackstone_wall", + "blockRuntimeId" : 10496 + }, + { + "id" : "minecraft:polished_blackstone_brick_wall", + "blockRuntimeId" : 1594 + }, + { + "id" : "minecraft:cobbled_deepslate_wall", + "blockRuntimeId" : 12260 + }, + { + "id" : "minecraft:deepslate_tile_wall", + "blockRuntimeId" : 7636 + }, + { + "id" : "minecraft:polished_deepslate_wall", + "blockRuntimeId" : 11995 + }, + { + "id" : "minecraft:deepslate_brick_wall", + "blockRuntimeId" : 659 + }, + { + "id" : "minecraft:mud_brick_wall", + "blockRuntimeId" : 1353 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11542 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11543 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11544 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11545 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11546 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11547 + }, + { + "id" : "minecraft:mangrove_fence", + "blockRuntimeId" : 10405 + }, + { + "id" : "minecraft:bamboo_fence", + "blockRuntimeId" : 863 + }, + { + "id" : "minecraft:nether_brick_fence", + "blockRuntimeId" : 6071 + }, + { + "id" : "minecraft:crimson_fence", + "blockRuntimeId" : 12174 + }, + { + "id" : "minecraft:warped_fence", + "blockRuntimeId" : 8819 + }, + { + "id" : "minecraft:fence_gate", + "blockRuntimeId" : 76 + }, + { + "id" : "minecraft:spruce_fence_gate", + "blockRuntimeId" : 10356 + }, + { + "id" : "minecraft:birch_fence_gate", + "blockRuntimeId" : 5170 + }, + { + "id" : "minecraft:jungle_fence_gate", + "blockRuntimeId" : 7946 + }, + { + "id" : "minecraft:acacia_fence_gate", + "blockRuntimeId" : 11764 + }, + { + "id" : "minecraft:dark_oak_fence_gate", + "blockRuntimeId" : 5950 + }, + { + "id" : "minecraft:mangrove_fence_gate", + "blockRuntimeId" : 6406 + }, + { + "id" : "minecraft:bamboo_fence_gate", + "blockRuntimeId" : 7611 + }, + { + "id" : "minecraft:crimson_fence_gate", + "blockRuntimeId" : 6826 + }, + { + "id" : "minecraft:warped_fence_gate", + "blockRuntimeId" : 7980 + }, + { + "id" : "minecraft:normal_stone_stairs", + "blockRuntimeId" : 864 + }, + { + "id" : "minecraft:stone_stairs", + "blockRuntimeId" : 5101 + }, + { + "id" : "minecraft:mossy_cobblestone_stairs", + "blockRuntimeId" : 5869 + }, + { + "id" : "minecraft:oak_stairs", + "blockRuntimeId" : 273 + }, + { + "id" : "minecraft:spruce_stairs", + "blockRuntimeId" : 128 + }, + { + "id" : "minecraft:birch_stairs", + "blockRuntimeId" : 10781 + }, + { + "id" : "minecraft:jungle_stairs", + "blockRuntimeId" : 10745 + }, + { + "id" : "minecraft:acacia_stairs", + "blockRuntimeId" : 9950 + }, + { + "id" : "minecraft:dark_oak_stairs", + "blockRuntimeId" : 7628 + }, + { + "id" : "minecraft:mangrove_stairs", + "blockRuntimeId" : 6376 + }, + { + "id" : "minecraft:bamboo_stairs", + "blockRuntimeId" : 1339 + }, + { + "id" : "minecraft:bamboo_mosaic_stairs", + "blockRuntimeId" : 9958 + }, + { + "id" : "minecraft:stone_brick_stairs", + "blockRuntimeId" : 1554 + }, + { + "id" : "minecraft:mossy_stone_brick_stairs", + "blockRuntimeId" : 9233 + }, + { + "id" : "minecraft:sandstone_stairs", + "blockRuntimeId" : 4980 + }, + { + "id" : "minecraft:smooth_sandstone_stairs", + "blockRuntimeId" : 5020 + }, + { + "id" : "minecraft:red_sandstone_stairs", + "blockRuntimeId" : 7931 + }, + { + "id" : "minecraft:smooth_red_sandstone_stairs", + "blockRuntimeId" : 8127 + }, + { + "id" : "minecraft:granite_stairs", + "blockRuntimeId" : 4546 + }, + { + "id" : "minecraft:polished_granite_stairs", + "blockRuntimeId" : 5927 + }, + { + "id" : "minecraft:diorite_stairs", + "blockRuntimeId" : 6172 + }, + { + "id" : "minecraft:polished_diorite_stairs", + "blockRuntimeId" : 10486 + }, + { + "id" : "minecraft:andesite_stairs", + "blockRuntimeId" : 7889 + }, + { + "id" : "minecraft:polished_andesite_stairs", + "blockRuntimeId" : 10806 + }, + { + "id" : "minecraft:brick_stairs", + "blockRuntimeId" : 10302 + }, + { + "id" : "minecraft:nether_brick_stairs", + "blockRuntimeId" : 106 + }, + { + "id" : "minecraft:red_nether_brick_stairs", + "blockRuntimeId" : 10374 + }, + { + "id" : "minecraft:end_brick_stairs", + "blockRuntimeId" : 10140 + }, + { + "id" : "minecraft:quartz_stairs", + "blockRuntimeId" : 6932 + }, + { + "id" : "minecraft:smooth_quartz_stairs", + "blockRuntimeId" : 11878 + }, + { + "id" : "minecraft:purpur_stairs", + "blockRuntimeId" : 11933 + }, + { + "id" : "minecraft:prismarine_stairs", + "blockRuntimeId" : 11441 + }, + { + "id" : "minecraft:dark_prismarine_stairs", + "blockRuntimeId" : 11608 + }, + { + "id" : "minecraft:prismarine_bricks_stairs", + "blockRuntimeId" : 206 + }, + { + "id" : "minecraft:crimson_stairs", + "blockRuntimeId" : 10038 + }, + { + "id" : "minecraft:warped_stairs", + "blockRuntimeId" : 5111 + }, + { + "id" : "minecraft:blackstone_stairs", + "blockRuntimeId" : 10797 + }, + { + "id" : "minecraft:polished_blackstone_stairs", + "blockRuntimeId" : 6078 + }, + { + "id" : "minecraft:polished_blackstone_brick_stairs", + "blockRuntimeId" : 6258 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 6385 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 6368 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 6086 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 581 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 623 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 5679 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 9917 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_stairs", + "blockRuntimeId" : 8806 + }, + { + "id" : "minecraft:cobbled_deepslate_stairs", + "blockRuntimeId" : 147 + }, + { + "id" : "minecraft:deepslate_tile_stairs", + "blockRuntimeId" : 6818 + }, + { + "id" : "minecraft:polished_deepslate_stairs", + "blockRuntimeId" : 522 + }, + { + "id" : "minecraft:deepslate_brick_stairs", + "blockRuntimeId" : 11600 + }, + { + "id" : "minecraft:mud_brick_stairs", + "blockRuntimeId" : 8103 + }, + { + "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:bamboo_door" + }, + { + "id" : "minecraft:iron_door" + }, + { + "id" : "minecraft:crimson_door" + }, + { + "id" : "minecraft:warped_door" + }, + { + "id" : "minecraft:trapdoor", + "blockRuntimeId" : 229 + }, + { + "id" : "minecraft:spruce_trapdoor", + "blockRuntimeId" : 10324 + }, + { + "id" : "minecraft:birch_trapdoor", + "blockRuntimeId" : 10422 + }, + { + "id" : "minecraft:jungle_trapdoor", + "blockRuntimeId" : 7962 + }, + { + "id" : "minecraft:acacia_trapdoor", + "blockRuntimeId" : 8170 + }, + { + "id" : "minecraft:dark_oak_trapdoor", + "blockRuntimeId" : 11680 + }, + { + "id" : "minecraft:mangrove_trapdoor", + "blockRuntimeId" : 6266 + }, + { + "id" : "minecraft:bamboo_trapdoor", + "blockRuntimeId" : 7828 + }, + { + "id" : "minecraft:iron_trapdoor", + "blockRuntimeId" : 549 + }, + { + "id" : "minecraft:crimson_trapdoor", + "blockRuntimeId" : 6114 + }, + { + "id" : "minecraft:warped_trapdoor", + "blockRuntimeId" : 6898 + }, + { + "id" : "minecraft:iron_bars", + "blockRuntimeId" : 6966 + }, + { + "id" : "minecraft:glass", + "blockRuntimeId" : 9914 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1756 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1764 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1763 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1771 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1768 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1770 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1757 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1760 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1761 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1769 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1765 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1759 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1767 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1766 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1758 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1762 + }, + { + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 9325 + }, + { + "id" : "minecraft:glass_pane", + "blockRuntimeId" : 7798 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7401 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7409 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7408 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7416 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7413 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7415 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7402 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7405 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7406 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7414 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7410 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7404 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7412 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7411 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7403 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7407 + }, + { + "id" : "minecraft:ladder", + "blockRuntimeId" : 12441 + }, + { + "id" : "minecraft:scaffolding", + "blockRuntimeId" : 4964 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6049 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8404 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6052 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8375 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7851 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7852 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7853 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7854 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7855 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7856 + }, + { + "id" : "minecraft:mangrove_slab", + "blockRuntimeId" : 1772 + }, + { + "id" : "minecraft:bamboo_slab", + "blockRuntimeId" : 10300 + }, + { + "id" : "minecraft:bamboo_mosaic_slab", + "blockRuntimeId" : 4081 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6054 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8402 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6050 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8405 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8376 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8370 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8406 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8387 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8392 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8393 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8390 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8391 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8389 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8388 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6053 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6056 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8377 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8386 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6055 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8403 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8371 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8372 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8373 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8374 + }, + { + "id" : "minecraft:crimson_slab", + "blockRuntimeId" : 9250 + }, + { + "id" : "minecraft:warped_slab", + "blockRuntimeId" : 10254 + }, + { + "id" : "minecraft:blackstone_slab", + "blockRuntimeId" : 1533 + }, + { + "id" : "minecraft:polished_blackstone_slab", + "blockRuntimeId" : 9752 + }, + { + "id" : "minecraft:polished_blackstone_brick_slab", + "blockRuntimeId" : 5971 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 7800 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 10372 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 9787 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 7863 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 11993 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 249 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 10317 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 1323 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 11488 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 288 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 6072 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 5109 + }, + { + "id" : "minecraft:mud_brick_slab", + "blockRuntimeId" : 5687 + }, + { + "id" : "minecraft:brick_block", + "blockRuntimeId" : 6930 + }, + { + "id" : "minecraft:chiseled_nether_bricks", + "blockRuntimeId" : 11427 + }, + { + "id" : "minecraft:cracked_nether_bricks", + "blockRuntimeId" : 6333 + }, + { + "id" : "minecraft:quartz_bricks", + "blockRuntimeId" : 10109 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10319 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10320 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10321 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10322 + }, + { + "id" : "minecraft:end_bricks", + "blockRuntimeId" : 281 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9837 + }, + { + "id" : "minecraft:polished_blackstone_bricks", + "blockRuntimeId" : 6845 + }, + { + "id" : "minecraft:cracked_polished_blackstone_bricks", + "blockRuntimeId" : 11376 + }, + { + "id" : "minecraft:gilded_blackstone", + "blockRuntimeId" : 6367 + }, + { + "id" : "minecraft:chiseled_polished_blackstone", + "blockRuntimeId" : 7627 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 6362 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 5937 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 8045 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 7945 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 7799 + }, + { + "id" : "minecraft:cobblestone", + "blockRuntimeId" : 5008 + }, + { + "id" : "minecraft:mossy_cobblestone", + "blockRuntimeId" : 252 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 10442 + }, + { + "id" : "minecraft:smooth_stone", + "blockRuntimeId" : 6363 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5046 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5047 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5048 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5049 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10352 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10353 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10354 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10355 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 7979 + }, + { + "id" : "minecraft:dried_kelp_block", + "blockRuntimeId" : 12157 + }, + { + "id" : "minecraft:gold_block", + "blockRuntimeId" : 323 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 12440 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 6816 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 823 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 12424 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 4946 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 11912 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 1309 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 1322 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 11720 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 6854 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 9916 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 11359 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 8059 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 11471 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 5202 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 7400 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 214 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 1782 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 272 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 6065 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 12439 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 7850 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 591 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5089 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5091 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5090 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5092 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9835 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9836 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 6012 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 1515 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 6257 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 1310 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 6013 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 11450 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 146 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 5168 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 12437 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4083 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4091 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4090 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4098 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4095 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4097 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4084 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4087 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4088 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4096 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4092 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4086 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4094 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4093 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4085 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4089 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1572 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1580 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1579 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1587 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1584 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1586 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1573 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1576 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1577 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1585 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1581 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1575 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1583 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1582 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1574 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1578 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10022 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10030 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10029 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10037 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10034 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10036 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10023 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10026 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10027 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10035 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10031 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10025 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10033 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10032 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10024 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10028 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1275 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1283 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1282 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1290 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1287 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1289 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1276 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1279 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1280 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1288 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1284 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1278 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1286 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1285 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1277 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1281 + }, + { + "id" : "minecraft:clay", + "blockRuntimeId" : 10902 + }, + { + "id" : "minecraft:hardened_clay", + "blockRuntimeId" : 872 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9926 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9934 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9933 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9941 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9938 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9940 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9927 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9930 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9931 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9939 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9935 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9929 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9937 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9936 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9928 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9932 + }, + { + "id" : "minecraft:white_glazed_terracotta", + "blockRuntimeId" : 8154 + }, + { + "id" : "minecraft:silver_glazed_terracotta", + "blockRuntimeId" : 4540 + }, + { + "id" : "minecraft:gray_glazed_terracotta", + "blockRuntimeId" : 12431 + }, + { + "id" : "minecraft:black_glazed_terracotta", + "blockRuntimeId" : 8800 + }, + { + "id" : "minecraft:brown_glazed_terracotta", + "blockRuntimeId" : 4940 + }, + { + "id" : "minecraft:red_glazed_terracotta", + "blockRuntimeId" : 5944 + }, + { + "id" : "minecraft:orange_glazed_terracotta", + "blockRuntimeId" : 1774 + }, + { + "id" : "minecraft:yellow_glazed_terracotta", + "blockRuntimeId" : 1536 + }, + { + "id" : "minecraft:lime_glazed_terracotta", + "blockRuntimeId" : 223 + }, + { + "id" : "minecraft:green_glazed_terracotta", + "blockRuntimeId" : 10382 + }, + { + "id" : "minecraft:cyan_glazed_terracotta", + "blockRuntimeId" : 7939 + }, + { + "id" : "minecraft:light_blue_glazed_terracotta", + "blockRuntimeId" : 8052 + }, + { + "id" : "minecraft:blue_glazed_terracotta", + "blockRuntimeId" : 8046 + }, + { + "id" : "minecraft:purple_glazed_terracotta", + "blockRuntimeId" : 10789 + }, + { + "id" : "minecraft:magenta_glazed_terracotta", + "blockRuntimeId" : 1588 + }, + { + "id" : "minecraft:pink_glazed_terracotta", + "blockRuntimeId" : 10311 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 11892 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 11894 + }, + { + "id" : "minecraft:packed_mud", + "blockRuntimeId" : 283 + }, + { + "id" : "minecraft:mud_bricks", + "blockRuntimeId" : 10661 + }, + { + "id" : "minecraft:nether_wart_block", + "blockRuntimeId" : 6074 + }, + { + "id" : "minecraft:warped_wart_block", + "blockRuntimeId" : 9255 + }, + { + "id" : "minecraft:shroomlight", + "blockRuntimeId" : 7610 + }, + { + "id" : "minecraft:crimson_nylium", + "blockRuntimeId" : 5968 + }, + { + "id" : "minecraft:warped_nylium", + "blockRuntimeId" : 10107 + }, + { + "id" : "minecraft:basalt", + "blockRuntimeId" : 6130 + }, + { + "id" : "minecraft:polished_basalt", + "blockRuntimeId" : 24 + }, + { + "id" : "minecraft:smooth_basalt", + "blockRuntimeId" : 1780 + }, + { + "id" : "minecraft:soul_soil", + "blockRuntimeId" : 8412 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 8333 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 8334 + }, + { + "id" : "minecraft:farmland", + "blockRuntimeId" : 5689 + }, + { + "id" : "minecraft:grass", + "blockRuntimeId" : 10753 + }, + { + "id" : "minecraft:grass_path", + "blockRuntimeId" : 12259 + }, + { + "id" : "minecraft:podzol", + "blockRuntimeId" : 6815 + }, + { + "id" : "minecraft:mycelium", + "blockRuntimeId" : 5076 + }, + { + "id" : "minecraft:mud", + "blockRuntimeId" : 10456 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1268 + }, + { + "id" : "minecraft:iron_ore", + "blockRuntimeId" : 6855 + }, + { + "id" : "minecraft:gold_ore", + "blockRuntimeId" : 1535 + }, + { + "id" : "minecraft:diamond_ore", + "blockRuntimeId" : 6142 + }, + { + "id" : "minecraft:lapis_ore", + "blockRuntimeId" : 11877 + }, + { + "id" : "minecraft:redstone_ore", + "blockRuntimeId" : 6068 + }, + { + "id" : "minecraft:coal_ore", + "blockRuntimeId" : 6066 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 4947 + }, + { + "id" : "minecraft:emerald_ore", + "blockRuntimeId" : 11525 + }, + { + "id" : "minecraft:quartz_ore", + "blockRuntimeId" : 6282 + }, + { + "id" : "minecraft:nether_gold_ore", + "blockRuntimeId" : 27 + }, + { + "id" : "minecraft:ancient_debris", + "blockRuntimeId" : 9857 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 11451 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 9856 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 12216 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 11440 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 10388 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 10108 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 11358 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 105 + }, + { + "id" : "minecraft:gravel", + "blockRuntimeId" : 12466 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1269 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1271 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1273 + }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 11763 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 253 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1270 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1272 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1274 + }, + { + "id" : "minecraft:polished_blackstone", + "blockRuntimeId" : 5075 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 11932 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 5974 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 5975 + }, + { + "id" : "minecraft:cactus", + "blockRuntimeId" : 10764 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10444 + }, + { + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 11721 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10445 + }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 10046 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10446 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 9322 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10447 + }, + { + "id" : "minecraft:stripped_jungle_log", + "blockRuntimeId" : 1257 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 5607 + }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 8814 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 5608 + }, + { + "id" : "minecraft:stripped_dark_oak_log", + "blockRuntimeId" : 216 + }, + { + "id" : "minecraft:mangrove_log", + "blockRuntimeId" : 578 + }, + { + "id" : "minecraft:stripped_mangrove_log", + "blockRuntimeId" : 12463 + }, + { + "id" : "minecraft:crimson_stem", + "blockRuntimeId" : 9247 + }, + { + "id" : "minecraft:stripped_crimson_stem", + "blockRuntimeId" : 10726 + }, + { + "id" : "minecraft:warped_stem", + "blockRuntimeId" : 10256 + }, + { + "id" : "minecraft:stripped_warped_stem", + "blockRuntimeId" : 11578 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4099 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4105 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4100 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4106 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4101 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4107 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4102 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4108 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4103 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4109 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4104 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4110 + }, + { + "id" : "minecraft:mangrove_wood", + "blockRuntimeId" : 5938 + }, + { + "id" : "minecraft:stripped_mangrove_wood", + "blockRuntimeId" : 6008 + }, + { + "id" : "minecraft:crimson_hyphae", + "blockRuntimeId" : 6075 + }, + { + "id" : "minecraft:stripped_crimson_hyphae", + "blockRuntimeId" : 10269 + }, + { + "id" : "minecraft:warped_hyphae", + "blockRuntimeId" : 9252 + }, + { + "id" : "minecraft:stripped_warped_hyphae", + "blockRuntimeId" : 8160 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9840 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9841 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9842 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9843 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 6134 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 6135 + }, + { + "id" : "minecraft:mangrove_leaves", + "blockRuntimeId" : 10438 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 11888 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 10097 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1327 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1328 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1329 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1330 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1331 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1332 + }, + { + "id" : "minecraft:mangrove_propagule", + "blockRuntimeId" : 10754 + }, + { + "id" : "minecraft:bee_nest", + "blockRuntimeId" : 8336 + }, + { + "id" : "minecraft:wheat_seeds" + }, + { + "id" : "minecraft:pumpkin_seeds" + }, + { + "id" : "minecraft:melon_seeds" + }, + { + "id" : "minecraft:beetroot_seeds" + }, + { + "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", + "blockRuntimeId" : 622 + }, + { + "id" : "minecraft:melon_slice" + }, + { + "id" : "minecraft:glistering_melon_slice" + }, + { + "id" : "minecraft:sweet_berries" + }, + { + "id" : "minecraft:glow_berries" + }, + { + "id" : "minecraft:pumpkin", + "blockRuntimeId" : 6358 + }, + { + "id" : "minecraft:carved_pumpkin", + "blockRuntimeId" : 11556 + }, + { + "id" : "minecraft:lit_pumpkin", + "blockRuntimeId" : 10457 + }, + { + "id" : "minecraft:honeycomb" + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 1552 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8036 + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 1551 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8035 + }, + { + "id" : "minecraft:nether_sprouts" + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10262 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10260 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10261 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10259 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10263 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10267 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10265 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10266 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10264 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10268 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6397 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6395 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6396 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6394 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6398 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 69 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 67 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 68 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 66 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 70 + }, + { + "id" : "minecraft:crimson_roots", + "blockRuntimeId" : 11751 + }, + { + "id" : "minecraft:warped_roots", + "blockRuntimeId" : 6143 + }, + { + "id" : "minecraft:yellow_flower", + "blockRuntimeId" : 530 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5009 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5010 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5011 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5012 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5013 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5014 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5015 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5016 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5017 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5018 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5019 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8033 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8034 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8037 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8038 + }, + { + "id" : "minecraft:wither_rose", + "blockRuntimeId" : 9915 + }, + { + "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", + "blockRuntimeId" : 1517 + }, + { + "id" : "minecraft:weeping_vines", + "blockRuntimeId" : 8060 + }, + { + "id" : "minecraft:twisting_vines", + "blockRuntimeId" : 8273 + }, + { + "id" : "minecraft:waterlily", + "blockRuntimeId" : 1781 + }, + { + "id" : "minecraft:seagrass", + "blockRuntimeId" : 246 + }, + { + "id" : "minecraft:kelp" + }, + { + "id" : "minecraft:deadbush", + "blockRuntimeId" : 6842 + }, + { + "id" : "minecraft:bamboo", + "blockRuntimeId" : 5077 + }, + { + "id" : "minecraft:snow", + "blockRuntimeId" : 5973 + }, + { + "id" : "minecraft:ice", + "blockRuntimeId" : 10461 + }, + { + "id" : "minecraft:packed_ice", + "blockRuntimeId" : 282 + }, + { + "id" : "minecraft:blue_ice", + "blockRuntimeId" : 10805 + }, + { + "id" : "minecraft:snow_layer", + "blockRuntimeId" : 155 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 11594 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 1516 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 286 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 10310 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 7978 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 205 + }, + { + "id" : "minecraft:mangrove_roots", + "blockRuntimeId" : 9925 + }, + { + "id" : "minecraft:muddy_mangrove_roots", + "blockRuntimeId" : 573 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 9330 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 6101 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 11490 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 10660 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 8058 + }, + { + "id" : "minecraft:glow_lichen", + "blockRuntimeId" : 8266 + }, + { + "id" : "minecraft:amethyst_block", + "blockRuntimeId" : 322 + }, + { + "id" : "minecraft:budding_amethyst", + "blockRuntimeId" : 10780 + }, + { + "id" : "minecraft:amethyst_cluster", + "blockRuntimeId" : 11988 + }, + { + "id" : "minecraft:large_amethyst_bud", + "blockRuntimeId" : 6893 + }, + { + "id" : "minecraft:medium_amethyst_bud", + "blockRuntimeId" : 6157 + }, + { + "id" : "minecraft:small_amethyst_bud", + "blockRuntimeId" : 532 + }, + { + "id" : "minecraft:tuff", + "blockRuntimeId" : 577 + }, + { + "id" : "minecraft:calcite", + "blockRuntimeId" : 215 + }, + { + "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", + "blockRuntimeId" : 4939 + }, + { + "id" : "minecraft:red_mushroom", + "blockRuntimeId" : 6366 + }, + { + "id" : "minecraft:crimson_fungus", + "blockRuntimeId" : 11931 + }, + { + "id" : "minecraft:warped_fungus", + "blockRuntimeId" : 287 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11540 + }, + { + "id" : "minecraft:red_mushroom_block", + "blockRuntimeId" : 5004 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11541 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11526 + }, + { + "id" : "minecraft:egg" + }, + { + "id" : "minecraft:sugar_cane" + }, + { + "id" : "minecraft:sugar" + }, + { + "id" : "minecraft:rotten_flesh" + }, + { + "id" : "minecraft:bone" + }, + { + "id" : "minecraft:web", + "blockRuntimeId" : 10485 + }, + { + "id" : "minecraft:spider_eye" + }, + { + "id" : "minecraft:mob_spawner", + "blockRuntimeId" : 631 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5921 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5922 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5923 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5924 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5925 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5926 + }, + { + "id" : "minecraft:infested_deepslate", + "blockRuntimeId" : 6806 + }, + { + "id" : "minecraft:dragon_egg", + "blockRuntimeId" : 11449 + }, + { + "id" : "minecraft:turtle_egg", + "blockRuntimeId" : 12175 + }, + { + "id" : "minecraft:frog_spawn", + "blockRuntimeId" : 6180 + }, + { + "id" : "minecraft:pearlescent_froglight", + "blockRuntimeId" : 10193 + }, + { + "id" : "minecraft:verdant_froglight", + "blockRuntimeId" : 10251 + }, + { + "id" : "minecraft:ochre_froglight", + "blockRuntimeId" : 4519 + }, + { + "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:obsidian", + "blockRuntimeId" : 658 + }, + { + "id" : "minecraft:crying_obsidian", + "blockRuntimeId" : 10494 + }, + { + "id" : "minecraft:bedrock", + "blockRuntimeId" : 10795 + }, + { + "id" : "minecraft:soul_sand", + "blockRuntimeId" : 8413 + }, + { + "id" : "minecraft:netherrack", + "blockRuntimeId" : 10815 + }, + { + "id" : "minecraft:magma", + "blockRuntimeId" : 12187 + }, + { + "id" : "minecraft:nether_wart" + }, + { + "id" : "minecraft:end_stone", + "blockRuntimeId" : 5613 + }, + { + "id" : "minecraft:chorus_flower", + "blockRuntimeId" : 6311 + }, + { + "id" : "minecraft:chorus_plant", + "blockRuntimeId" : 8086 + }, + { + "id" : "minecraft:chorus_fruit" + }, + { + "id" : "minecraft:popped_chorus_fruit" + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 859 + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 860 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7802 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7803 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7804 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7805 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7806 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7807 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7808 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7809 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7810 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7811 + }, + { + "id" : "minecraft:sculk", + "blockRuntimeId" : 10814 + }, + { + "id" : "minecraft:sculk_vein", + "blockRuntimeId" : 11294 + }, + { + "id" : "minecraft:sculk_catalyst", + "blockRuntimeId" : 5006 + }, + { + "id" : "minecraft:sculk_shrieker", + "blockRuntimeId" : 219 + }, + { + "id" : "minecraft:sculk_sensor", + "blockRuntimeId" : 6170 + }, + { + "id" : "minecraft:reinforced_deepslate", + "blockRuntimeId" : 8798 + }, + { + "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: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: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: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: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: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: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:spyglass" + }, + { + "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", + "blockRuntimeId" : 1347 + }, + { + "id" : "minecraft:soul_torch", + "blockRuntimeId" : 6809 + }, + { + "id" : "minecraft:sea_pickle", + "blockRuntimeId" : 8821 + }, + { + "id" : "minecraft:lantern", + "blockRuntimeId" : 10852 + }, + { + "id" : "minecraft:soul_lantern", + "blockRuntimeId" : 8331 + }, + { + "id" : "minecraft:candle", + "blockRuntimeId" : 11581 + }, + { + "id" : "minecraft:white_candle", + "blockRuntimeId" : 7881 + }, + { + "id" : "minecraft:orange_candle", + "blockRuntimeId" : 592 + }, + { + "id" : "minecraft:magenta_candle", + "blockRuntimeId" : 648 + }, + { + "id" : "minecraft:light_blue_candle", + "blockRuntimeId" : 6350 + }, + { + "id" : "minecraft:yellow_candle", + "blockRuntimeId" : 9942 + }, + { + "id" : "minecraft:lime_candle", + "blockRuntimeId" : 10126 + }, + { + "id" : "minecraft:pink_candle", + "blockRuntimeId" : 11548 + }, + { + "id" : "minecraft:gray_candle", + "blockRuntimeId" : 1562 + }, + { + "id" : "minecraft:light_gray_candle", + "blockRuntimeId" : 9982 + }, + { + "id" : "minecraft:cyan_candle", + "blockRuntimeId" : 11904 + }, + { + "id" : "minecraft:purple_candle", + "blockRuntimeId" : 10816 + }, + { + "id" : "minecraft:blue_candle" + }, + { + "id" : "minecraft:brown_candle", + "blockRuntimeId" : 9225 + }, + { + "id" : "minecraft:green_candle", + "blockRuntimeId" : 1301 + }, + { + "id" : "minecraft:red_candle", + "blockRuntimeId" : 6846 + }, + { + "id" : "minecraft:black_candle", + "blockRuntimeId" : 171 + }, + { + "id" : "minecraft:crafting_table", + "blockRuntimeId" : 8820 + }, + { + "id" : "minecraft:cartography_table", + "blockRuntimeId" : 12467 + }, + { + "id" : "minecraft:fletching_table", + "blockRuntimeId" : 8799 + }, + { + "id" : "minecraft:smithing_table", + "blockRuntimeId" : 5119 + }, + { + "id" : "minecraft:beehive", + "blockRuntimeId" : 9858 + }, + { + "id" : "minecraft:campfire" + }, + { + "id" : "minecraft:soul_campfire" + }, + { + "id" : "minecraft:furnace", + "blockRuntimeId" : 11980 + }, + { + "id" : "minecraft:blast_furnace", + "blockRuntimeId" : 11745 + }, + { + "id" : "minecraft:smoker", + "blockRuntimeId" : 1262 + }, + { + "id" : "minecraft:respawn_anchor", + "blockRuntimeId" : 1296 + }, + { + "id" : "minecraft:brewing_stand" + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10406 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10410 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10414 + }, + { + "id" : "minecraft:grindstone", + "blockRuntimeId" : 12217 + }, + { + "id" : "minecraft:enchanting_table", + "blockRuntimeId" : 10495 + }, + { + "id" : "minecraft:bookshelf", + "blockRuntimeId" : 10443 + }, + { + "id" : "minecraft:chiseled_bookshelf", + "blockRuntimeId" : 326 + }, + { + "id" : "minecraft:lectern", + "blockRuntimeId" : 10718 + }, + { + "id" : "minecraft:cauldron" + }, + { + "id" : "minecraft:composter", + "blockRuntimeId" : 7996 + }, + { + "id" : "minecraft:chest", + "blockRuntimeId" : 10893 + }, + { + "id" : "minecraft:trapped_chest", + "blockRuntimeId" : 8164 + }, + { + "id" : "minecraft:ender_chest", + "blockRuntimeId" : 6150 + }, + { + "id" : "minecraft:barrel", + "blockRuntimeId" : 6299 + }, + { + "id" : "minecraft:undyed_shulker_box", + "blockRuntimeId" : 5074 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7897 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7905 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7904 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7912 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7909 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7911 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7898 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7901 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7902 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7910 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7906 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7900 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7908 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7907 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7899 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7903 + }, + { + "id" : "minecraft:armor_stand" + }, + { + "id" : "minecraft:noteblock", + "blockRuntimeId" : 576 + }, + { + "id" : "minecraft:jukebox", + "blockRuntimeId" : 7423 + }, + { + "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:disc_fragment_5" + }, + { + "id" : "minecraft:glowstone_dust" + }, + { + "id" : "minecraft:glowstone", + "blockRuntimeId" : 5662 + }, + { + "id" : "minecraft:redstone_lamp", + "blockRuntimeId" : 251 + }, + { + "id" : "minecraft:sea_lantern", + "blockRuntimeId" : 11724 + }, + { + "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: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:crimson_hanging_sign" + }, + { + "id" : "minecraft:warped_hanging_sign" + }, + { + "id" : "minecraft:mangrove_hanging_sign" + }, + { + "id" : "minecraft:bamboo_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:beacon", + "blockRuntimeId" : 145 + }, + { + "id" : "minecraft:bell", + "blockRuntimeId" : 10686 + }, + { + "id" : "minecraft:conduit", + "blockRuntimeId" : 6011 + }, + { + "id" : "minecraft:stonecutter_block", + "blockRuntimeId" : 11752 + }, + { + "id" : "minecraft:end_portal_frame", + "blockRuntimeId" : 9811 + }, + { + "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: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: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", + "blockRuntimeId" : 9241 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 1799 + }, + { + "id" : "minecraft:end_crystal" + }, + { + "id" : "minecraft:paper" + }, + { + "id" : "minecraft:book" + }, + { + "id" : "minecraft:writable_book" + }, + { + "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: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: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:bamboo_chest_raft" + }, + { + "id" : "minecraft:rail", + "blockRuntimeId" : 5697 + }, + { + "id" : "minecraft:golden_rail", + "blockRuntimeId" : 7913 + }, + { + "id" : "minecraft:detector_rail", + "blockRuntimeId" : 5909 + }, + { + "id" : "minecraft:activator_rail", + "blockRuntimeId" : 537 + }, + { + "id" : "minecraft:minecart" + }, + { + "id" : "minecraft:chest_minecart" + }, + { + "id" : "minecraft:hopper_minecart" + }, + { + "id" : "minecraft:tnt_minecart" + }, + { + "id" : "minecraft:redstone" + }, + { + "id" : "minecraft:redstone_block", + "blockRuntimeId" : 5169 + }, + { + "id" : "minecraft:redstone_torch", + "blockRuntimeId" : 4534 + }, + { + "id" : "minecraft:lever", + "blockRuntimeId" : 10284 + }, + { + "id" : "minecraft:wooden_button", + "blockRuntimeId" : 10149 + }, + { + "id" : "minecraft:spruce_button", + "blockRuntimeId" : 6102 + }, + { + "id" : "minecraft:birch_button", + "blockRuntimeId" : 11944 + }, + { + "id" : "minecraft:jungle_button", + "blockRuntimeId" : 116 + }, + { + "id" : "minecraft:acacia_button", + "blockRuntimeId" : 11409 + }, + { + "id" : "minecraft:dark_oak_button", + "blockRuntimeId" : 93 + }, + { + "id" : "minecraft:mangrove_button", + "blockRuntimeId" : 10840 + }, + { + "id" : "minecraft:bamboo_button", + "blockRuntimeId" : 10238 + }, + { + "id" : "minecraft:stone_button", + "blockRuntimeId" : 826 + }, + { + "id" : "minecraft:crimson_button", + "blockRuntimeId" : 6213 + }, + { + "id" : "minecraft:warped_button", + "blockRuntimeId" : 11428 + }, + { + "id" : "minecraft:polished_blackstone_button", + "blockRuntimeId" : 11968 + }, + { + "id" : "minecraft:tripwire_hook", + "blockRuntimeId" : 9264 + }, + { + "id" : "minecraft:wooden_pressure_plate", + "blockRuntimeId" : 12241 + }, + { + "id" : "minecraft:spruce_pressure_plate", + "blockRuntimeId" : 5152 + }, + { + "id" : "minecraft:birch_pressure_plate", + "blockRuntimeId" : 4948 + }, + { + "id" : "minecraft:jungle_pressure_plate", + "blockRuntimeId" : 5028 + }, + { + "id" : "minecraft:acacia_pressure_plate", + "blockRuntimeId" : 7812 + }, + { + "id" : "minecraft:dark_oak_pressure_plate", + "blockRuntimeId" : 9306 + }, + { + "id" : "minecraft:mangrove_pressure_plate", + "blockRuntimeId" : 5646 + }, + { + "id" : "minecraft:bamboo_pressure_plate", + "blockRuntimeId" : 9819 + }, + { + "id" : "minecraft:crimson_pressure_plate", + "blockRuntimeId" : 12447 + }, + { + "id" : "minecraft:warped_pressure_plate", + "blockRuntimeId" : 256 + }, + { + "id" : "minecraft:stone_pressure_plate", + "blockRuntimeId" : 5663 + }, + { + "id" : "minecraft:light_weighted_pressure_plate", + "blockRuntimeId" : 5058 + }, + { + "id" : "minecraft:heavy_weighted_pressure_plate", + "blockRuntimeId" : 1783 + }, + { + "id" : "minecraft:polished_blackstone_pressure_plate", + "blockRuntimeId" : 9990 + }, + { + "id" : "minecraft:observer", + "blockRuntimeId" : 4522 + }, + { + "id" : "minecraft:daylight_detector", + "blockRuntimeId" : 5976 + }, + { + "id" : "minecraft:repeater" + }, + { + "id" : "minecraft:comparator" + }, + { + "id" : "minecraft:hopper" + }, + { + "id" : "minecraft:dropper", + "blockRuntimeId" : 11563 + }, + { + "id" : "minecraft:dispenser", + "blockRuntimeId" : 12191 + }, + { + "id" : "minecraft:piston", + "blockRuntimeId" : 1545 + }, + { + "id" : "minecraft:sticky_piston", + "blockRuntimeId" : 6145 + }, + { + "id" : "minecraft:tnt", + "blockRuntimeId" : 10479 + }, + { + "id" : "minecraft:name_tag" + }, + { + "id" : "minecraft:loom", + "blockRuntimeId" : 5603 + }, + { + "id" : "minecraft:banner" + }, + { + "id" : "minecraft:banner", + "damage" : 8 + }, + { + "id" : "minecraft:banner", + "damage" : 7 + }, + { + "id" : "minecraft:banner", + "damage" : 15 + }, + { + "id" : "minecraft:banner", + "damage" : 12 + }, + { + "id" : "minecraft:banner", + "damage" : 14 + }, + { + "id" : "minecraft:banner", + "damage" : 1 + }, + { + "id" : "minecraft:banner", + "damage" : 4 + }, + { + "id" : "minecraft:banner", + "damage" : 5 + }, + { + "id" : "minecraft:banner", + "damage" : 13 + }, + { + "id" : "minecraft:banner", + "damage" : 9 + }, + { + "id" : "minecraft:banner", + "damage" : 3 + }, + { + "id" : "minecraft:banner", + "damage" : 11 + }, + { + "id" : "minecraft:banner", + "damage" : 10 + }, + { + "id" : "minecraft:banner", + "damage" : 2 + }, + { + "id" : "minecraft:banner", + "damage" : 6 + }, + { + "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: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", + "blockRuntimeId" : 10148 + }, + { + "id" : "minecraft:lodestone_compass" + } + ] +} \ 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 2e3733aa6..8f0c9ce8e 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_19_0.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json similarity index 99% rename from core/src/main/resources/bedrock/runtime_item_states.1_19_0.json rename to core/src/main/resources/bedrock/runtime_item_states.1_19_20.json index b1ffe6353..00be1af06 100644 --- a/core/src/main/resources/bedrock/runtime_item_states.1_19_0.json +++ b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json @@ -9,7 +9,7 @@ }, { "name" : "minecraft:acacia_chest_boat", - "id" : 643 + "id" : 642 }, { "name" : "minecraft:acacia_door", @@ -217,7 +217,7 @@ }, { "name" : "minecraft:birch_chest_boat", - "id" : 640 + "id" : 639 }, { "name" : "minecraft:birch_door", @@ -541,7 +541,7 @@ }, { "name" : "minecraft:chest_boat", - "id" : 646 + "id" : 645 }, { "name" : "minecraft:chest_minecart", @@ -921,7 +921,7 @@ }, { "name" : "minecraft:dark_oak_chest_boat", - "id" : 644 + "id" : 643 }, { "name" : "minecraft:dark_oak_door", @@ -1121,7 +1121,7 @@ }, { "name" : "minecraft:disc_fragment_5", - "id" : 638 + "id" : 637 }, { "name" : "minecraft:dispenser", @@ -1197,7 +1197,7 @@ }, { "name" : "minecraft:echo_shard", - "id" : 648 + "id" : 647 }, { "name" : "minecraft:egg", @@ -1831,10 +1831,6 @@ "name" : "minecraft:fire_charge", "id" : 509 }, - { - "name" : "minecraft:firefly_spawn_egg", - "id" : 633 - }, { "name" : "minecraft:firework_rocket", "id" : 519 @@ -2409,7 +2405,7 @@ }, { "name" : "minecraft:jungle_chest_boat", - "id" : 641 + "id" : 640 }, { "name" : "minecraft:jungle_door", @@ -2669,7 +2665,7 @@ }, { "name" : "minecraft:mangrove_boat", - "id" : 636 + "id" : 635 }, { "name" : "minecraft:mangrove_button", @@ -2677,11 +2673,11 @@ }, { "name" : "minecraft:mangrove_chest_boat", - "id" : 645 + "id" : 644 }, { "name" : "minecraft:mangrove_door", - "id" : 634 + "id" : 633 }, { "name" : "minecraft:mangrove_double_slab", @@ -2721,7 +2717,7 @@ }, { "name" : "minecraft:mangrove_sign", - "id" : 635 + "id" : 634 }, { "name" : "minecraft:mangrove_slab", @@ -2865,7 +2861,7 @@ }, { "name" : "minecraft:music_disc_5", - "id" : 637 + "id" : 636 }, { "name" : "minecraft:music_disc_blocks", @@ -3041,7 +3037,7 @@ }, { "name" : "minecraft:oak_chest_boat", - "id" : 639 + "id" : 638 }, { "name" : "minecraft:oak_sign", @@ -3477,7 +3473,7 @@ }, { "name" : "minecraft:recovery_compass", - "id" : 647 + "id" : 646 }, { "name" : "minecraft:red_candle", @@ -3817,7 +3813,7 @@ }, { "name" : "minecraft:spruce_chest_boat", - "id" : 642 + "id" : 641 }, { "name" : "minecraft:spruce_door", @@ -4083,6 +4079,10 @@ "name" : "minecraft:totem_of_undying", "id" : 568 }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 648 + }, { "name" : "minecraft:trapdoor", "id" : 96 diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_19_50.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_50.json new file mode 100644 index 000000000..d8a12c794 --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_19_50.json @@ -0,0 +1,4654 @@ +[ + { + "name" : "minecraft:acacia_boat", + "id" : 379 + }, + { + "name" : "minecraft:acacia_button", + "id" : -140 + }, + { + "name" : "minecraft:acacia_chest_boat", + "id" : 645 + }, + { + "name" : "minecraft:acacia_door", + "id" : 556 + }, + { + "name" : "minecraft:acacia_fence_gate", + "id" : 187 + }, + { + "name" : "minecraft:acacia_hanging_sign", + "id" : -504 + }, + { + "name" : "minecraft:acacia_pressure_plate", + "id" : -150 + }, + { + "name" : "minecraft:acacia_sign", + "id" : 579 + }, + { + "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:activator_rail", + "id" : 126 + }, + { + "name" : "minecraft:agent_spawn_egg", + "id" : 487 + }, + { + "name" : "minecraft:air", + "id" : -158 + }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 631 + }, + { + "name" : "minecraft:allow", + "id" : 210 + }, + { + "name" : "minecraft:amethyst_block", + "id" : -327 + }, + { + "name" : "minecraft:amethyst_cluster", + "id" : -329 + }, + { + "name" : "minecraft:amethyst_shard", + "id" : 624 + }, + { + "name" : "minecraft:ancient_debris", + "id" : -271 + }, + { + "name" : "minecraft:andesite_stairs", + "id" : -171 + }, + { + "name" : "minecraft:anvil", + "id" : 145 + }, + { + "name" : "minecraft:apple", + "id" : 257 + }, + { + "name" : "minecraft:armor_stand", + "id" : 552 + }, + { + "name" : "minecraft:arrow", + "id" : 301 + }, + { + "name" : "minecraft:axolotl_bucket", + "id" : 369 + }, + { + "name" : "minecraft:axolotl_spawn_egg", + "id" : 500 + }, + { + "name" : "minecraft:azalea", + "id" : -337 + }, + { + "name" : "minecraft:azalea_leaves", + "id" : -324 + }, + { + "name" : "minecraft:azalea_leaves_flowered", + "id" : -325 + }, + { + "name" : "minecraft:baked_potato", + "id" : 281 + }, + { + "name" : "minecraft:balloon", + "id" : 598 + }, + { + "name" : "minecraft:bamboo", + "id" : -163 + }, + { + "name" : "minecraft:bamboo_button", + "id" : -511 + }, + { + "name" : "minecraft:bamboo_chest_raft", + "id" : 648 + }, + { + "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" : 638 + }, + { + "name" : "minecraft:bamboo_sapling", + "id" : -164 + }, + { + "name" : "minecraft:bamboo_sign", + "id" : 637 + }, + { + "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" : 567 + }, + { + "name" : "minecraft:banner_pattern", + "id" : 655 + }, + { + "name" : "minecraft:barrel", + "id" : -203 + }, + { + "name" : "minecraft:barrier", + "id" : -161 + }, + { + "name" : "minecraft:basalt", + "id" : -234 + }, + { + "name" : "minecraft:bat_spawn_egg", + "id" : 453 + }, + { + "name" : "minecraft:beacon", + "id" : 138 + }, + { + "name" : "minecraft:bed", + "id" : 418 + }, + { + "name" : "minecraft:bedrock", + "id" : 7 + }, + { + "name" : "minecraft:bee_nest", + "id" : -218 + }, + { + "name" : "minecraft:bee_spawn_egg", + "id" : 494 + }, + { + "name" : "minecraft:beef", + "id" : 273 + }, + { + "name" : "minecraft:beehive", + "id" : -219 + }, + { + "name" : "minecraft:beetroot", + "id" : 285 + }, + { + "name" : "minecraft:beetroot_seeds", + "id" : 295 + }, + { + "name" : "minecraft:beetroot_soup", + "id" : 286 + }, + { + "name" : "minecraft:bell", + "id" : -206 + }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, + { + "name" : "minecraft:birch_boat", + "id" : 376 + }, + { + "name" : "minecraft:birch_button", + "id" : -141 + }, + { + "name" : "minecraft:birch_chest_boat", + "id" : 642 + }, + { + "name" : "minecraft:birch_door", + "id" : 554 + }, + { + "name" : "minecraft:birch_fence_gate", + "id" : 184 + }, + { + "name" : "minecraft:birch_hanging_sign", + "id" : -502 + }, + { + "name" : "minecraft:birch_pressure_plate", + "id" : -151 + }, + { + "name" : "minecraft:birch_sign", + "id" : 577 + }, + { + "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:black_candle", + "id" : -428 + }, + { + "name" : "minecraft:black_candle_cake", + "id" : -445 + }, + { + "name" : "minecraft:black_dye", + "id" : 395 + }, + { + "name" : "minecraft:black_glazed_terracotta", + "id" : 235 + }, + { + "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:blast_furnace", + "id" : -196 + }, + { + "name" : "minecraft:blaze_powder", + "id" : 429 + }, + { + "name" : "minecraft:blaze_rod", + "id" : 423 + }, + { + "name" : "minecraft:blaze_spawn_egg", + "id" : 456 + }, + { + "name" : "minecraft:bleach", + "id" : 596 + }, + { + "name" : "minecraft:blue_candle", + "id" : -424 + }, + { + "name" : "minecraft:blue_candle_cake", + "id" : -441 + }, + { + "name" : "minecraft:blue_dye", + "id" : 399 + }, + { + "name" : "minecraft:blue_glazed_terracotta", + "id" : 231 + }, + { + "name" : "minecraft:blue_ice", + "id" : -11 + }, + { + "name" : "minecraft:boat", + "id" : 653 + }, + { + "name" : "minecraft:bone", + "id" : 415 + }, + { + "name" : "minecraft:bone_block", + "id" : 216 + }, + { + "name" : "minecraft:bone_meal", + "id" : 411 + }, + { + "name" : "minecraft:book", + "id" : 387 + }, + { + "name" : "minecraft:bookshelf", + "id" : 47 + }, + { + "name" : "minecraft:border_block", + "id" : 212 + }, + { + "name" : "minecraft:bordure_indented_banner_pattern", + "id" : 586 + }, + { + "name" : "minecraft:bow", + "id" : 300 + }, + { + "name" : "minecraft:bowl", + "id" : 321 + }, + { + "name" : "minecraft:bread", + "id" : 261 + }, + { + "name" : "minecraft:brewing_stand", + "id" : 431 + }, + { + "name" : "minecraft:brick", + "id" : 383 + }, + { + "name" : "minecraft:brick_block", + "id" : 45 + }, + { + "name" : "minecraft:brick_stairs", + "id" : 108 + }, + { + "name" : "minecraft:brown_candle", + "id" : -425 + }, + { + "name" : "minecraft:brown_candle_cake", + "id" : -442 + }, + { + "name" : "minecraft:brown_dye", + "id" : 398 + }, + { + "name" : "minecraft:brown_glazed_terracotta", + "id" : 232 + }, + { + "name" : "minecraft:brown_mushroom", + "id" : 39 + }, + { + "name" : "minecraft:brown_mushroom_block", + "id" : 99 + }, + { + "name" : "minecraft:bubble_column", + "id" : -160 + }, + { + "name" : "minecraft:bucket", + "id" : 360 + }, + { + "name" : "minecraft:budding_amethyst", + "id" : -328 + }, + { + "name" : "minecraft:cactus", + "id" : 81 + }, + { + "name" : "minecraft:cake", + "id" : 417 + }, + { + "name" : "minecraft:calcite", + "id" : -326 + }, + { + "name" : "minecraft:camel_spawn_egg", + "id" : 633 + }, + { + "name" : "minecraft:camera", + "id" : 593 + }, + { + "name" : "minecraft:campfire", + "id" : 589 + }, + { + "name" : "minecraft:candle", + "id" : -412 + }, + { + "name" : "minecraft:candle_cake", + "id" : -429 + }, + { + "name" : "minecraft:carpet", + "id" : 171 + }, + { + "name" : "minecraft:carrot", + "id" : 279 + }, + { + "name" : "minecraft:carrot_on_a_stick", + "id" : 517 + }, + { + "name" : "minecraft:carrots", + "id" : 141 + }, + { + "name" : "minecraft:cartography_table", + "id" : -200 + }, + { + "name" : "minecraft:carved_pumpkin", + "id" : -155 + }, + { + "name" : "minecraft:cat_spawn_egg", + "id" : 488 + }, + { + "name" : "minecraft:cauldron", + "id" : 432 + }, + { + "name" : "minecraft:cave_spider_spawn_egg", + "id" : 457 + }, + { + "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" : 619 + }, + { + "name" : "minecraft:chain_command_block", + "id" : 189 + }, + { + "name" : "minecraft:chainmail_boots", + "id" : 342 + }, + { + "name" : "minecraft:chainmail_chestplate", + "id" : 340 + }, + { + "name" : "minecraft:chainmail_helmet", + "id" : 339 + }, + { + "name" : "minecraft:chainmail_leggings", + "id" : 341 + }, + { + "name" : "minecraft:charcoal", + "id" : 303 + }, + { + "name" : "minecraft:chemical_heat", + "id" : 192 + }, + { + "name" : "minecraft:chemistry_table", + "id" : 238 + }, + { + "name" : "minecraft:chest", + "id" : 54 + }, + { + "name" : "minecraft:chest_boat", + "id" : 649 + }, + { + "name" : "minecraft:chest_minecart", + "id" : 389 + }, + { + "name" : "minecraft:chicken", + "id" : 275 + }, + { + "name" : "minecraft:chicken_spawn_egg", + "id" : 435 + }, + { + "name" : "minecraft:chiseled_bookshelf", + "id" : -526 + }, + { + "name" : "minecraft:chiseled_deepslate", + "id" : -395 + }, + { + "name" : "minecraft:chiseled_nether_bricks", + "id" : -302 + }, + { + "name" : "minecraft:chiseled_polished_blackstone", + "id" : -279 + }, + { + "name" : "minecraft:chorus_flower", + "id" : 200 + }, + { + "name" : "minecraft:chorus_fruit", + "id" : 558 + }, + { + "name" : "minecraft:chorus_plant", + "id" : 240 + }, + { + "name" : "minecraft:clay", + "id" : 82 + }, + { + "name" : "minecraft:clay_ball", + "id" : 384 + }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, + { + "name" : "minecraft:clock", + "id" : 393 + }, + { + "name" : "minecraft:coal", + "id" : 302 + }, + { + "name" : "minecraft:coal_block", + "id" : 173 + }, + { + "name" : "minecraft:coal_ore", + "id" : 16 + }, + { + "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_wall", + "id" : 139 + }, + { + "name" : "minecraft:cocoa", + "id" : 127 + }, + { + "name" : "minecraft:cocoa_beans", + "id" : 412 + }, + { + "name" : "minecraft:cod", + "id" : 264 + }, + { + "name" : "minecraft:cod_bucket", + "id" : 364 + }, + { + "name" : "minecraft:cod_spawn_egg", + "id" : 480 + }, + { + "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" : 563 + }, + { + "name" : "minecraft:comparator", + "id" : 522 + }, + { + "name" : "minecraft:compass", + "id" : 391 + }, + { + "name" : "minecraft:composter", + "id" : -213 + }, + { + "name" : "minecraft:compound", + "id" : 594 + }, + { + "name" : "minecraft:concrete", + "id" : 236 + }, + { + "name" : "minecraft:concrete_powder", + "id" : 237 + }, + { + "name" : "minecraft:conduit", + "id" : -157 + }, + { + "name" : "minecraft:cooked_beef", + "id" : 274 + }, + { + "name" : "minecraft:cooked_chicken", + "id" : 276 + }, + { + "name" : "minecraft:cooked_cod", + "id" : 268 + }, + { + "name" : "minecraft:cooked_mutton", + "id" : 551 + }, + { + "name" : "minecraft:cooked_porkchop", + "id" : 263 + }, + { + "name" : "minecraft:cooked_rabbit", + "id" : 289 + }, + { + "name" : "minecraft:cooked_salmon", + "id" : 269 + }, + { + "name" : "minecraft:cookie", + "id" : 271 + }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 504 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, + { + "name" : "minecraft:coral", + "id" : -131 + }, + { + "name" : "minecraft:coral_block", + "id" : -132 + }, + { + "name" : "minecraft:coral_fan", + "id" : -133 + }, + { + "name" : "minecraft:coral_fan_dead", + "id" : -134 + }, + { + "name" : "minecraft:coral_fan_hang", + "id" : -135 + }, + { + "name" : "minecraft:coral_fan_hang2", + "id" : -136 + }, + { + "name" : "minecraft:coral_fan_hang3", + "id" : -137 + }, + { + "name" : "minecraft:cow_spawn_egg", + "id" : 436 + }, + { + "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:crafting_table", + "id" : 58 + }, + { + "name" : "minecraft:creeper_banner_pattern", + "id" : 582 + }, + { + "name" : "minecraft:creeper_spawn_egg", + "id" : 441 + }, + { + "name" : "minecraft:crimson_button", + "id" : -260 + }, + { + "name" : "minecraft:crimson_door", + "id" : 616 + }, + { + "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" : 614 + }, + { + "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" : 575 + }, + { + "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:cyan_candle", + "id" : -422 + }, + { + "name" : "minecraft:cyan_candle_cake", + "id" : -439 + }, + { + "name" : "minecraft:cyan_dye", + "id" : 401 + }, + { + "name" : "minecraft:cyan_glazed_terracotta", + "id" : 229 + }, + { + "name" : "minecraft:dark_oak_boat", + "id" : 380 + }, + { + "name" : "minecraft:dark_oak_button", + "id" : -142 + }, + { + "name" : "minecraft:dark_oak_chest_boat", + "id" : 646 + }, + { + "name" : "minecraft:dark_oak_door", + "id" : 557 + }, + { + "name" : "minecraft:dark_oak_fence_gate", + "id" : 186 + }, + { + "name" : "minecraft:dark_oak_hanging_sign", + "id" : -505 + }, + { + "name" : "minecraft:dark_oak_pressure_plate", + "id" : -152 + }, + { + "name" : "minecraft:dark_oak_sign", + "id" : 580 + }, + { + "name" : "minecraft:dark_oak_stairs", + "id" : 164 + }, + { + "name" : "minecraft:dark_oak_trapdoor", + "id" : -147 + }, + { + "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:deadbush", + "id" : 32 + }, + { + "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:detector_rail", + "id" : 28 + }, + { + "name" : "minecraft:diamond", + "id" : 304 + }, + { + "name" : "minecraft:diamond_axe", + "id" : 319 + }, + { + "name" : "minecraft:diamond_block", + "id" : 57 + }, + { + "name" : "minecraft:diamond_boots", + "id" : 350 + }, + { + "name" : "minecraft:diamond_chestplate", + "id" : 348 + }, + { + "name" : "minecraft:diamond_helmet", + "id" : 347 + }, + { + "name" : "minecraft:diamond_hoe", + "id" : 332 + }, + { + "name" : "minecraft:diamond_horse_armor", + "id" : 533 + }, + { + "name" : "minecraft:diamond_leggings", + "id" : 349 + }, + { + "name" : "minecraft:diamond_ore", + "id" : 56 + }, + { + "name" : "minecraft:diamond_pickaxe", + "id" : 318 + }, + { + "name" : "minecraft:diamond_shovel", + "id" : 317 + }, + { + "name" : "minecraft:diamond_sword", + "id" : 316 + }, + { + "name" : "minecraft:diorite_stairs", + "id" : -170 + }, + { + "name" : "minecraft:dirt", + "id" : 3 + }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, + { + "name" : "minecraft:disc_fragment_5", + "id" : 640 + }, + { + "name" : "minecraft:dispenser", + "id" : 23 + }, + { + "name" : "minecraft:dolphin_spawn_egg", + "id" : 484 + }, + { + "name" : "minecraft:donkey_spawn_egg", + "id" : 465 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 + }, + { + "name" : "minecraft:double_plant", + "id" : 175 + }, + { + "name" : "minecraft:double_stone_block_slab", + "id" : 43 + }, + { + "name" : "minecraft:double_stone_block_slab2", + "id" : 181 + }, + { + "name" : "minecraft:double_stone_block_slab3", + "id" : -167 + }, + { + "name" : "minecraft:double_stone_block_slab4", + "id" : -168 + }, + { + "name" : "minecraft:double_wooden_slab", + "id" : 157 + }, + { + "name" : "minecraft:dragon_breath", + "id" : 560 + }, + { + "name" : "minecraft:dragon_egg", + "id" : 122 + }, + { + "name" : "minecraft:dried_kelp", + "id" : 270 + }, + { + "name" : "minecraft:dried_kelp_block", + "id" : -139 + }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, + { + "name" : "minecraft:dropper", + "id" : 125 + }, + { + "name" : "minecraft:drowned_spawn_egg", + "id" : 483 + }, + { + "name" : "minecraft:dye", + "id" : 654 + }, + { + "name" : "minecraft:echo_shard", + "id" : 651 + }, + { + "name" : "minecraft:egg", + "id" : 390 + }, + { + "name" : "minecraft:elder_guardian_spawn_egg", + "id" : 471 + }, + { + "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" : 564 + }, + { + "name" : "minecraft:emerald", + "id" : 512 + }, + { + "name" : "minecraft:emerald_block", + "id" : 133 + }, + { + "name" : "minecraft:emerald_ore", + "id" : 129 + }, + { + "name" : "minecraft:empty_map", + "id" : 515 + }, + { + "name" : "minecraft:enchanted_book", + "id" : 521 + }, + { + "name" : "minecraft:enchanted_golden_apple", + "id" : 259 + }, + { + "name" : "minecraft:enchanting_table", + "id" : 116 + }, + { + "name" : "minecraft:end_brick_stairs", + "id" : -178 + }, + { + "name" : "minecraft:end_bricks", + "id" : 206 + }, + { + "name" : "minecraft:end_crystal", + "id" : 657 + }, + { + "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:ender_chest", + "id" : 130 + }, + { + "name" : "minecraft:ender_eye", + "id" : 433 + }, + { + "name" : "minecraft:ender_pearl", + "id" : 422 + }, + { + "name" : "minecraft:enderman_spawn_egg", + "id" : 442 + }, + { + "name" : "minecraft:endermite_spawn_egg", + "id" : 460 + }, + { + "name" : "minecraft:evoker_spawn_egg", + "id" : 475 + }, + { + "name" : "minecraft:experience_bottle", + "id" : 508 + }, + { + "name" : "minecraft:exposed_copper", + "id" : -341 + }, + { + "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:farmland", + "id" : 60 + }, + { + "name" : "minecraft:feather", + "id" : 327 + }, + { + "name" : "minecraft:fence", + "id" : 85 + }, + { + "name" : "minecraft:fence_gate", + "id" : 107 + }, + { + "name" : "minecraft:fermented_spider_eye", + "id" : 428 + }, + { + "name" : "minecraft:field_masoned_banner_pattern", + "id" : 585 + }, + { + "name" : "minecraft:filled_map", + "id" : 420 + }, + { + "name" : "minecraft:fire", + "id" : 51 + }, + { + "name" : "minecraft:fire_charge", + "id" : 509 + }, + { + "name" : "minecraft:firework_rocket", + "id" : 519 + }, + { + "name" : "minecraft:firework_star", + "id" : 520 + }, + { + "name" : "minecraft:fishing_rod", + "id" : 392 + }, + { + "name" : "minecraft:fletching_table", + "id" : -201 + }, + { + "name" : "minecraft:flint", + "id" : 356 + }, + { + "name" : "minecraft:flint_and_steel", + "id" : 299 + }, + { + "name" : "minecraft:flower_banner_pattern", + "id" : 581 + }, + { + "name" : "minecraft:flower_pot", + "id" : 514 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 + }, + { + "name" : "minecraft:flowing_lava", + "id" : 10 + }, + { + "name" : "minecraft:flowing_water", + "id" : 8 + }, + { + "name" : "minecraft:fox_spawn_egg", + "id" : 490 + }, + { + "name" : "minecraft:frame", + "id" : 513 + }, + { + "name" : "minecraft:frog_spawn", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 628 + }, + { + "name" : "minecraft:frosted_ice", + "id" : 207 + }, + { + "name" : "minecraft:furnace", + "id" : 61 + }, + { + "name" : "minecraft:ghast_spawn_egg", + "id" : 454 + }, + { + "name" : "minecraft:ghast_tear", + "id" : 424 + }, + { + "name" : "minecraft:gilded_blackstone", + "id" : -281 + }, + { + "name" : "minecraft:glass", + "id" : 20 + }, + { + "name" : "minecraft:glass_bottle", + "id" : 427 + }, + { + "name" : "minecraft:glass_pane", + "id" : 102 + }, + { + "name" : "minecraft:glistering_melon_slice", + "id" : 434 + }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, + { + "name" : "minecraft:glow_berries", + "id" : 658 + }, + { + "name" : "minecraft:glow_frame", + "id" : 623 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 503 + }, + { + "name" : "minecraft:glow_lichen", + "id" : -411 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 502 + }, + { + "name" : "minecraft:glow_stick", + "id" : 601 + }, + { + "name" : "minecraft:glowingobsidian", + "id" : 246 + }, + { + "name" : "minecraft:glowstone", + "id" : 89 + }, + { + "name" : "minecraft:glowstone_dust", + "id" : 394 + }, + { + "name" : "minecraft:goat_horn", + "id" : 627 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 501 + }, + { + "name" : "minecraft:gold_block", + "id" : 41 + }, + { + "name" : "minecraft:gold_ingot", + "id" : 306 + }, + { + "name" : "minecraft:gold_nugget", + "id" : 425 + }, + { + "name" : "minecraft:gold_ore", + "id" : 14 + }, + { + "name" : "minecraft:golden_apple", + "id" : 258 + }, + { + "name" : "minecraft:golden_axe", + "id" : 325 + }, + { + "name" : "minecraft:golden_boots", + "id" : 354 + }, + { + "name" : "minecraft:golden_carrot", + "id" : 283 + }, + { + "name" : "minecraft:golden_chestplate", + "id" : 352 + }, + { + "name" : "minecraft:golden_helmet", + "id" : 351 + }, + { + "name" : "minecraft:golden_hoe", + "id" : 333 + }, + { + "name" : "minecraft:golden_horse_armor", + "id" : 532 + }, + { + "name" : "minecraft:golden_leggings", + "id" : 353 + }, + { + "name" : "minecraft:golden_pickaxe", + "id" : 324 + }, + { + "name" : "minecraft:golden_rail", + "id" : 27 + }, + { + "name" : "minecraft:golden_shovel", + "id" : 323 + }, + { + "name" : "minecraft:golden_sword", + "id" : 322 + }, + { + "name" : "minecraft:granite_stairs", + "id" : -169 + }, + { + "name" : "minecraft:grass", + "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_dye", + "id" : 403 + }, + { + "name" : "minecraft:gray_glazed_terracotta", + "id" : 227 + }, + { + "name" : "minecraft:green_candle", + "id" : -426 + }, + { + "name" : "minecraft:green_candle_cake", + "id" : -443 + }, + { + "name" : "minecraft:green_dye", + "id" : 397 + }, + { + "name" : "minecraft:green_glazed_terracotta", + "id" : 233 + }, + { + "name" : "minecraft:grindstone", + "id" : -195 + }, + { + "name" : "minecraft:guardian_spawn_egg", + "id" : 461 + }, + { + "name" : "minecraft:gunpowder", + "id" : 328 + }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, + { + "name" : "minecraft:hard_glass", + "id" : 253 + }, + { + "name" : "minecraft:hard_glass_pane", + "id" : 190 + }, + { + "name" : "minecraft:hard_stained_glass", + "id" : 254 + }, + { + "name" : "minecraft:hard_stained_glass_pane", + "id" : 191 + }, + { + "name" : "minecraft:hardened_clay", + "id" : 172 + }, + { + "name" : "minecraft:hay_block", + "id" : 170 + }, + { + "name" : "minecraft:heart_of_the_sea", + "id" : 571 + }, + { + "name" : "minecraft:heavy_weighted_pressure_plate", + "id" : 148 + }, + { + "name" : "minecraft:hoglin_spawn_egg", + "id" : 496 + }, + { + "name" : "minecraft:honey_block", + "id" : -220 + }, + { + "name" : "minecraft:honey_bottle", + "id" : 592 + }, + { + "name" : "minecraft:honeycomb", + "id" : 591 + }, + { + "name" : "minecraft:honeycomb_block", + "id" : -221 + }, + { + "name" : "minecraft:hopper", + "id" : 527 + }, + { + "name" : "minecraft:hopper_minecart", + "id" : 526 + }, + { + "name" : "minecraft:horse_spawn_egg", + "id" : 458 + }, + { + "name" : "minecraft:husk_spawn_egg", + "id" : 463 + }, + { + "name" : "minecraft:ice", + "id" : 79 + }, + { + "name" : "minecraft:ice_bomb", + "id" : 595 + }, + { + "name" : "minecraft:infested_deepslate", + "id" : -454 + }, + { + "name" : "minecraft:info_update", + "id" : 248 + }, + { + "name" : "minecraft:info_update2", + "id" : 249 + }, + { + "name" : "minecraft:ink_sac", + "id" : 413 + }, + { + "name" : "minecraft:invisible_bedrock", + "id" : 95 + }, + { + "name" : "minecraft:iron_axe", + "id" : 298 + }, + { + "name" : "minecraft:iron_bars", + "id" : 101 + }, + { + "name" : "minecraft:iron_block", + "id" : 42 + }, + { + "name" : "minecraft:iron_boots", + "id" : 346 + }, + { + "name" : "minecraft:iron_chestplate", + "id" : 344 + }, + { + "name" : "minecraft:iron_door", + "id" : 372 + }, + { + "name" : "minecraft:iron_helmet", + "id" : 343 + }, + { + "name" : "minecraft:iron_hoe", + "id" : 331 + }, + { + "name" : "minecraft:iron_horse_armor", + "id" : 531 + }, + { + "name" : "minecraft:iron_ingot", + "id" : 305 + }, + { + "name" : "minecraft:iron_leggings", + "id" : 345 + }, + { + "name" : "minecraft:iron_nugget", + "id" : 569 + }, + { + "name" : "minecraft:iron_ore", + "id" : 15 + }, + { + "name" : "minecraft:iron_pickaxe", + "id" : 297 + }, + { + "name" : "minecraft:iron_shovel", + "id" : 296 + }, + { + "name" : "minecraft:iron_sword", + "id" : 307 + }, + { + "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" : 377 + }, + { + "name" : "minecraft:jungle_button", + "id" : -143 + }, + { + "name" : "minecraft:jungle_chest_boat", + "id" : 643 + }, + { + "name" : "minecraft:jungle_door", + "id" : 555 + }, + { + "name" : "minecraft:jungle_fence_gate", + "id" : 185 + }, + { + "name" : "minecraft:jungle_hanging_sign", + "id" : -503 + }, + { + "name" : "minecraft:jungle_pressure_plate", + "id" : -153 + }, + { + "name" : "minecraft:jungle_sign", + "id" : 578 + }, + { + "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:kelp", + "id" : 382 + }, + { + "name" : "minecraft:ladder", + "id" : 65 + }, + { + "name" : "minecraft:lantern", + "id" : -208 + }, + { + "name" : "minecraft:lapis_block", + "id" : 22 + }, + { + "name" : "minecraft:lapis_lazuli", + "id" : 414 + }, + { + "name" : "minecraft:lapis_ore", + "id" : 21 + }, + { + "name" : "minecraft:large_amethyst_bud", + "id" : -330 + }, + { + "name" : "minecraft:lava", + "id" : 11 + }, + { + "name" : "minecraft:lava_bucket", + "id" : 363 + }, + { + "name" : "minecraft:lava_cauldron", + "id" : -210 + }, + { + "name" : "minecraft:lead", + "id" : 547 + }, + { + "name" : "minecraft:leather", + "id" : 381 + }, + { + "name" : "minecraft:leather_boots", + "id" : 338 + }, + { + "name" : "minecraft:leather_chestplate", + "id" : 336 + }, + { + "name" : "minecraft:leather_helmet", + "id" : 335 + }, + { + "name" : "minecraft:leather_horse_armor", + "id" : 530 + }, + { + "name" : "minecraft:leather_leggings", + "id" : 337 + }, + { + "name" : "minecraft:leaves", + "id" : 18 + }, + { + "name" : "minecraft:leaves2", + "id" : 161 + }, + { + "name" : "minecraft:lectern", + "id" : -194 + }, + { + "name" : "minecraft:lever", + "id" : 69 + }, + { + "name" : "minecraft:light_block", + "id" : -215 + }, + { + "name" : "minecraft:light_blue_candle", + "id" : -416 + }, + { + "name" : "minecraft:light_blue_candle_cake", + "id" : -433 + }, + { + "name" : "minecraft:light_blue_dye", + "id" : 407 + }, + { + "name" : "minecraft:light_blue_glazed_terracotta", + "id" : 223 + }, + { + "name" : "minecraft:light_gray_candle", + "id" : -421 + }, + { + "name" : "minecraft:light_gray_candle_cake", + "id" : -438 + }, + { + "name" : "minecraft:light_gray_dye", + "id" : 402 + }, + { + "name" : "minecraft:light_weighted_pressure_plate", + "id" : 147 + }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, + { + "name" : "minecraft:lime_candle", + "id" : -418 + }, + { + "name" : "minecraft:lime_candle_cake", + "id" : -435 + }, + { + "name" : "minecraft:lime_dye", + "id" : 405 + }, + { + "name" : "minecraft:lime_glazed_terracotta", + "id" : 225 + }, + { + "name" : "minecraft:lingering_potion", + "id" : 562 + }, + { + "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" : 473 + }, + { + "name" : "minecraft:lodestone", + "id" : -222 + }, + { + "name" : "minecraft:lodestone_compass", + "id" : 602 + }, + { + "name" : "minecraft:log", + "id" : 17 + }, + { + "name" : "minecraft:log2", + "id" : 162 + }, + { + "name" : "minecraft:loom", + "id" : -204 + }, + { + "name" : "minecraft:magenta_candle", + "id" : -415 + }, + { + "name" : "minecraft:magenta_candle_cake", + "id" : -432 + }, + { + "name" : "minecraft:magenta_dye", + "id" : 408 + }, + { + "name" : "minecraft:magenta_glazed_terracotta", + "id" : 222 + }, + { + "name" : "minecraft:magma", + "id" : 213 + }, + { + "name" : "minecraft:magma_cream", + "id" : 430 + }, + { + "name" : "minecraft:magma_cube_spawn_egg", + "id" : 455 + }, + { + "name" : "minecraft:mangrove_boat", + "id" : 636 + }, + { + "name" : "minecraft:mangrove_button", + "id" : -487 + }, + { + "name" : "minecraft:mangrove_chest_boat", + "id" : 647 + }, + { + "name" : "minecraft:mangrove_door", + "id" : 634 + }, + { + "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" : 635 + }, + { + "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" : 599 + }, + { + "name" : "minecraft:medium_amethyst_bud", + "id" : -331 + }, + { + "name" : "minecraft:melon_block", + "id" : 103 + }, + { + "name" : "minecraft:melon_seeds", + "id" : 293 + }, + { + "name" : "minecraft:melon_slice", + "id" : 272 + }, + { + "name" : "minecraft:melon_stem", + "id" : 105 + }, + { + "name" : "minecraft:milk_bucket", + "id" : 361 + }, + { + "name" : "minecraft:minecart", + "id" : 370 + }, + { + "name" : "minecraft:mob_spawner", + "id" : 52 + }, + { + "name" : "minecraft:mojang_banner_pattern", + "id" : 584 + }, + { + "name" : "minecraft:monster_egg", + "id" : 97 + }, + { + "name" : "minecraft:mooshroom_spawn_egg", + "id" : 440 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 + }, + { + "name" : "minecraft:mossy_cobblestone", + "id" : 48 + }, + { + "name" : "minecraft:mossy_cobblestone_stairs", + "id" : -179 + }, + { + "name" : "minecraft:mossy_stone_brick_stairs", + "id" : -175 + }, + { + "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" : 466 + }, + { + "name" : "minecraft:mushroom_stew", + "id" : 260 + }, + { + "name" : "minecraft:music_disc_11", + "id" : 544 + }, + { + "name" : "minecraft:music_disc_13", + "id" : 534 + }, + { + "name" : "minecraft:music_disc_5", + "id" : 639 + }, + { + "name" : "minecraft:music_disc_blocks", + "id" : 536 + }, + { + "name" : "minecraft:music_disc_cat", + "id" : 535 + }, + { + "name" : "minecraft:music_disc_chirp", + "id" : 537 + }, + { + "name" : "minecraft:music_disc_far", + "id" : 538 + }, + { + "name" : "minecraft:music_disc_mall", + "id" : 539 + }, + { + "name" : "minecraft:music_disc_mellohi", + "id" : 540 + }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 626 + }, + { + "name" : "minecraft:music_disc_pigstep", + "id" : 620 + }, + { + "name" : "minecraft:music_disc_stal", + "id" : 541 + }, + { + "name" : "minecraft:music_disc_strad", + "id" : 542 + }, + { + "name" : "minecraft:music_disc_wait", + "id" : 545 + }, + { + "name" : "minecraft:music_disc_ward", + "id" : 543 + }, + { + "name" : "minecraft:mutton", + "id" : 550 + }, + { + "name" : "minecraft:mycelium", + "id" : 110 + }, + { + "name" : "minecraft:name_tag", + "id" : 548 + }, + { + "name" : "minecraft:nautilus_shell", + "id" : 570 + }, + { + "name" : "minecraft:nether_brick", + "id" : 112 + }, + { + "name" : "minecraft:nether_brick_fence", + "id" : 113 + }, + { + "name" : "minecraft:nether_brick_stairs", + "id" : 114 + }, + { + "name" : "minecraft:nether_gold_ore", + "id" : -288 + }, + { + "name" : "minecraft:nether_sprouts", + "id" : 621 + }, + { + "name" : "minecraft:nether_star", + "id" : 518 + }, + { + "name" : "minecraft:nether_wart", + "id" : 294 + }, + { + "name" : "minecraft:nether_wart_block", + "id" : 214 + }, + { + "name" : "minecraft:netherbrick", + "id" : 523 + }, + { + "name" : "minecraft:netherite_axe", + "id" : 607 + }, + { + "name" : "minecraft:netherite_block", + "id" : -270 + }, + { + "name" : "minecraft:netherite_boots", + "id" : 612 + }, + { + "name" : "minecraft:netherite_chestplate", + "id" : 610 + }, + { + "name" : "minecraft:netherite_helmet", + "id" : 609 + }, + { + "name" : "minecraft:netherite_hoe", + "id" : 608 + }, + { + "name" : "minecraft:netherite_ingot", + "id" : 603 + }, + { + "name" : "minecraft:netherite_leggings", + "id" : 611 + }, + { + "name" : "minecraft:netherite_pickaxe", + "id" : 606 + }, + { + "name" : "minecraft:netherite_scrap", + "id" : 613 + }, + { + "name" : "minecraft:netherite_shovel", + "id" : 605 + }, + { + "name" : "minecraft:netherite_sword", + "id" : 604 + }, + { + "name" : "minecraft:netherrack", + "id" : 87 + }, + { + "name" : "minecraft:netherreactor", + "id" : 247 + }, + { + "name" : "minecraft:normal_stone_stairs", + "id" : -180 + }, + { + "name" : "minecraft:noteblock", + "id" : 25 + }, + { + "name" : "minecraft:npc_spawn_egg", + "id" : 470 + }, + { + "name" : "minecraft:oak_boat", + "id" : 375 + }, + { + "name" : "minecraft:oak_chest_boat", + "id" : 641 + }, + { + "name" : "minecraft:oak_hanging_sign", + "id" : -500 + }, + { + "name" : "minecraft:oak_sign", + "id" : 358 + }, + { + "name" : "minecraft:oak_stairs", + "id" : 53 + }, + { + "name" : "minecraft:observer", + "id" : 251 + }, + { + "name" : "minecraft:obsidian", + "id" : 49 + }, + { + "name" : "minecraft:ocelot_spawn_egg", + "id" : 451 + }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, + { + "name" : "minecraft:orange_candle", + "id" : -414 + }, + { + "name" : "minecraft:orange_candle_cake", + "id" : -431 + }, + { + "name" : "minecraft:orange_dye", + "id" : 409 + }, + { + "name" : "minecraft:orange_glazed_terracotta", + "id" : 221 + }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "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" : 357 + }, + { + "name" : "minecraft:panda_spawn_egg", + "id" : 489 + }, + { + "name" : "minecraft:paper", + "id" : 386 + }, + { + "name" : "minecraft:parrot_spawn_egg", + "id" : 478 + }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, + { + "name" : "minecraft:phantom_membrane", + "id" : 574 + }, + { + "name" : "minecraft:phantom_spawn_egg", + "id" : 486 + }, + { + "name" : "minecraft:pig_spawn_egg", + "id" : 437 + }, + { + "name" : "minecraft:piglin_banner_pattern", + "id" : 587 + }, + { + "name" : "minecraft:piglin_brute_spawn_egg", + "id" : 499 + }, + { + "name" : "minecraft:piglin_spawn_egg", + "id" : 497 + }, + { + "name" : "minecraft:pillager_spawn_egg", + "id" : 491 + }, + { + "name" : "minecraft:pink_candle", + "id" : -419 + }, + { + "name" : "minecraft:pink_candle_cake", + "id" : -436 + }, + { + "name" : "minecraft:pink_dye", + "id" : 404 + }, + { + "name" : "minecraft:pink_glazed_terracotta", + "id" : 226 + }, + { + "name" : "minecraft:piston", + "id" : 33 + }, + { + "name" : "minecraft:piston_arm_collision", + "id" : 34 + }, + { + "name" : "minecraft:planks", + "id" : 5 + }, + { + "name" : "minecraft:podzol", + "id" : 243 + }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, + { + "name" : "minecraft:poisonous_potato", + "id" : 282 + }, + { + "name" : "minecraft:polar_bear_spawn_egg", + "id" : 472 + }, + { + "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_stairs", + "id" : -173 + }, + { + "name" : "minecraft:polished_granite_stairs", + "id" : -172 + }, + { + "name" : "minecraft:popped_chorus_fruit", + "id" : 559 + }, + { + "name" : "minecraft:porkchop", + "id" : 262 + }, + { + "name" : "minecraft:portal", + "id" : 90 + }, + { + "name" : "minecraft:potato", + "id" : 280 + }, + { + "name" : "minecraft:potatoes", + "id" : 142 + }, + { + "name" : "minecraft:potion", + "id" : 426 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 + }, + { + "name" : "minecraft:powered_comparator", + "id" : 150 + }, + { + "name" : "minecraft:powered_repeater", + "id" : 94 + }, + { + "name" : "minecraft:prismarine", + "id" : 168 + }, + { + "name" : "minecraft:prismarine_bricks_stairs", + "id" : -4 + }, + { + "name" : "minecraft:prismarine_crystals", + "id" : 549 + }, + { + "name" : "minecraft:prismarine_shard", + "id" : 565 + }, + { + "name" : "minecraft:prismarine_stairs", + "id" : -2 + }, + { + "name" : "minecraft:pufferfish", + "id" : 267 + }, + { + "name" : "minecraft:pufferfish_bucket", + "id" : 367 + }, + { + "name" : "minecraft:pufferfish_spawn_egg", + "id" : 481 + }, + { + "name" : "minecraft:pumpkin", + "id" : 86 + }, + { + "name" : "minecraft:pumpkin_pie", + "id" : 284 + }, + { + "name" : "minecraft:pumpkin_seeds", + "id" : 292 + }, + { + "name" : "minecraft:pumpkin_stem", + "id" : 104 + }, + { + "name" : "minecraft:purple_candle", + "id" : -423 + }, + { + "name" : "minecraft:purple_candle_cake", + "id" : -440 + }, + { + "name" : "minecraft:purple_dye", + "id" : 400 + }, + { + "name" : "minecraft:purple_glazed_terracotta", + "id" : 219 + }, + { + "name" : "minecraft:purpur_block", + "id" : 201 + }, + { + "name" : "minecraft:purpur_stairs", + "id" : 203 + }, + { + "name" : "minecraft:quartz", + "id" : 524 + }, + { + "name" : "minecraft:quartz_block", + "id" : 155 + }, + { + "name" : "minecraft:quartz_bricks", + "id" : -304 + }, + { + "name" : "minecraft:quartz_ore", + "id" : 153 + }, + { + "name" : "minecraft:quartz_stairs", + "id" : 156 + }, + { + "name" : "minecraft:rabbit", + "id" : 288 + }, + { + "name" : "minecraft:rabbit_foot", + "id" : 528 + }, + { + "name" : "minecraft:rabbit_hide", + "id" : 529 + }, + { + "name" : "minecraft:rabbit_spawn_egg", + "id" : 459 + }, + { + "name" : "minecraft:rabbit_stew", + "id" : 290 + }, + { + "name" : "minecraft:rail", + "id" : 66 + }, + { + "name" : "minecraft:rapid_fertilizer", + "id" : 597 + }, + { + "name" : "minecraft:ravager_spawn_egg", + "id" : 493 + }, + { + "name" : "minecraft:raw_copper", + "id" : 507 + }, + { + "name" : "minecraft:raw_copper_block", + "id" : -452 + }, + { + "name" : "minecraft:raw_gold", + "id" : 506 + }, + { + "name" : "minecraft:raw_gold_block", + "id" : -453 + }, + { + "name" : "minecraft:raw_iron", + "id" : 505 + }, + { + "name" : "minecraft:raw_iron_block", + "id" : -451 + }, + { + "name" : "minecraft:recovery_compass", + "id" : 650 + }, + { + "name" : "minecraft:red_candle", + "id" : -427 + }, + { + "name" : "minecraft:red_candle_cake", + "id" : -444 + }, + { + "name" : "minecraft:red_dye", + "id" : 396 + }, + { + "name" : "minecraft:red_flower", + "id" : 38 + }, + { + "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_stairs", + "id" : -184 + }, + { + "name" : "minecraft:red_sandstone", + "id" : 179 + }, + { + "name" : "minecraft:red_sandstone_stairs", + "id" : 180 + }, + { + "name" : "minecraft:redstone", + "id" : 373 + }, + { + "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" : 419 + }, + { + "name" : "minecraft:repeating_command_block", + "id" : 188 + }, + { + "name" : "minecraft:reserved6", + "id" : 255 + }, + { + "name" : "minecraft:respawn_anchor", + "id" : -272 + }, + { + "name" : "minecraft:rotten_flesh", + "id" : 277 + }, + { + "name" : "minecraft:saddle", + "id" : 371 + }, + { + "name" : "minecraft:salmon", + "id" : 265 + }, + { + "name" : "minecraft:salmon_bucket", + "id" : 365 + }, + { + "name" : "minecraft:salmon_spawn_egg", + "id" : 482 + }, + { + "name" : "minecraft:sand", + "id" : 12 + }, + { + "name" : "minecraft:sandstone", + "id" : 24 + }, + { + "name" : "minecraft:sandstone_stairs", + "id" : 128 + }, + { + "name" : "minecraft:sapling", + "id" : 6 + }, + { + "name" : "minecraft:scaffolding", + "id" : -165 + }, + { + "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:scute", + "id" : 572 + }, + { + "name" : "minecraft:sea_lantern", + "id" : 169 + }, + { + "name" : "minecraft:sea_pickle", + "id" : -156 + }, + { + "name" : "minecraft:seagrass", + "id" : -130 + }, + { + "name" : "minecraft:shears", + "id" : 421 + }, + { + "name" : "minecraft:sheep_spawn_egg", + "id" : 438 + }, + { + "name" : "minecraft:shield", + "id" : 355 + }, + { + "name" : "minecraft:shroomlight", + "id" : -230 + }, + { + "name" : "minecraft:shulker_box", + "id" : 218 + }, + { + "name" : "minecraft:shulker_shell", + "id" : 566 + }, + { + "name" : "minecraft:shulker_spawn_egg", + "id" : 469 + }, + { + "name" : "minecraft:silver_glazed_terracotta", + "id" : 228 + }, + { + "name" : "minecraft:silverfish_spawn_egg", + "id" : 443 + }, + { + "name" : "minecraft:skeleton_horse_spawn_egg", + "id" : 467 + }, + { + "name" : "minecraft:skeleton_spawn_egg", + "id" : 444 + }, + { + "name" : "minecraft:skull", + "id" : 516 + }, + { + "name" : "minecraft:skull_banner_pattern", + "id" : 583 + }, + { + "name" : "minecraft:slime", + "id" : 165 + }, + { + "name" : "minecraft:slime_ball", + "id" : 388 + }, + { + "name" : "minecraft:slime_spawn_egg", + "id" : 445 + }, + { + "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_stairs", + "id" : -185 + }, + { + "name" : "minecraft:smooth_red_sandstone_stairs", + "id" : -176 + }, + { + "name" : "minecraft:smooth_sandstone_stairs", + "id" : -177 + }, + { + "name" : "minecraft:smooth_stone", + "id" : -183 + }, + { + "name" : "minecraft:snow", + "id" : 80 + }, + { + "name" : "minecraft:snow_layer", + "id" : 78 + }, + { + "name" : "minecraft:snowball", + "id" : 374 + }, + { + "name" : "minecraft:soul_campfire", + "id" : 622 + }, + { + "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" : 600 + }, + { + "name" : "minecraft:spawn_egg", + "id" : 656 + }, + { + "name" : "minecraft:spider_eye", + "id" : 278 + }, + { + "name" : "minecraft:spider_spawn_egg", + "id" : 446 + }, + { + "name" : "minecraft:splash_potion", + "id" : 561 + }, + { + "name" : "minecraft:sponge", + "id" : 19 + }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, + { + "name" : "minecraft:spruce_boat", + "id" : 378 + }, + { + "name" : "minecraft:spruce_button", + "id" : -144 + }, + { + "name" : "minecraft:spruce_chest_boat", + "id" : 644 + }, + { + "name" : "minecraft:spruce_door", + "id" : 553 + }, + { + "name" : "minecraft:spruce_fence_gate", + "id" : 183 + }, + { + "name" : "minecraft:spruce_hanging_sign", + "id" : -501 + }, + { + "name" : "minecraft:spruce_pressure_plate", + "id" : -154 + }, + { + "name" : "minecraft:spruce_sign", + "id" : 576 + }, + { + "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:spyglass", + "id" : 625 + }, + { + "name" : "minecraft:squid_spawn_egg", + "id" : 450 + }, + { + "name" : "minecraft:stained_glass", + "id" : 241 + }, + { + "name" : "minecraft:stained_glass_pane", + "id" : 160 + }, + { + "name" : "minecraft:stained_hardened_clay", + "id" : 159 + }, + { + "name" : "minecraft:standing_banner", + "id" : 176 + }, + { + "name" : "minecraft:standing_sign", + "id" : 63 + }, + { + "name" : "minecraft:stick", + "id" : 320 + }, + { + "name" : "minecraft:sticky_piston", + "id" : 29 + }, + { + "name" : "minecraft:sticky_piston_arm_collision", + "id" : -217 + }, + { + "name" : "minecraft:stone", + "id" : 1 + }, + { + "name" : "minecraft:stone_axe", + "id" : 315 + }, + { + "name" : "minecraft:stone_block_slab", + "id" : 44 + }, + { + "name" : "minecraft:stone_block_slab2", + "id" : 182 + }, + { + "name" : "minecraft:stone_block_slab3", + "id" : -162 + }, + { + "name" : "minecraft:stone_block_slab4", + "id" : -166 + }, + { + "name" : "minecraft:stone_brick_stairs", + "id" : 109 + }, + { + "name" : "minecraft:stone_button", + "id" : 77 + }, + { + "name" : "minecraft:stone_hoe", + "id" : 330 + }, + { + "name" : "minecraft:stone_pickaxe", + "id" : 314 + }, + { + "name" : "minecraft:stone_pressure_plate", + "id" : 70 + }, + { + "name" : "minecraft:stone_shovel", + "id" : 313 + }, + { + "name" : "minecraft:stone_stairs", + "id" : 67 + }, + { + "name" : "minecraft:stone_sword", + "id" : 312 + }, + { + "name" : "minecraft:stonebrick", + "id" : 98 + }, + { + "name" : "minecraft:stonecutter", + "id" : 245 + }, + { + "name" : "minecraft:stonecutter_block", + "id" : -197 + }, + { + "name" : "minecraft:stray_spawn_egg", + "id" : 462 + }, + { + "name" : "minecraft:strider_spawn_egg", + "id" : 495 + }, + { + "name" : "minecraft:string", + "id" : 326 + }, + { + "name" : "minecraft:stripped_acacia_log", + "id" : -8 + }, + { + "name" : "minecraft:stripped_birch_log", + "id" : -6 + }, + { + "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_jungle_log", + "id" : -7 + }, + { + "name" : "minecraft:stripped_mangrove_log", + "id" : -485 + }, + { + "name" : "minecraft:stripped_mangrove_wood", + "id" : -498 + }, + { + "name" : "minecraft:stripped_oak_log", + "id" : -10 + }, + { + "name" : "minecraft:stripped_spruce_log", + "id" : -5 + }, + { + "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" : 416 + }, + { + "name" : "minecraft:sugar_cane", + "id" : 385 + }, + { + "name" : "minecraft:suspicious_stew", + "id" : 590 + }, + { + "name" : "minecraft:sweet_berries", + "id" : 287 + }, + { + "name" : "minecraft:sweet_berry_bush", + "id" : -207 + }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 630 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 629 + }, + { + "name" : "minecraft:tallgrass", + "id" : 31 + }, + { + "name" : "minecraft:target", + "id" : -239 + }, + { + "name" : "minecraft:tinted_glass", + "id" : -334 + }, + { + "name" : "minecraft:tnt", + "id" : 46 + }, + { + "name" : "minecraft:tnt_minecart", + "id" : 525 + }, + { + "name" : "minecraft:torch", + "id" : 50 + }, + { + "name" : "minecraft:totem_of_undying", + "id" : 568 + }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 652 + }, + { + "name" : "minecraft:trapdoor", + "id" : 96 + }, + { + "name" : "minecraft:trapped_chest", + "id" : 146 + }, + { + "name" : "minecraft:trident", + "id" : 546 + }, + { + "name" : "minecraft:trip_wire", + "id" : 132 + }, + { + "name" : "minecraft:tripwire_hook", + "id" : 131 + }, + { + "name" : "minecraft:tropical_fish", + "id" : 266 + }, + { + "name" : "minecraft:tropical_fish_bucket", + "id" : 366 + }, + { + "name" : "minecraft:tropical_fish_spawn_egg", + "id" : 479 + }, + { + "name" : "minecraft:tuff", + "id" : -333 + }, + { + "name" : "minecraft:turtle_egg", + "id" : -159 + }, + { + "name" : "minecraft:turtle_helmet", + "id" : 573 + }, + { + "name" : "minecraft:turtle_spawn_egg", + "id" : 485 + }, + { + "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:verdant_froglight", + "id" : -470 + }, + { + "name" : "minecraft:vex_spawn_egg", + "id" : 476 + }, + { + "name" : "minecraft:villager_spawn_egg", + "id" : 449 + }, + { + "name" : "minecraft:vindicator_spawn_egg", + "id" : 474 + }, + { + "name" : "minecraft:vine", + "id" : 106 + }, + { + "name" : "minecraft:wall_banner", + "id" : 177 + }, + { + "name" : "minecraft:wall_sign", + "id" : 68 + }, + { + "name" : "minecraft:wandering_trader_spawn_egg", + "id" : 492 + }, + { + "name" : "minecraft:warden_spawn_egg", + "id" : 632 + }, + { + "name" : "minecraft:warped_button", + "id" : -261 + }, + { + "name" : "minecraft:warped_door", + "id" : 617 + }, + { + "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" : 618 + }, + { + "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" : 615 + }, + { + "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" : 362 + }, + { + "name" : "minecraft:waterlily", + "id" : 111 + }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "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_copper", + "id" : -345 + }, + { + "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_copper", + "id" : -446 + }, + { + "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_copper", + "id" : -346 + }, + { + "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:weathered_copper", + "id" : -342 + }, + { + "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" : 334 + }, + { + "name" : "minecraft:wheat_seeds", + "id" : 291 + }, + { + "name" : "minecraft:white_candle", + "id" : -413 + }, + { + "name" : "minecraft:white_candle_cake", + "id" : -430 + }, + { + "name" : "minecraft:white_dye", + "id" : 410 + }, + { + "name" : "minecraft:white_glazed_terracotta", + "id" : 220 + }, + { + "name" : "minecraft:witch_spawn_egg", + "id" : 452 + }, + { + "name" : "minecraft:wither_rose", + "id" : -216 + }, + { + "name" : "minecraft:wither_skeleton_spawn_egg", + "id" : 464 + }, + { + "name" : "minecraft:wolf_spawn_egg", + "id" : 439 + }, + { + "name" : "minecraft:wood", + "id" : -212 + }, + { + "name" : "minecraft:wooden_axe", + "id" : 311 + }, + { + "name" : "minecraft:wooden_button", + "id" : 143 + }, + { + "name" : "minecraft:wooden_door", + "id" : 359 + }, + { + "name" : "minecraft:wooden_hoe", + "id" : 329 + }, + { + "name" : "minecraft:wooden_pickaxe", + "id" : 310 + }, + { + "name" : "minecraft:wooden_pressure_plate", + "id" : 72 + }, + { + "name" : "minecraft:wooden_shovel", + "id" : 309 + }, + { + "name" : "minecraft:wooden_slab", + "id" : 158 + }, + { + "name" : "minecraft:wooden_sword", + "id" : 308 + }, + { + "name" : "minecraft:wool", + "id" : 35 + }, + { + "name" : "minecraft:writable_book", + "id" : 510 + }, + { + "name" : "minecraft:written_book", + "id" : 511 + }, + { + "name" : "minecraft:yellow_candle", + "id" : -417 + }, + { + "name" : "minecraft:yellow_candle_cake", + "id" : -434 + }, + { + "name" : "minecraft:yellow_dye", + "id" : 406 + }, + { + "name" : "minecraft:yellow_flower", + "id" : 37 + }, + { + "name" : "minecraft:yellow_glazed_terracotta", + "id" : 224 + }, + { + "name" : "minecraft:zoglin_spawn_egg", + "id" : 498 + }, + { + "name" : "minecraft:zombie_horse_spawn_egg", + "id" : 468 + }, + { + "name" : "minecraft:zombie_pigman_spawn_egg", + "id" : 448 + }, + { + "name" : "minecraft:zombie_spawn_egg", + "id" : 447 + }, + { + "name" : "minecraft:zombie_villager_spawn_egg", + "id" : 477 + } +] \ No newline at end of file diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 6d1ab9d52..34618025f 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -71,7 +71,7 @@ floodgate-key-file: key.pem # This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, # but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. # Removing a name from this list will delete its cached login information on the next Geyser startup. -# The file for this is in the same folder as this config, named "saved-refresh-tokens.json". +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". saved-user-logins: - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername - ThisOtherExampleUsernameShouldAlsoBeLongEnough @@ -185,6 +185,18 @@ force-resource-packs: true # 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. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. diff --git a/core/src/main/resources/git.properties b/core/src/main/resources/git.properties new file mode 100644 index 000000000..f14e55623 --- /dev/null +++ b/core/src/main/resources/git.properties @@ -0,0 +1,7 @@ +git.branch=${branch} +git.build.number=${buildNumber} +git.build.version=${projectVersion} +git.commit.id=${commit} +git.commit.id.abbrev=${commitAbbrev} +git.commit.message.full=${commitMessage} +git.remote.origin.url=${repository} diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index d92904027..a9cf5999a 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit d92904027061856248ece8382face369e9cc5d67 +Subproject commit a9cf5999af605902b18dd5c77d3562481f8d7f3d diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 919908f48..677c5b087 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 919908f4825e9fa1bb7b5a2f5e09218f0a3f72f3 +Subproject commit 677c5b0872d2f0c99ad834c0ca49a0ae3b45fde3 diff --git a/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java b/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java index 6a280ea57..e83c6f73d 100644 --- a/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java +++ b/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java @@ -85,6 +85,7 @@ public class MessageTranslatorTest { @Test public void convertToPlainText() { Assert.assertEquals("JSON message is not handled properly", "Many colors here", MessageTranslator.convertToPlainText("{\"extra\":[{\"color\":\"red\",\"text\":\"M\"},{\"color\":\"gold\",\"text\":\"a\"},{\"color\":\"yellow\",\"text\":\"n\"},{\"color\":\"green\",\"text\":\"y \"},{\"color\":\"aqua\",\"text\":\"c\"},{\"color\":\"dark_purple\",\"text\":\"o\"},{\"color\":\"red\",\"text\":\"l\"},{\"color\":\"gold\",\"text\":\"o\"},{\"color\":\"yellow\",\"text\":\"r\"},{\"color\":\"green\",\"text\":\"s \"},{\"color\":\"aqua\",\"text\":\"h\"},{\"color\":\"dark_purple\",\"text\":\"e\"},{\"color\":\"red\",\"text\":\"r\"},{\"color\":\"gold\",\"text\":\"e\"}],\"text\":\"\"}", "en_US")); + Assert.assertEquals("Legacy formatted message is not handled properly (Colors)", "Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e")); Assert.assertEquals("Legacy formatted message is not handled properly (Colors)", "Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e", "en_US")); Assert.assertEquals("Legacy formatted message is not handled properly (Style)", "Obf Bold Strikethrough Underline Italic Reset", MessageTranslator.convertToPlainText("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US")); Assert.assertEquals("Valid lenient JSON is not handled properly", "Strange", MessageTranslator.convertToPlainText("§rStrange", "en_US")); diff --git a/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java new file mode 100644 index 000000000..145f46369 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java @@ -0,0 +1,158 @@ +/* + * 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.translator.inventory.item; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.item.GeyserCustomItemOptions; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.OptionalInt; + +public class CustomItemsTest { + private ItemMapping testMappingWithDamage; + private Object2IntMap tagToCustomItemWithDamage; + private ItemMapping testMappingWithNoDamage; + private Object2IntMap tagToCustomItemWithNoDamage; + + @Before + public void setup() { + CustomItemOptions a = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.of(2), OptionalInt.empty()); + CustomItemOptions b = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(5), OptionalInt.empty()); + CustomItemOptions c = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(3)); + CustomItemOptions d = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.empty(), OptionalInt.of(8)); + CustomItemOptions e = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(12)); + CustomItemOptions f = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(8), OptionalInt.of(6)); + CustomItemOptions g = new GeyserCustomItemOptions(TriState.NOT_SET, OptionalInt.of(20), OptionalInt.empty()); + + Object2IntMap optionsToId = new Object2IntArrayMap<>(); + // Order here is important, hence why we're using an array map + optionsToId.put(g, 7); + optionsToId.put(f, 6); + optionsToId.put(e, 5); + optionsToId.put(d, 4); + optionsToId.put(c, 3); + optionsToId.put(b, 2); + optionsToId.put(a, 1); + + tagToCustomItemWithDamage = new Object2IntOpenHashMap<>(); + + CompoundTag tag = new CompoundTag(""); + addCustomModelData(6, tag); + // Test item with no damage should be treated as unbreakable + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + addCustomModelData(20, tag); + // Test that an unbreakable item isn't tested for Damaged if there is no damaged predicate + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(g)); + + tag = new CompoundTag(""); + addCustomModelData(3, tag); + setUnbreakable(true, tag); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + addDamage(16, tag); + setUnbreakable(false, tag); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e)); + + tag = new CompoundTag(""); + addCustomModelData(7, tag); + addDamage(6, tag); + setUnbreakable(false, tag); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c)); + + tag = new CompoundTag(""); + addCustomModelData(9, tag); + addDamage(6, tag); + setUnbreakable(true, tag); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + addCustomModelData(9, tag); + addDamage(6, tag); + setUnbreakable(false, tag); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(f)); + + List> customItemOptions = optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList(); + + testMappingWithDamage = ItemMapping.builder() + .customItemOptions(customItemOptions) + .maxDamage(100) + .build(); + + // Test differences with items with no max damage + + tagToCustomItemWithNoDamage = new Object2IntOpenHashMap<>(); + + tag = new CompoundTag(""); + addCustomModelData(2, tag); + // Damage predicates existing mean an item will never match if the item mapping has no max damage + tagToCustomItemWithNoDamage.put(tag, -1); + + testMappingWithNoDamage = ItemMapping.builder() + .customItemOptions(customItemOptions) + .maxDamage(0) + .build(); + } + + private void addCustomModelData(int value, CompoundTag tag) { + tag.put(new IntTag("CustomModelData", value)); + } + + private void addDamage(int value, CompoundTag tag) { + tag.put(new IntTag("Damage", value)); + } + + private void setUnbreakable(boolean value, CompoundTag tag) { + tag.put(new ByteTag("Unbreakable", (byte) (value ? 1 : 0))); + } + + @Test + public void testCustomItems() { + for (Object2IntMap.Entry entry : this.tagToCustomItemWithDamage.object2IntEntrySet()) { + int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithDamage); + Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id); + } + + for (Object2IntMap.Entry entry : this.tagToCustomItemWithNoDamage.object2IntEntrySet()) { + int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithNoDamage); + Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id); + } + } +} diff --git a/gradle.properties b/gradle.properties index 8f6ac8e85..4fb72e2fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,9 @@ +# Gradle settings +org.gradle.jvmargs=-Xmx4G +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.parallel=true + group=org.geysermc version=2.1.0-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..9b595671e --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,98 @@ +[versions] +jackson = "2.14.0" +fastutil = "8.5.2" +netty = "4.1.80.Final" +guava = "29.0-jre" +gson = "2.3.1" # Provided by Spigot 1.8.8 +websocket = "1.5.1" +protocol = "2.9.15-20221129.204554-2" +raknet = "1.6.28-20220125.214016-6" +mcauthlib = "d9d773e" +mcprotocollib = "1.19.3-20221218.141127-8" +packetlib = "3.0.1" +adventure = "4.12.0-20220629.025215-9" +adventure-platform = "4.1.2" +junit = "4.13.1" +checkerframework = "3.19.0" +cumulus = "1.1.1" +events = "1.0-SNAPSHOT" +log4j = "2.17.1" +jline = "3.21.0" +terminalconsoleappender = "1.2.0" +paper = "1.19-R0.1-SNAPSHOT" +viaversion = "4.0.0" +adapters = "1.6-SNAPSHOT" +commodore = "2.2" +bungeecord = "a7c6ede" +velocity = "3.0.0" +sponge = "8.0.0" +fabric-minecraft = "1.19.1" +fabric-loader = "0.14.8" +fabric-api = "0.58.5+1.19.1" + +[libraries] +jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" } +jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" } +jackson-dataformat-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" } + +fastutil-int-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-int-maps", version.ref = "fastutil" } +fastutil-int-long-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-long-maps", version.ref = "fastutil" } +fastutil-int-byte-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-byte-maps", version.ref = "fastutil" } +fastutil-int-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-boolean-maps", version.ref = "fastutil" } +fastutil-object-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version.ref = "fastutil" } +fastutil-object-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version.ref = "fastutil" } + +adventure-text-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" } # Remove when we remove our Adventure bump +adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" } +adventure-text-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" } +adventure-text-serializer-bungeecord = { group = "net.kyori", name = "adventure-text-serializer-bungeecord", version.ref = "adventure-platform" } + +netty-resolver-dns = { group = "io.netty", name = "netty-resolver-dns", version.ref = "netty" } +netty-resolver-dns-native-macos = { group = "io.netty", name = "netty-resolver-dns-native-macos", version.ref = "netty" } +netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", version.ref = "netty" } +netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" } +netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" } +netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" } + +log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } +log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" } +log4j-slf4j18-impl = { group = "org.apache.logging.log4j", name = "log4j-slf4j18-impl", version.ref = "log4j" } + +jline-terminal = { group = "org.jline", name = "jline-terminal", version.ref = "jline" } +jline-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" } +jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jline" } + +paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } +paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "paper" } + +# check these on https://modmuss50.me/fabric.html +fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } + +adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", version.ref = "adapters" } +bungeecord-proxy = { group = "com.github.SpigotMC.BungeeCord", name = "bungeecord-proxy", version.ref = "bungeecord" } +checker-qual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerframework" } +commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" } +cumulus = { group = "org.geysermc.cumulus", name = "cumulus", version.ref = "cumulus" } +events = { group = "org.geysermc.event", name = "events", version.ref = "events" } +guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" } +mcprotocollib = { group = "com.github.steveice10", name = "mcprotocollib", version.ref = "mcprotocollib" } +packetlib = { group = "com.github.steveice10", name = "packetlib", version.ref = "packetlib" } +protocol = { group = "com.nukkitx.protocol", name = "bedrock-v560", version.ref = "protocol" } +raknet = { group = "com.nukkitx.network", name = "raknet", version.ref = "raknet" } +sponge-api = { group = "org.spongepowered", name = "spongeapi", version.ref = "sponge" } +terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" } +velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } +viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" } +websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" } + +[bundles] +jackson = [ "jackson-annotations", "jackson-core", "jackson-dataformat-yaml" ] +fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps" ] +adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ] +log4j = [ "log4j-api", "log4j-core", "log4j-slf4j18-impl" ] +jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..8049c684f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index dd08f3922..6f212d3ac 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,20 +1,10 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) +// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { // Floodgate, Cumulus etc. - maven("https://repo.opencollab.dev/maven-releases") { - mavenContent { releasesOnly() } - } - maven("https://repo.opencollab.dev/maven-snapshots") { - mavenContent { - // This has the unintended side effect of not allowing snapshot version pinning. - // Likely a bug in Gradle's implementation of snapshot pinning - // See https://github.com/gradle/gradle/pull/406 - snapshotsOnly() - } - } + maven("https://repo.opencollab.dev/main") // Paper, Velocity maven("https://repo.papermc.io/repository/maven-public") @@ -48,12 +38,17 @@ dependencyResolutionManagement { maven("https://jitpack.io") { content { includeGroupByRegex("com\\.github\\..*") } } + + // For Adventure snapshots + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") } } pluginManagement { repositories { gradlePluginPortal() + maven("https://maven.fabricmc.net/") + maven("https://repo.opencollab.dev/maven-snapshots") } plugins { id("net.kyori.blossom") version "1.2.0" @@ -69,6 +64,7 @@ include(":ap") include(":api") include(":geyser-api") include(":bungeecord") +include(":fabric") include(":spigot") include(":sponge") include(":standalone") @@ -80,6 +76,7 @@ include(":core") project(":api").projectDir = file("api/base") project(":geyser-api").projectDir = file("api/geyser") project(":bungeecord").projectDir = file("bootstrap/bungeecord") +project(":fabric").projectDir = file("bootstrap/fabric") project(":spigot").projectDir = file("bootstrap/spigot") project(":sponge").projectDir = file("bootstrap/sponge") project(":standalone").projectDir = file("bootstrap/standalone")