diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9621fa1d039..39e9fe1881a 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -9,47 +9,50 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Set up JDK 16 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 16 + distribution: 'temurin' + java-version: 17 + cache: 'gradle' - name: submodules-init uses: snickerbockers/submodules-init@v4 - - name: Build with Maven - run: mvn -B package -T 2C + - 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/target/Geyser.jar + path: bootstrap/standalone/build/libs/Geyser-Standalone.jar - name: Archive artifacts (Geyser Spigot) uses: actions/upload-artifact@v2 if: success() with: name: Geyser Spigot - path: bootstrap/spigot/target/Geyser-Spigot.jar + path: bootstrap/spigot/build/libs/Geyser-Spigot.jar - name: Archive artifacts (Geyser BungeeCord) uses: actions/upload-artifact@v2 if: success() with: name: Geyser BungeeCord - path: bootstrap/bungeecord/target/Geyser-BungeeCord.jar + path: bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar - name: Archive artifacts (Geyser Sponge) uses: actions/upload-artifact@v2 if: success() with: name: Geyser Sponge - path: bootstrap/sponge/target/Geyser-Sponge.jar + path: bootstrap/sponge/build/libs/Geyser-Sponge.jar - name: Archive artifacts (Geyser Velocity) uses: actions/upload-artifact@v2 if: success() with: name: Geyser Velocity - path: bootstrap/velocity/target/Geyser-Velocity.jar + path: bootstrap/velocity/build/libs/Geyser-Velocity.jar diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 598cab46ad8..00000000000 --- 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/.gitignore b/.gitignore index f1baa3abb8e..2b7e2972c29 100644 --- a/.gitignore +++ b/.gitignore @@ -235,8 +235,12 @@ nbdist/ # End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode +### Gradle ### +.gradle + ### Geyser ### run/ +extensions/ config.yml logs/ key.pem @@ -245,4 +249,5 @@ locales/ /packs/ /dump.json /saved-refresh-tokens.json -/languages/ \ No newline at end of file +/custom_mappings/ +/languages/ diff --git a/Jenkinsfile b/Jenkinsfile index 1a98f47ad53..072f991540c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,8 @@ pipeline { agent any tools { - maven 'Maven 3' - jdk 'Java 16' + gradle 'Gradle 7' + jdk 'Java 17' } options { buildDiscarder(logRotator(artifactNumToKeepStr: '20')) @@ -11,11 +11,16 @@ pipeline { stage ('Build') { steps { sh 'git submodule update --init --recursive' - sh 'mvn clean package' + rtGradleRun( + usesPlugin: true, + tool: 'Gradle 7', + buildFile: 'build.gradle.kts', + tasks: 'clean build', + ) } post { success { - archiveArtifacts artifacts: 'bootstrap/**/target/*.jar', excludes: 'bootstrap/**/target/original-*.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/Geyser-*.jar', fingerprint: true } } } @@ -28,23 +33,25 @@ pipeline { } steps { - rtMavenDeployer( - id: "maven-deployer", + rtGradleDeployer( + id: "GRADLE_DEPLOYER", serverId: "opencollab-artifactory", releaseRepo: "maven-releases", snapshotRepo: "maven-snapshots" ) - rtMavenResolver( - id: "maven-resolver", - serverId: "opencollab-artifactory", - releaseRepo: "maven-deploy-release", - snapshotRepo: "maven-deploy-snapshot" + rtGradleResolver( + id: "GRADLE_RESOLVER", + serverId: "opencollab-artifactory" ) - rtMavenRun( - pom: 'pom.xml', - goals: 'javadoc:jar source:jar install -pl :core -am -DskipTests', - deployerId: "maven-deployer", - resolverId: "maven-resolver" + rtGradleRun( + usesPlugin: true, + tool: 'Gradle 7', + rootDir: "", + useWrapper: true, + buildFile: 'build.gradle.kts', + tasks: 'artifactoryPublish', + deployerId: "GRADLE_DEPLOYER", + resolverId: "GRADLE_RESOLVER" ) rtPublishBuildInfo( serverId: "opencollab-artifactory" @@ -94,7 +101,6 @@ pipeline { success { script { if (env.BRANCH_NAME == 'master') { - build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/master', 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 62db2d60a19..dc6e21b1ad6 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/1.19.11 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/ap/build.gradle.kts b/ap/build.gradle.kts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ap/pom.xml b/ap/pom.xml deleted file mode 100644 index 90bb1dc7397..00000000000 --- a/ap/pom.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - - - ap - 2.0.6-SNAPSHOT - \ No newline at end of file diff --git a/ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java similarity index 97% rename from ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java index 7ab760cecfe..f9ba683022f 100644 --- a/ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; diff --git a/ap/src/main/java/org/geysermc/processor/ClassProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java similarity index 96% rename from ap/src/main/java/org/geysermc/processor/ClassProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java index a6259a853e2..e1da50f258e 100644 --- a/ap/src/main/java/org/geysermc/processor/ClassProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -39,6 +39,7 @@ import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; @@ -163,10 +164,15 @@ private BufferedReader createReader() throws IOException { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + this.outputPath); return Files.newBufferedReader(this.outputPath); } + FileObject obj = this.processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", this.annotationClassName); if (obj != null) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + obj.toUri()); - return new BufferedReader(obj.openReader(false)); + try { + return new BufferedReader(obj.openReader(false)); + } catch (NoSuchFileException ignored) { + return null; + } } return null; } diff --git a/ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java similarity index 97% rename from ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java index 971abd98482..84e2e2ffd3e 100644 --- a/ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; diff --git a/ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/ItemRemapperProcessor.java similarity index 97% rename from ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/ItemRemapperProcessor.java index 39d5f9fdfff..2dd00506d98 100644 --- a/ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/ItemRemapperProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; diff --git a/ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java similarity index 97% rename from ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java index 97687e98198..9b99d679b26 100644 --- a/ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; diff --git a/ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java similarity index 97% rename from ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java index 3e6a7c412fd..c35c0ee4e06 100644 --- a/ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.processor; +package org.geysermc.geyser.processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; diff --git a/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000000..1f6475b61a8 --- /dev/null +++ b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,5 @@ +org.geysermc.geyser.processor.BlockEntityProcessor +org.geysermc.geyser.processor.CollisionRemapperProcessor +org.geysermc.geyser.processor.ItemRemapperProcessor +org.geysermc.geyser.processor.PacketTranslatorProcessor +org.geysermc.geyser.processor.SoundHandlerProcessor \ No newline at end of file diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts new file mode 100644 index 00000000000..6b6fb8f46b2 --- /dev/null +++ b/api/base/build.gradle.kts @@ -0,0 +1,7 @@ +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/pom.xml b/api/base/pom.xml deleted file mode 100644 index 0eeb536ea70..00000000000 --- a/api/base/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - org.geysermc - api-parent - 2.0.6-SNAPSHOT - - 4.0.0 - - base-api - - - 16 - 16 - - - - - org.checkerframework - checker-qual - 3.19.0 - provided - - - \ 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 9f315faf489..7543d16617a 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 static T api(@NonNull Class apiClass) { /** * 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 static void set(@NonNull GeyserApiBase api) { * * @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 3549a912a62..a845e37fdf8 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.geysermc.api.session.Connection; +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 @@ */ 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); + + /** + * Sends a form to the given connection and opens it. + * + * @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); /** - * 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 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 0; + 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 new file mode 100644 index 00000000000..1cd7a9d1384 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/connection/Connection.java @@ -0,0 +1,121 @@ +/* + * 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.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. + */ +public interface Connection { + /** + * Returns the bedrock name of the connection. + */ + @NonNull String bedrockUsername(); + + /** + * Returns the java name of the connection. + */ + @MonotonicNonNull + String javaUsername(); + + /** + * Returns the UUID of the connection. + */ + @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 + * @return true if the transfer was a success + */ + boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); +} diff --git a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java new file mode 100644 index 00000000000..15d0da02701 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.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.api.util; + +import org.checkerframework.checker.nullness.qual.NonNull; + +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; + } + + /** + * Get the BedrockPlatform from the identifier. + * + * @param id the BedrockPlatform identifier + * @return The BedrockPlatform or {@link #UNKNOWN} if the platform wasn't found + */ + @NonNull + public static BedrockPlatform fromId(int id) { + return id < VALUES.length ? VALUES[id] : VALUES[0]; + } + + /** + * @return friendly display name of platform. + */ + @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 00000000000..70346ffa563 --- /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/api/base/src/main/java/org/geysermc/api/util/UiProfile.java b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java new file mode 100644 index 00000000000..cddb9726033 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java @@ -0,0 +1,45 @@ +/* + * 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 UiProfile { + CLASSIC, POCKET; + + private static final UiProfile[] VALUES = values(); + + /** + * 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/build.gradle.kts b/api/geyser/build.gradle.kts new file mode 100644 index 00000000000..dcde8533749 --- /dev/null +++ b/api/geyser/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("geyser.api-conventions") +} + +dependencies { + api(projects.api) +} + +publishing { + publications.named("mavenJava") { + groupId = rootProject.group as String + ".geyser" + artifactId = "api" + } +} \ No newline at end of file diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml deleted file mode 100644 index 0071668bf4a..00000000000 --- a/api/geyser/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - org.geysermc - api-parent - 2.0.6-SNAPSHOT - - 4.0.0 - - geyser-api - - - 16 - 16 - - - - - org.checkerframework - checker-qual - 3.19.0 - provided - - - org.geysermc - base-api - 2.0.6-SNAPSHOT - compile - - - \ No newline at end of file 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 07491888195..f86206d366d 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 @@ -27,8 +27,14 @@ 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.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; import java.util.List; import java.util.UUID; @@ -38,44 +44,76 @@ */ public interface GeyserApi extends GeyserApiBase { /** - * Shuts down the current Geyser instance. + * {@inheritDoc} */ - void shutdown(); + @Override + @Nullable GeyserConnection connectionByUuid(@NonNull UUID uuid); + + /** + * {@inheritDoc} + */ + @Override + @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); /** - * Reloads the current Geyser instance. + * {@inheritDoc} */ - void reload(); + @NonNull + List onlineConnections(); /** - * Gets if this Geyser instance is running in an IDE. This only needs to be used in cases where files - * expected to be in a jarfile are not present. + * Gets the {@link ExtensionManager}. * - * @return true if the version number is not 'DEV'. + * @return the extension manager */ - boolean productionEnvironment(); + @NonNull + ExtensionManager extensionManager(); /** - * {@inheritDoc} + * Provides an implementation for the specified API type. + * + * @param apiClass the builder class + * @param the implementation type + * @param the API type + * @return the builder instance */ - @Override - @Nullable GeyserConnection connectionByUuid(@NonNull UUID uuid); + @NonNull + R provider(@NonNull Class apiClass, @Nullable Object... args); /** - * {@inheritDoc} + * Gets the {@link EventBus} for handling + * Geyser events. + * + * @return the event bus */ - @Override - @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); + @NonNull + EventBus eventBus(); /** - * {@inheritDoc} + * Gets the default {@link RemoteServer} configured + * within the config file that is used by default. + * + * @return the default remote server used within Geyser */ - @Override - @Nullable GeyserConnection connectionByName(@NonNull String name); + @NonNull + RemoteServer defaultRemoteServer(); /** - * {@inheritDoc} + * Gets the {@link BedrockListener} used for listening + * for Minecraft: Bedrock Edition client connections. + * + * @return the listener used for Bedrock client connectins */ @NonNull - List onlineConnections(); + BedrockListener bedrockListener(); + + /** + * Gets the current {@link GeyserApiBase} instance. + * + * @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 new file mode 100644 index 00000000000..2f1f2b24dba --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -0,0 +1,215 @@ +/* + * 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.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; + +/** + * Represents a command. + */ +public interface Command { + + /** + * Gets the command name. + * + * @return the command name + */ + @NonNull + String name(); + + /** + * Gets the command description. + * + * @return the command description + */ + @NonNull + String description(); + + /** + * Gets the permission node associated with + * this command. + * + * @return the permission node for this command + */ + @NonNull + String permission(); + + /** + * Gets the aliases for this command. + * + * @return the aliases for this command + */ + @NonNull + List aliases(); + + /** + * Gets if this command is designed to be used only by server operators. + * + * @return if this command is designated to be used only by server operators. + */ + boolean isSuggestedOpOnly(); + + /** + * Gets if this command is executable on console. + * + * @return if this command is executable on console + */ + boolean isExecutableOnConsole(); + + /** + * Gets the subcommands associated with this + * command. Mainly used within the Geyser Standalone + * GUI to know what subcommands are supported. + * + * @return the subcommands associated with this command + */ + @NonNull + default List subCommands() { + return Collections.emptyList(); + } + + /** + * Used to send a deny message to Java players if this command can only be used by Bedrock players. + * + * @return true if this command can only be used by Bedrock players. + */ + default boolean isBedrockOnly() { + return false; + } + + /** + * 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(@NonNull String name); + + /** + * Sets the command description. + * + * @param description the command description + * @return the builder + */ + Builder description(@NonNull String description); + + /** + * Sets the permission node. + * + * @param permission the permission node + * @return the builder + */ + Builder permission(@NonNull String permission); + + /** + * Sets the aliases. + * + * @param aliases the aliases + * @return the builder + */ + Builder aliases(@NonNull List aliases); + + /** + * Sets if this command is designed to be used only by server operators. + * + * @param suggestedOpOnly if this command is designed to be used only by server operators + * @return the builder + */ + Builder suggestedOpOnly(boolean suggestedOpOnly); + + /** + * Sets if this command is executable on console. + * + * @param executableOnConsole if this command is executable on console + * @return the builder + */ + Builder executableOnConsole(boolean executableOnConsole); + + /** + * Sets the subcommands. + * + * @param subCommands the subcommands + * @return the builder + */ + Builder subCommands(@NonNull List subCommands); + + /** + * Sets if this command is bedrock only. + * + * @param bedrockOnly if this command is bedrock only + * @return the builder + */ + Builder bedrockOnly(boolean bedrockOnly); + + /** + * Sets the {@link CommandExecutor} for this command. + * + * @param executor the command executor + * @return the builder + */ + 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 new file mode 100644 index 00000000000..12a54ee9082 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java @@ -0,0 +1,45 @@ +/* + * 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.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}. + * + * @param source the command source + * @param command the command + * @param args the arguments + */ + 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 new file mode 100644 index 00000000000..45276e2c466 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -0,0 +1,81 @@ +/* + * 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.command; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents an instance capable of sending commands. + */ +public interface CommandSource { + + /** + * The name of the command source. + * + * @return the name of the command source + */ + String name(); + + /** + * Sends the given message to the command source + * + * @param message the message to send + */ + void sendMessage(@NonNull String message); + + /** + * Sends the given messages to the command source + * + * @param messages the messages to send + */ + default void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + /** + * If this source is the console. + * + * @return true if this source is the console + */ + boolean isConsole(); + + /** + * Returns the locale of the command source. + * + * @return the locale of the command source. + */ + String locale(); + + /** + * Checks if this command source has the given permission + * + * @param permission The permission node to check + * @return true if this command source has a permission + */ + boolean hasPermission(String permission); +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index 79260ac9593..13fd60407c4 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -25,10 +25,11 @@ package org.geysermc.geyser.api.connection; -import org.geysermc.api.session.Connection; +import org.geysermc.api.connection.Connection; +import org.geysermc.geyser.api.command.CommandSource; /** - * Represents a player session used in Geyser. + * Represents a player connection used in Geyser. */ -public interface GeyserConnection extends Connection { +public interface GeyserConnection extends Connection, CommandSource { } 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 new file mode 100644 index 00000000000..801bfa45fd3 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.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.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; + +/** + * Represents a bus capable of subscribing + * or "listening" to events and firing them. + */ +public interface EventBus extends OwnedEventBus> { + @Override + @NonNull + 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 00000000000..064dd55f60f --- /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 00000000000..7f91d09a39e --- /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 new file mode 100644 index 00000000000..a58d3589173 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java @@ -0,0 +1,41 @@ +/* + * 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.event.Event; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.Set; + +/** + * An {@link EventBus} with additional methods that implicitly + * set the extension instance. + */ +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/ExtensionEventSubscriber.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java new file mode 100644 index 00000000000..9c5fffa2f17 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java @@ -0,0 +1,32 @@ +/* + * 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.Subscriber; + +public interface ExtensionEventSubscriber extends Subscriber { +} 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 new file mode 100644 index 00000000000..158f14d53bb --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.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.api.event.connection; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.connection.GeyserConnection; + +/** + * An event that contains a {@link GeyserConnection}. + */ +public abstract class ConnectionEvent implements Event { + private final GeyserConnection connection; + + public ConnectionEvent(@NonNull GeyserConnection connection) { + this.connection = connection; + } + + /** + * Gets the {@link GeyserConnection}. + * + * @return the connection + */ + @NonNull + public GeyserConnection connection() { + return this.connection; + } +} 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 new file mode 100644 index 00000000000..e46492b3697 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.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.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.connection.ConnectionEvent; + +import java.util.Set; + +/** + * Called when the Java server defines the commands available on the server. + *
+ * This event is mapped to the existence of Brigadier on the server. + */ +public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable { + private final Set commands; + private boolean cancelled; + + public ServerDefineCommandsEvent(@NonNull GeyserConnection connection, @NonNull Set commands) { + super(connection); + this.commands = commands; + } + + /** + * A collection of commands sent from the server. Any element in this collection can be removed, but no element can + * be added. + * + * @return a collection of the commands sent over + */ + @NonNull + public Set commands() { + return this.commands; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public interface CommandInfo { + /** + * Gets the name of the command. + * + * @return the name of the command + */ + String name(); + + /** + * Gets the description of the command. + * + * @return the description of the command + */ + String description(); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java new file mode 100644 index 00000000000..77d5efa658e --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -0,0 +1,57 @@ +/* + * 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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.command.Command; + +import java.util.Map; + +/** + * 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 interface GeyserDefineCommandsEvent extends Event { + + /** + * Registers the given {@link Command} into the Geyser + * command manager. + * + * @param command the command to register + */ + void register(@NonNull Command command); + + /** + * Gets all the registered built-in {@link Command}s. + * + * @return all the registered built-in commands + */ + @NonNull + 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 new file mode 100644 index 00000000000..0957b8551c1 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -0,0 +1,76 @@ +/* + * 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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; + +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 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 + */ + @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 + */ + @NonNull + List getExistingNonVanillaCustomItems(); + + /** + * 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 + */ + 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 + */ + 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 new file mode 100644 index 00000000000..e9b283ecbbd --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; + +import java.nio.file.Path; +import java.util.List; + +/** + * Called when resource packs are loaded within Geyser. + * + * @param resourcePacks a mutable list of the currently listed resource packs + */ +public record GeyserLoadResourcePacksEvent(@NonNull List resourcePacks) implements Event { +} 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 new file mode 100644 index 00000000000..8d145f615dc --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java @@ -0,0 +1,41 @@ +/* + * 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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +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 has completed initializing. + * + * @param extensionManager the extension manager + * @param eventBus the event bus + */ +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 new file mode 100644 index 00000000000..8be89dafdcf --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java @@ -0,0 +1,41 @@ +/* + * 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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +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 starting to initialize. + * + * @param extensionManager the extension manager + * @param eventBus the event bus + */ +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 new file mode 100644 index 00000000000..7793ef997c8 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -0,0 +1,38 @@ +/* + * 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.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +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 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 new file mode 100644 index 00000000000..33fc159de52 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -0,0 +1,139 @@ +/* + * 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.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 extends EventRegistrar { + + /** + * Gets if the extension is enabled + * + * @return true if the extension is enabled + */ + default boolean isEnabled() { + return this.extensionLoader().isEnabled(this); + } + + /** + * Enables or disables the extension + * + * @param enabled if the extension should be enabled + */ + default void setEnabled(boolean enabled) { + this.extensionLoader().setEnabled(this, enabled); + } + + /** + * Gets the extension's data folder + * + * @return the extension's data folder + */ + @NonNull + default Path dataFolder() { + return this.extensionLoader().dataFolder(this); + } + + /** + * Gets the {@link ExtensionEventBus}. + * + * @return the extension event bus + */ + @NonNull + default ExtensionEventBus eventBus() { + return this.extensionLoader().eventBus(this); + } + + /** + * Gets the {@link ExtensionManager}. + * + * @return the extension manager + */ + @NonNull + default ExtensionManager extensionManager() { + return this.geyserApi().extensionManager(); + } + + /** + * Gets the extension's name + * + * @return the extension's name + */ + @NonNull + default String name() { + return this.description().name(); + } + + /** + * Gets this extension's {@link ExtensionDescription}. + * + * @return the extension's description + */ + @NonNull + default ExtensionDescription description() { + return this.extensionLoader().description(this); + } + + /** + * Gets the extension's logger + * + * @return the extension's logger + */ + @NonNull + default ExtensionLogger logger() { + return this.extensionLoader().logger(this); + } + + /** + * Gets the {@link ExtensionLoader}. + * + * @return the extension loader + */ + @NonNull + default ExtensionLoader extensionLoader() { + return Objects.requireNonNull(this.extensionManager().extensionLoader()); + } + + /** + * Gets the {@link GeyserApiBase} instance + * + * @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 new file mode 100644 index 00000000000..2df3ee815b5 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -0,0 +1,106 @@ +/* + * 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.extension; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +/** + * Represents the description of an {@link Extension}. + */ +public interface ExtensionDescription { + + /** + * Gets the extension's id. + * + * @return the extension's id + */ + @NonNull + String id(); + + /** + * Gets the extension's name. + * + * @return the extension's name + */ + @NonNull + String name(); + + /** + * Gets the extension's main class. + * + * @return the extension's main class + */ + @NonNull + String main(); + + /** + * 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 + */ + default String apiVersion() { + return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); + } + + /** + * Gets the extension's description. + * + * @return the extension's description + */ + @NonNull + String version(); + + /** + * Gets the extension's authors. + * + * @return the extension's authors + */ + @NonNull + List 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 new file mode 100644 index 00000000000..30414d500e4 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -0,0 +1,105 @@ +/* + * 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.extension; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.ExtensionEventBus; + +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. + * + * @param extension the extension + * @return if the extension is enabled + */ + protected abstract boolean isEnabled(@NonNull Extension extension); + + /** + * Sets if the given {@link Extension} is enabled. + * + * @param extension the extension to enable + * @param enabled if the extension should be enabled + */ + protected abstract void setEnabled(@NonNull Extension extension, boolean enabled); + + /** + * Gets the given {@link Extension}'s data folder. + * + * @param extension the extension + * @return the data folder of the given extension + */ + @NonNull + protected abstract Path dataFolder(@NonNull Extension extension); + + /** + * Gets the given {@link Extension}'s {@link ExtensionDescription}. + * + * @param extension the extension + * @return the description of the given extension + */ + @NonNull + protected abstract ExtensionDescription description(@NonNull Extension extension); + + /** + * Gets the given {@link Extension}'s {@link ExtensionEventBus}. + * + * @param extension the extension + * @return the extension's event bus + */ + @NonNull + protected abstract ExtensionEventBus eventBus(@NonNull Extension extension); + + /** + * Gets the {@link ExtensionLogger} for the given {@link Extension}. + * + * @param extension the extension + * @return the extension logger for the given extension + */ + @NonNull + protected abstract ExtensionLogger logger(@NonNull Extension extension); + + /** + * Loads all extensions. + * + * @param extensionManager the extension manager + */ + protected abstract void loadAllExtensions(@NonNull ExtensionManager extensionManager); + + /** + * Registers the given {@link Extension} with the given {@link ExtensionManager}. + * + * @param extension the extension + * @param extensionManager the extension manager + */ + protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { + extensionManager.register(extension); + } +} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java new file mode 100644 index 00000000000..17e108455fd --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java @@ -0,0 +1,94 @@ +/* + * 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.extension; + +/** + * This is the Geyser extension logger + */ +public interface ExtensionLogger { + /** + * Get the logger prefix + * + * @return the logger prefix + */ + String prefix(); + + /** + * Logs a severe message to console + * + * @param message the message to log + */ + void severe(String message); + + /** + * Logs a severe message and an exception to console + * + * @param message the message to log + * @param error the error to throw + */ + void severe(String message, Throwable error); + + /** + * Logs an error message to console + * + * @param message the message to log + */ + void error(String message); + + /** + * Logs an error message and an exception to console + * + * @param message the message to log + * @param error the error to throw + */ + void error(String message, Throwable error); + + /** + * Logs a warning message to console + * + * @param message the message to log + */ + void warning(String message); + + /** + * Logs an info message to console + * + * @param message the message to log + */ + void info(String message); + + /** + * Logs a debug message to console + * + * @param message the message to log + */ + void debug(String message); + + /** + * If debug is enabled for this logger + */ + boolean isDebug(); +} 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 new file mode 100644 index 00000000000..a9d0d7376d2 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -0,0 +1,90 @@ +/* + * 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.extension; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; + +/** + * Manages Geyser {@link Extension}s + */ +public abstract class ExtensionManager { + + /** + * Gets an extension with the given name. + * + * @param name the name of the extension + * @return an extension with the given name + */ + @Nullable + public abstract Extension extension(@NonNull String name); + + /** + * Enables the given {@link Extension}. + * + * @param extension the extension to enable + */ + public abstract void enable(@NonNull Extension extension); + + /** + * Disables the given {@link Extension}. + * + * @param extension the extension to disable + */ + public abstract void disable(@NonNull Extension extension); + + /** + * Gets all the {@link Extension}s currently loaded. + * + * @return all the extensions currently loaded + */ + @NonNull + public abstract Collection extensions(); + + /** + * Gets the {@link ExtensionLoader}. + * + * @return the extension loader + */ + @Nullable + public abstract ExtensionLoader extensionLoader(); + + /** + * Registers an {@link Extension} with the given {@link ExtensionLoader}. + * + * @param extension the extension + */ + public abstract void register(@NonNull Extension extension); + + /** + * Loads all extensions from the given {@link ExtensionLoader}. + */ + protected final void loadAllExtensions(@NonNull ExtensionLoader extensionLoader) { + extensionLoader.loadAllExtensions(this); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java new file mode 100644 index 00000000000..1fe88e9e990 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.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.api.extension.exception; + +/** + * Thrown when an extension's description is invalid. + */ +public class InvalidDescriptionException extends Exception { + public InvalidDescriptionException(Throwable cause) { + super(cause); + } + + public InvalidDescriptionException(String message) { + super(message); + } + + public InvalidDescriptionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java new file mode 100644 index 00000000000..7fb6b692241 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.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.api.extension.exception; + +/** + * Thrown when an extension is invalid. + */ +public class InvalidExtensionException extends Exception { + public InvalidExtensionException(Throwable cause) { + super(cause); + } + + public InvalidExtensionException(String message) { + super(message); + } + + public InvalidExtensionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java new file mode 100644 index 00000000000..17763fb77d8 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -0,0 +1,109 @@ +/* + * 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.item.custom; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.GeyserApi; + +/** + * This is used to store data for a custom item. + */ +public interface CustomItemData { + /** + * Gets the item's name. + * + * @return the item's name + */ + @NonNull String name(); + + /** + * Gets the custom item options of the item. + * + * @return the custom item options of the item. + */ + CustomItemOptions customItemOptions(); + + /** + * Gets the item's display name. By default, this is the item's name. + * + * @return the item's display name + */ + @NonNull String displayName(); + + /** + * Gets the item's icon. By default, this is the item's name. + * + * @return the item's icon + */ + @NonNull String icon(); + + /** + * Gets if the item is allowed to be put into the offhand. + * + * @return true if the item is allowed to be used in the offhand, false otherwise + */ + boolean allowOffhand(); + + /** + * Gets the item's texture size. This is to resize the item if the texture is not 16x16. + * + * @return the item's texture size + */ + int textureSize(); + + /** + * Gets the item's render offsets. If it is null, the item will be rendered normally, with no offsets. + * + * @return the item's render offsets + */ + @Nullable CustomRenderOffsets renderOffsets(); + + static CustomItemData.Builder builder() { + return GeyserApi.api().provider(CustomItemData.Builder.class); + } + + interface Builder { + /** + * Will also set the display name and icon to the provided parameter, if it is currently not set. + */ + Builder name(@NonNull String name); + + Builder customItemOptions(@NonNull CustomItemOptions customItemOptions); + + Builder displayName(@NonNull String displayName); + + Builder icon(@NonNull String icon); + + Builder allowOffhand(boolean allowOffhand); + + Builder textureSize(int textureSize); + + Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); + + CustomItemData build(); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java new file mode 100644 index 00000000000..ec26a6e375c --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java @@ -0,0 +1,83 @@ +/* + * 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.item.custom; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.util.TriState; + +import java.util.OptionalInt; + +/** + * This class represents the different ways you can register custom items + */ +public interface CustomItemOptions { + /** + * Gets if the item should be unbreakable. + * + * @return if the item should be unbreakable + */ + @NonNull TriState unbreakable(); + + /** + * Gets the item's custom model data predicate. + * + * @return the item's custom model data + */ + @NonNull OptionalInt customModelData(); + + /** + * Gets the item's damage predicate. + * + * @return the item's damage predicate + */ + @NonNull OptionalInt damagePredicate(); + + /** + * Checks if the item has at least one option set + * + * @return true if the item at least one options set + */ + default boolean hasCustomItemOptions() { + return this.unbreakable() != TriState.NOT_SET || + this.customModelData().isPresent() || + this.damagePredicate().isPresent(); + } + + static CustomItemOptions.Builder builder() { + return GeyserApi.api().provider(CustomItemOptions.Builder.class); + } + + interface Builder { + Builder unbreakable(boolean unbreakable); + + Builder customModelData(int customModelData); + + Builder damagePredicate(int damagePredicate); + + CustomItemOptions build(); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java new file mode 100644 index 00000000000..f81da0ae261 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.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.api.item.custom; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * This class is used to store the render offsets of custom items. + */ +public record CustomRenderOffsets(@Nullable Hand mainHand, @Nullable Hand offhand) { + /** + * The hand that is used for the offset. + */ + public record Hand(@Nullable Offset firstPerson, @Nullable Offset thirdPerson) { + } + + /** + * The offset of the item. + */ + public record Offset(@Nullable OffsetXYZ position, @Nullable OffsetXYZ rotation, @Nullable OffsetXYZ scale) { + } + + /** + * X, Y and Z positions for the offset. + */ + public record OffsetXYZ(float x, float y, float z) { + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java new file mode 100644 index 00000000000..d2cef637a9c --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java @@ -0,0 +1,188 @@ +/* + * 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.item.custom; + +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.GeyserApi; + +import java.util.OptionalInt; +import java.util.Set; + +/** + * Represents a completely custom item that is not based on an existing vanilla Minecraft item. + */ +public interface NonVanillaCustomItemData extends CustomItemData { + /** + * Gets the java identifier for this item. + * + * @return The java identifier for this item. + */ + @NonNull String identifier(); + + /** + * Gets the java item id of the item. + * + * @return the java item id of the item + */ + @NonNegative int javaId(); + + /** + * Gets the stack size of the item. + * + * @return the stack size of the item + */ + @NonNegative int stackSize(); + + /** + * Gets the max damage of the item. + * + * @return the max damage of the item + */ + int maxDamage(); + + /** + * Gets the tool type of the item. + * + * @return the tool type of the item + */ + @Nullable String toolType(); + + /** + * Gets the tool tier of the item. + * + * @return the tool tier of the item + */ + @Nullable String toolTier(); + + /** + * Gets the armor type of the item. + * + * @return the armor type of the item + */ + @Nullable String armorType(); + + /** + * Gets the armor protection value of the item. + * + * @return the armor protection value of the item + */ + int protectionValue(); + + /** + * Gets the item's translation string. + * + * @return the item's translation string + */ + @Nullable String translationString(); + + /** + * Gets the repair materials of the item. + * + * @return the repair materials of the item + */ + @Nullable Set repairMaterials(); + + /** + * Gets the item's creative category, or tab id. + * + * @return the item's creative category + */ + @NonNull OptionalInt creativeCategory(); + + /** + * Gets the item's creative group. + * + * @return the item's creative group + */ + @Nullable String creativeGroup(); + + /** + * Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and + * normally allow the player to equip it. This is not meant for armor. + * + * @return if the item is a hat + */ + boolean isHat(); + + /** + * Gets if the item is a tool. This is used to set the render type of the item, if the item is handheld. + * + * @return if the item is a tool + */ + boolean isTool(); + + static NonVanillaCustomItemData.Builder builder() { + return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class); + } + + interface Builder extends CustomItemData.Builder { + Builder name(@NonNull String name); + + Builder identifier(@NonNull String identifier); + + Builder javaId(@NonNegative int javaId); + + Builder stackSize(@NonNegative int stackSize); + + Builder maxDamage(int maxDamage); + + Builder toolType(@Nullable String toolType); + + Builder toolTier(@Nullable String toolTier); + + Builder armorType(@Nullable String armorType); + + Builder protectionValue(int protectionValue); + + Builder translationString(@Nullable String translationString); + + Builder repairMaterials(@Nullable Set repairMaterials); + + Builder creativeCategory(int creativeCategory); + + Builder creativeGroup(@Nullable String creativeGroup); + + Builder hat(boolean isHat); + + Builder tool(boolean isTool); + + @Override + Builder displayName(@NonNull String displayName); + + @Override + Builder allowOffhand(boolean allowOffhand); + + @Override + Builder textureSize(int textureSize); + + @Override + Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); + + NonVanillaCustomItemData build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java similarity index 69% rename from core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java index 1edbd0f2970..3176f338422 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java @@ -23,26 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.session.auth; +package org.geysermc.geyser.api.network; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import lombok.Getter; +import java.util.Locale; -import java.io.IOException; - -@Getter +/** + * The authentication types that a Java server can be on connection. + */ public enum AuthType { OFFLINE, ONLINE, + /** + * The internal name for connecting to an online mode server without needing a Java account. The presence of this + * authentication type does not necessarily mean the Floodgate plugin is installed; it only means that this + * authentication type will be attempted. + */ FLOODGATE; - public static final AuthType[] VALUES = values(); - - public static AuthType getById(int id) { - return id < VALUES.length ? VALUES[id] : OFFLINE; - } + private static final AuthType[] VALUES = values(); /** * Convert the AuthType string (from config) to the enum, ONLINE on fail @@ -52,7 +50,7 @@ public static AuthType getById(int id) { * @return The converted AuthType */ public static AuthType getByName(String name) { - String upperCase = name.toUpperCase(); + String upperCase = name.toUpperCase(Locale.ROOT); for (AuthType type : VALUES) { if (type.name().equals(upperCase)) { return type; @@ -60,11 +58,4 @@ public static AuthType getByName(String name) { } return ONLINE; } - - public static class Deserializer extends JsonDeserializer { - @Override - public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return getByName(p.getValueAsString()); - } - } } \ No newline at end of file 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 new file mode 100644 index 00000000000..61fe286aa13 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java @@ -0,0 +1,77 @@ +/* + * 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.network; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * The listener that handles connections from Minecraft: + * Bedrock Edition. + */ +public interface BedrockListener { + + /** + * Gets the address used for listening for Bedrock + * connections from. + * + * @return the listening address + */ + @NonNull + String address(); + + /** + * Gets the port used for listening for Bedrock + * connections from. + * + * @return the listening port + */ + int port(); + + /** + * Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled. + *

+ * This is the first line that will be displayed. + * + * @return the primary MOTD shown to Bedrock players. + */ + String primaryMotd(); + + /** + * Gets the secondary MOTD shown to Bedrock players if a ping passthrough setting is not enabled. + *

+ * This is the second line that will be displayed. + * + * @return the secondary MOTD shown to Bedrock players. + */ + String secondaryMotd(); + + /** + * Gets the server name that is sent to Bedrock clients. + * + * @return the server sent to Bedrock clients + */ + String serverName(); +} diff --git a/api/base/src/main/java/org/geysermc/api/session/Connection.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java similarity index 61% rename from api/base/src/main/java/org/geysermc/api/session/Connection.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java index 3e997912b4a..8ac5d8a03ad 100644 --- a/api/base/src/main/java/org/geysermc/api/session/Connection.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java @@ -23,46 +23,48 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.api.session; +package org.geysermc.geyser.api.network; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.common.value.qual.IntRange; - -import java.util.UUID; /** - * Represents a player connection. + * Represents the Java server that Geyser is connecting to. */ -@NonNull -public interface Connection { +public interface RemoteServer { + + /** + * Gets the IP address of the remote server. + * + * @return the IP address of the remote server + */ + String address(); + /** - * Gets the name of the connection. + * Gets the port of the remote server. * - * @return the name of the connection + * @return the port of the remote server */ - String name(); + int port(); /** - * Gets the {@link UUID} of the connection. + * Gets the protocol version of the remote server. * - * @return the UUID of the connection + * @return the protocol version of the remote server */ - UUID uuid(); + int protocolVersion(); /** - * Gets the XUID of the connection. + * Gets the Minecraft version of the remote server. * - * @return the XUID of the connection + * @return the Minecraft version of the remote server */ - String xuid(); + String minecraftVersion(); /** - * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. + * Gets the {@link AuthType} required by the remote server. * - * @param address The address of the server - * @param port The port of the server - * @return true if the transfer was a success + * @return the auth type required by the remote server */ - boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); + @NonNull + AuthType authType(); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java b/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java new file mode 100644 index 00000000000..457a38e32e2 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java @@ -0,0 +1,83 @@ +/* + * 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.util; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * This is a way to represent a boolean, but with a non set value added. + * This class was inspired by adventure's version https://github.com/KyoriPowered/adventure/blob/main/4/api/src/main/java/net/kyori/adventure/util/TriState.java + */ +public enum TriState { + /** + * Describes a value that is not set, null, or not present. + */ + NOT_SET, + + /** + * Describes a true value. + */ + TRUE, + + /** + * Describes a false value. + */ + FALSE; + + /** + * Converts the TriState to a boolean. + * + * @return the boolean value of the TriState + */ + public @Nullable Boolean toBoolean() { + return switch (this) { + case TRUE -> true; + case FALSE -> false; + default -> null; + }; + } + + /** + * Creates a TriState from a boolean. + * + * @param value the Boolean value + * @return the created TriState + */ + public static @NonNull TriState fromBoolean(@Nullable Boolean value) { + return value == null ? NOT_SET : fromBoolean(value.booleanValue()); + } + + /** + * Creates a TriState from a primitive boolean. + * + * @param value the boolean value + * @return the created TriState + */ + public @NonNull static TriState fromBoolean(boolean value) { + return value ? TRUE : FALSE; + } +} diff --git a/api/pom.xml b/api/pom.xml deleted file mode 100644 index 9b4816954ec..00000000000 --- a/api/pom.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - - - api-parent - pom - - - 16 - 16 - - - - base - geyser - - \ No newline at end of file diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts new file mode 100644 index 00000000000..3e0e9c147f3 --- /dev/null +++ b/bootstrap/bungeecord/build.gradle.kts @@ -0,0 +1,35 @@ +dependencies { + api(projects.core) + + implementation(libs.adventure.text.serializer.bungeecord) +} + +platformRelocate("net.md_5.bungee.jni") +platformRelocate("com.fasterxml.jackson") +platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound +platformRelocate("net.kyori") + +// These dependencies are already present on the platform +provided(libs.bungeecord.proxy) + +application { + mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain") +} + +tasks.withType { + archiveBaseName.set("Geyser-BungeeCord") + + dependencies { + exclude(dependency("com.google.*:.*")) + exclude(dependency("org.yaml:.*")) + exclude(dependency("io.netty:netty-transport-native-epoll:.*")) + exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) + exclude(dependency("io.netty:netty-handler:.*")) + exclude(dependency("io.netty:netty-common:.*")) + exclude(dependency("io.netty:netty-buffer:.*")) + exclude(dependency("io.netty:netty-resolver:.*")) + exclude(dependency("io.netty:netty-transport:.*")) + exclude(dependency("io.netty:netty-codec:.*")) + exclude(dependency("io.netty:netty-resolver-dns:.*")) + } +} \ No newline at end of file diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml deleted file mode 100644 index 4ec01539fcd..00000000000 --- a/bootstrap/bungeecord/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.6-SNAPSHOT - - bootstrap-bungeecord - - - - org.geysermc - core - 2.0.6-SNAPSHOT - compile - - - - com.github.SpigotMC.BungeeCord - bungeecord-proxy - a7c6ede - provided - - - - ${outputName}-BungeeCord - - - src/main/resources/ - true - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - - - net.md_5.bungee.jni - org.geysermc.geyser.platform.bungeecord.shaded.jni - - - com.fasterxml.jackson - org.geysermc.geyser.platform.bungeecord.shaded.jackson - - - - io.netty.channel.kqueue - org.geysermc.geyser.platform.bungeecord.shaded.io.netty.channel.kqueue - - - net.kyori - org.geysermc.geyser.platform.bungeecord.shaded.kyori - - - - - - - - - com.google.*:* - org.yaml:* - io.netty:netty-transport-native-epoll:* - io.netty:netty-transport-native-unix-common:* - io.netty:netty-handler:* - io.netty:netty-common:* - io.netty:netty-buffer:* - io.netty:netty-resolver:* - io.netty:netty-transport:* - io.netty:netty-codec:* - io.netty:netty-resolver-dns:* - - - - - - - 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 54cb16edbdf..ba7bc464ffd 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 @@ -28,7 +28,6 @@ import lombok.Getter; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; -import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.dump.BootstrapDumpInfo; import java.util.ArrayList; @@ -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/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java index 2b1fa10c0ac..cef430bd62d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java @@ -39,8 +39,8 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.netty.PipelineUtils; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; import org.geysermc.geyser.network.netty.LocalSession; 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 0883c5ff022..dc760216311 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,36 +25,43 @@ 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.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; -import org.geysermc.geyser.command.CommandManager; -import org.geysermc.geyser.session.auth.AuthType; +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; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.util.FileUtils; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; -import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandManager; -import org.jetbrains.annotations.Nullable; +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; @@ -63,13 +70,13 @@ 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_1"); + ProtocolConstants.class.getField("MINECRAFT_1_19_3"); } catch (NoSuchFieldException e) { getLogger().warning(" / \\"); getLogger().warning(" / \\"); @@ -95,13 +102,27 @@ public void onEnable() { 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]; InetSocketAddress javaAddr = listener.getHost(); // By default this should be localhost but may need to be changed in some circumstances - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); // Don't use localhost if not listening on all interfaces if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { @@ -115,16 +136,25 @@ public void onEnable() { } } - 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().getAuthType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { + if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) { @@ -135,20 +165,63 @@ public void onEnable() { 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)); } @Override @@ -172,7 +245,7 @@ public GeyserBungeeLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } 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 00000000000..c68839b2095 --- /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/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java similarity index 64% rename from bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index 05df8ba9796..f65377643a1 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -25,19 +25,23 @@ 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.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; -public class BungeeCommandSender implements CommandSender { +import java.util.Locale; + +public class BungeeCommandSource implements GeyserCommandSource { private final net.md_5.bungee.api.CommandSender handle; - public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) { + public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded - GeyserLocale.loadGeyserLocale(getLocale()); + GeyserLocale.loadGeyserLocale(this.locale()); } @Override @@ -50,16 +54,31 @@ public void sendMessage(String message) { 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); } @Override - public String getLocale() { + 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 5bb323aacd7..2d02c9950a7 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 @@ -30,44 +30,48 @@ import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandExecutor; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import java.util.Arrays; import java.util.Collections; +import java.util.Map; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private final CommandExecutor commandExecutor; + private final GeyserCommandExecutor commandExecutor; - public GeyserBungeeCommandExecutor(GeyserImpl geyser) { - super("geyser"); + public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) { + super(name); - this.commandExecutor = new CommandExecutor(geyser); + this.commandExecutor = new GeyserCommandExecutor(geyser, commands); } @Override public void execute(CommandSender sender, String[] args) { - BungeeCommandSender commandSender = new BungeeCommandSender(sender); + BungeeCommandSource commandSender = new BungeeCommandSource(sender); GeyserSession session = this.commandExecutor.getGeyserSession(commandSender); if (args.length > 0) { GeyserCommand command = this.commandExecutor.getCommand(args[0]); if (command != null) { - if (!sender.hasPermission(command.getPermission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); + if (!sender.hasPermission(command.permission())) { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); commandSender.sendMessage(ChatColor.RED + message); return; } if (command.isBedrockOnly() && session == null) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale()); + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()); commandSender.sendMessage(ChatColor.RED + message); return; } command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); + commandSender.sendMessage(ChatColor.RED + message); } } else { this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]); @@ -77,7 +81,7 @@ public void execute(CommandSender sender, String[] args) { @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return commandExecutor.tabComplete(new BungeeCommandSender(sender)); + return commandExecutor.tabComplete(new BungeeCommandSource(sender)); } else { return Collections.emptyList(); } diff --git a/bootstrap/bungeecord/src/main/resources/bungee.yml b/bootstrap/bungeecord/src/main/resources/bungee.yml index 7390a4623cb..1e18b8da492 100644 --- a/bootstrap/bungeecord/src/main/resources/bungee.yml +++ b/bootstrap/bungeecord/src/main/resources/bungee.yml @@ -1,5 +1,5 @@ main: org.geysermc.geyser.platform.bungeecord.GeyserBungeePlugin -name: ${outputName}-BungeeCord -author: ${project.organization.name} -website: ${project.organization.url} -version: ${project.version} \ No newline at end of file +name: ${name}-BungeeCord +author: ${author} +website: ${url} +version: ${version} \ No newline at end of file diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts new file mode 100644 index 00000000000..743b75a2697 --- /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 3079c523ff9..f557d16c0ea 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.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; +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; - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - return BlockStateValues.JAVA_AIR_ID; - } +public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { + @JsonIgnore + private Path floodgateKeyPath; - @Override - public boolean hasOwnChunkCache() { - return false; + 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 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 00000000000..ee986ee6216 --- /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 00000000000..180197f2dd0 --- /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/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java similarity index 75% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java index 03d780f3cc1..f3f63324a9b 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.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.standalone.command; +package org.geysermc.geyser.platform.fabric; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.GeyserMain; -public class GeyserCommandManager extends CommandManager { +public class GeyserFabricMain extends GeyserMain { - public GeyserCommandManager(GeyserImpl geyser) { - super(geyser); + public static void main(String[] args) { + new GeyserFabricMain().displayMessage(); } @Override - public String getDescription(String command) { - return ""; // this is not sent over the protocol, so we return none + 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 00000000000..e5ff4b57731 --- /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 00000000000..1ea69cbe225 --- /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 00000000000..4f1c8b638f7 --- /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 00000000000..5973e04f178 --- /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 00000000000..7600e41361f --- /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 00000000000..94290906888 --- /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 00000000000..23e148775df --- /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 00000000000..eb4f61c6760 --- /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 00000000000..4e6a38a787f 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 00000000000..98a4109508b --- /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 00000000000..c688ace364c --- /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/pom.xml b/bootstrap/pom.xml deleted file mode 100644 index 0da8638114e..00000000000 --- a/bootstrap/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - - bootstrap-parent - pom - - - - spigot-public - https://hub.spigotmc.org/nexus/content/repositories/public/ - - - sponge-repo - https://repo.spongepowered.org/repository/maven-public/ - - - bungeecord-repo - https://oss.sonatype.org/content/repositories/snapshots - - - velocity-repo - https://repo.velocitypowered.com/snapshots/ - - - - - - org.geysermc - ap - 2.0.6-SNAPSHOT - provided - - - - - bungeecord - spigot - sponge - standalone - velocity - - \ No newline at end of file diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts new file mode 100644 index 00000000000..b5ef4e69e55 --- /dev/null +++ b/bootstrap/spigot/build.gradle.kts @@ -0,0 +1,63 @@ +dependencies { + api(projects.core) + + implementation(libs.adapters.spigot) + + implementation(libs.commodore) + + implementation(libs.adventure.text.serializer.bungeecord) + + // Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 + compileOnly(libs.paper.api) { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) + } + } + compileOnly(libs.paper.mojangapi) { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) + } + } +} + +platformRelocate("it.unimi.dsi.fastutil") +platformRelocate("com.fasterxml.jackson") +// 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(libs.viaversion) + +application { + mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") +} + +tasks.withType { + archiveBaseName.set("Geyser-Spigot") + + dependencies { + exclude(dependency("com.google.*:.*")) + exclude(dependency("org.yaml:.*")) + + // We cannot shade Netty, or else native libraries will not load + // Needed because older Spigot builds do not provide the haproxy module + exclude(dependency("io.netty:netty-transport-native-epoll:.*")) + exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) + exclude(dependency("io.netty:netty-transport-native-kqueue:.*")) + exclude(dependency("io.netty:netty-handler:.*")) + exclude(dependency("io.netty:netty-common:.*")) + exclude(dependency("io.netty:netty-buffer:.*")) + exclude(dependency("io.netty:netty-resolver:.*")) + exclude(dependency("io.netty:netty-transport:.*")) + exclude(dependency("io.netty:netty-codec:.*")) + exclude(dependency("io.netty:netty-codec-dns:.*")) + exclude(dependency("io.netty:netty-resolver-dns:.*")) + exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*")) + + // Commodore includes Brigadier + exclude(dependency("com.mojang:.*")) + } +} \ No newline at end of file diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml deleted file mode 100644 index 25bcb23f908..00000000000 --- a/bootstrap/spigot/pom.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.6-SNAPSHOT - - bootstrap-spigot - - - - papermc - https://repo.papermc.io/repository/maven-public/ - - - viaversion-repo - https://repo.viaversion.com - - - - minecraft-repo - https://libraries.minecraft.net/ - - - - - - org.geysermc - core - 2.0.6-SNAPSHOT - compile - - - io.papermc.paper - paper-api - 1.19-R0.1-SNAPSHOT - provided - - - io.papermc.paper - paper-mojangapi - 1.19-R0.1-SNAPSHOT - provided - - - com.viaversion - viaversion - 4.0.0 - provided - - - org.geysermc.geyser.adapters - spigot-all - 1.5-SNAPSHOT - - - me.lucko - commodore - 1.13 - compile - - - - ${outputName}-Spigot - - - src/main/resources/ - true - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - org.geysermc.geyser.platform.spigot.GeyserSpigotMain - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - - - it.unimi.dsi.fastutil - org.geysermc.geyser.platform.spigot.shaded.fastutil - - - com.fasterxml.jackson - org.geysermc.geyser.platform.spigot.shaded.jackson - - - net.kyori - org.geysermc.geyser.platform.spigot.shaded.kyori - - - org.objectweb.asm - org.geysermc.geyser.platform.spigot.shaded.asm - - - me.lucko.commodore - org.geysermc.geyser.platform.spigot.shaded.commodore - - - - io.netty.channel.kqueue - org.geysermc.geyser.platform.spigot.shaded.io.netty.channel.kqueue - - - - - - - - - com.google.*:* - org.yaml:* - - - io.netty:netty-transport-native-epoll:* - io.netty:netty-transport-native-unix-common:* - io.netty:netty-handler:* - io.netty:netty-common:* - io.netty:netty-buffer:* - io.netty:netty-resolver:* - io.netty:netty-transport:* - io.netty:netty-codec:* - io.netty:netty-codec-dns:* - io.netty:netty-resolver-dns:* - io.netty:netty-resolver-dns-native-macos:* - com.mojang:* - - - - - - - \ No newline at end of file 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 00000000000..930f84cecd8 --- /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/GeyserPaperPingPassthrough.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperPingPassthrough.java index 15bd6bde130..36dd81d4410 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperPingPassthrough.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperPingPassthrough.java @@ -29,7 +29,7 @@ import com.destroystokyo.paper.network.StatusClient; import com.destroystokyo.paper.profile.PlayerProfile; import org.bukkit.Bukkit; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.jetbrains.annotations.NotNull; @@ -62,11 +62,11 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { // Approximately pre-1.19 event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), - Bukkit.getMaxPlayers(), Bukkit.getVersion(), MinecraftProtocol.getJavaProtocolVersion(), null); + Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null); } else { event = new PaperServerListPingEvent(new GeyserStatusClient(inetSocketAddress), Bukkit.getMotd(), Bukkit.shouldSendChatPreviews(), Bukkit.getOnlinePlayers().size(), - Bukkit.getMaxPlayers(), Bukkit.getVersion(), MinecraftProtocol.getJavaProtocolVersion(), null); + Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null); } Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { @@ -82,7 +82,7 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { } GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), players, - new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftProtocol.getJavaProtocolVersion())); + new GeyserPingInfo.Version(Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion())); if (!event.shouldHidePlayers()) { for (PlayerProfile profile : event.getPlayerSample()) { @@ -105,7 +105,7 @@ private record GeyserStatusClient(InetSocketAddress address) implements StatusCl @Override public int getProtocolVersion() { - return MinecraftProtocol.getJavaProtocolVersion(); + return GameProtocol.getJavaProtocolVersion(); } @Override 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 7f8213155fe..d340935b3a2 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 @@ -28,8 +28,8 @@ import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; import java.util.ArrayList; import java.util.List; @@ -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/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java index 0fd8d849b4e..c1d3b68718f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -170,8 +170,8 @@ private ChannelInitializer getChildHandler(GeyserBootstrap bootstrap, C */ private void workAroundWeirdBug(GeyserBootstrap bootstrap) { MinecraftProtocol protocol = new MinecraftProtocol(); - LocalSession session = new LocalSession(bootstrap.getGeyserConfig().getRemote().getAddress(), - bootstrap.getGeyserConfig().getRemote().getPort(), this.serverSocketAddress, + LocalSession session = new LocalSession(bootstrap.getGeyserConfig().getRemote().address(), + bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper()); session.connect(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java index db5a0a1e18c..634d1f8a865 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java @@ -30,7 +30,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.util.CachedServerIcon; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; @@ -52,7 +52,7 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { Bukkit.getPluginManager().callEvent(event); GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()), - new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftProtocol.getJavaProtocolVersion()) // thanks Spigot for not exposing this, just default to latest + new GeyserPingInfo.Version(Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion()) // thanks Spigot for not exposing this, just default to latest ); Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(geyserPingInfo.getPlayerList()::add); return geyserPingInfo; 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 21c54308de6..5f00613826a 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,36 +32,44 @@ 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; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.command.CommandManager; -import org.geysermc.geyser.command.GeyserCommand; +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; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; -import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.*; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; 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,23 +147,31 @@ public void onEnable() { 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().getAddress().equalsIgnoreCase("auto")) { + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Don't use localhost if not listening on all interfaces if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { @@ -137,20 +184,9 @@ public void onEnable() { 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().getAuthType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { + 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 void onEnable() { 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 { @@ -178,8 +255,6 @@ public void onEnable() { } geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); - boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { try { @@ -193,14 +268,6 @@ public void onEnable() { } } } - // 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 - SpigotCommandSender.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* @@ -213,13 +280,7 @@ public void onEnable() { 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); @@ -236,48 +297,77 @@ public void onEnable() { } 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 pluginCommand = this.getCommand("geyser"); - pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); + PluginCommand geyserCommand = this.getCommand("geyser"); + geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); + + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + PluginCommand command = this.getCommand(entry.getKey().description().id()); + if (command == null) { + continue; + } + + command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); + } 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()) { - GeyserCommand command = entry.getValue(); - if (command.getAliases().contains(entry.getKey())) { + for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { + Command command = entry.getValue(); + if (command.aliases().contains(entry.getKey())) { // Don't register aliases continue; } - Bukkit.getPluginManager().addPermission(new Permission(command.getPermission(), - GeyserLocale.getLocaleStringLog(command.getDescription()), + Bukkit.getPluginManager().addPermission(new Permission(command.permission(), + GeyserLocale.getLocaleStringLog(command.description()), command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); } + // Register permissions for extension commands + for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) { + for (Map.Entry entry : commandEntry.getValue().entrySet()) { + Command command = entry.getValue(); + if (command.aliases().contains(entry.getKey())) { + // Don't register aliases + continue; + } + + if (command.permission().isBlank()) { + continue; + } + + 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(); geyserLogger.debug("Brigadier supported? " + brigadierSupported); if (brigadierSupported) { - GeyserBrigadierSupport.loadBrigadier(this, pluginCommand); + GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); } // Check to ensure the current setup can support the protocol version Geyser uses @@ -307,7 +397,7 @@ public GeyserSpigotLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } @@ -389,7 +479,7 @@ public ProtocolVersion getServerProtocolVersion() { */ private boolean isViaVersionNeeded() { ProtocolVersion serverVersion = getServerProtocolVersion(); - List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(), serverVersion.getVersion()); if (protocolList == null) { // No translation needed! 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 00000000000..5e3c4def8e1 --- /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/GeyserSpigotVersionChecker.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotVersionChecker.java index 923209e59e0..0212ff9b096 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotVersionChecker.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotVersionChecker.java @@ -29,7 +29,7 @@ import org.bukkit.Bukkit; import org.bukkit.UnsafeValues; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.GeyserLocale; import java.lang.reflect.InvocationTargetException; @@ -48,7 +48,7 @@ public static void checkForSupportedProtocol(GeyserLogger logger, boolean viaver try { // This method is only present on later versions of Paper UnsafeValues.class.getMethod("getProtocolVersion"); - if (Bukkit.getUnsafe().getProtocolVersion() != MinecraftProtocol.getJavaProtocolVersion()) { + if (Bukkit.getUnsafe().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) { sendOutdatedMessage(logger); } return; @@ -82,7 +82,7 @@ public static void checkForSupportedProtocol(GeyserLogger logger, boolean viaver } return; } - if (protocolVersion != MinecraftProtocol.getJavaProtocolVersion()) { + if (protocolVersion != GameProtocol.getJavaProtocolVersion()) { sendOutdatedMessage(logger); } return; @@ -94,13 +94,13 @@ public static void checkForSupportedProtocol(GeyserLogger logger, boolean viaver private static void checkViaVersionSupportedVersions(GeyserLogger logger) { // Run after ViaVersion has obtained the server protocol version Via.getPlatform().runSync(() -> { - if (Via.getAPI().getSupportedVersions().contains(MinecraftProtocol.getJavaProtocolVersion())) { + if (Via.getAPI().getSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) { // Via supports this protocol version; we will be able to connect. return; } - if (Via.getAPI().getFullSupportedVersions().contains(MinecraftProtocol.getJavaProtocolVersion())) { + if (Via.getAPI().getFullSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) { // ViaVersion supports our protocol, but the user has blocked them from connecting. - logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.blocked", MinecraftProtocol.getAllSupportedJavaVersions())); + logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.blocked", GameProtocol.getAllSupportedJavaVersions())); return; } // Else, presumably, ViaVersion is not updated. @@ -114,7 +114,7 @@ public static void sendOutdatedViaVersionMessage(GeyserLogger logger) { } private static void sendOutdatedMessage(GeyserLogger logger) { - logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.no_supported_protocol", MinecraftProtocol.getAllSupportedJavaVersions(), VIAVERSION_DOWNLOAD_URL)); + logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.no_supported_protocol", GameProtocol.getAllSupportedJavaVersions(), VIAVERSION_DOWNLOAD_URL)); } private GeyserSpigotVersionChecker() { 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 00000000000..5dd16da33ae --- /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/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java index 00c1ba58d09..9375e3a6271 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java @@ -31,7 +31,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.api.command.Command; import java.net.InetSocketAddress; import java.util.Iterator; @@ -49,14 +49,14 @@ public void onCommandSend(AsyncPlayerSendCommandsEvent event) { if (geyserBrigadier != null) { Player player = event.getPlayer(); boolean isJavaPlayer = isProbablyJavaPlayer(player); - Map commands = GeyserImpl.getInstance().getCommandManager().getCommands(); + Map commands = GeyserImpl.getInstance().commandManager().getCommands(); Iterator> it = geyserBrigadier.getChildren().iterator(); while (it.hasNext()) { CommandNode subnode = it.next(); - GeyserCommand command = commands.get(subnode.getName()); + Command command = commands.get(subnode.getName()); if (command != null) { - if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.getPermission())) { + if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) { // Remove this from the node as we don't have permission to use it it.remove(); } 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 b1bcfcaf828..61d39421463 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 @@ -30,41 +30,45 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandExecutor; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; -public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor { +public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor { - public GeyserSpigotCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - SpigotCommandSender commandSender = new SpigotCommandSender(sender); + SpigotCommandSource commandSender = new SpigotCommandSource(sender); GeyserSession session = getGeyserSession(commandSender); if (args.length > 0) { GeyserCommand geyserCommand = getCommand(args[0]); if (geyserCommand != null) { - if (!sender.hasPermission(geyserCommand.getPermission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); + if (!sender.hasPermission(geyserCommand.permission())) { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); commandSender.sendMessage(ChatColor.RED + message); return true; } if (geyserCommand.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale())); return true; } geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); + commandSender.sendMessage(ChatColor.RED + message); } } else { getCommand("help").execute(session, commandSender, new String[0]); @@ -76,7 +80,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return tabComplete(new SpigotCommandSender(sender)); + return tabComplete(new SpigotCommandSource(sender)); } return Collections.emptyList(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java index 6107d5b4723..655d3be231b 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 @@ -30,11 +30,11 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; import java.lang.reflect.Field; -public class GeyserSpigotCommandManager extends CommandManager { +public class GeyserSpigotCommandManager extends GeyserCommandManager { private static final CommandMap COMMAND_MAP; @@ -61,8 +61,12 @@ public GeyserSpigotCommandManager(GeyserImpl geyser) { } @Override - public String getDescription(String command) { + public String description(String command) { 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/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java deleted file mode 100644 index a05a6ebe0d1..00000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.platform.spigot.command; - -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; -import org.geysermc.geyser.text.GeyserLocale; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class SpigotCommandSender implements CommandSender { - - /** - * 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 SpigotCommandSender(org.bukkit.command.CommandSender handle) { - this.handle = handle; - this.locale = getSpigotLocale(); - // Ensure even Java players' languages are loaded - GeyserLocale.loadGeyserLocale(locale); - } - - @Override - public String name() { - return handle.getName(); - } - - @Override - public void sendMessage(String message) { - handle.sendMessage(message); - } - - @Override - public boolean isConsole() { - return handle instanceof ConsoleCommandSender; - } - - @Override - public String getLocale() { - return locale; - } - - @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/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java new file mode 100644 index 00000000000..95fba707fe1 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.command; + +import 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.command.GeyserCommandSource; +import org.geysermc.geyser.platform.spigot.PaperAdventure; +import org.geysermc.geyser.text.GeyserLocale; + +public class SpigotCommandSource implements GeyserCommandSource { + private final org.bukkit.command.CommandSender handle; + + public SpigotCommandSource(org.bukkit.command.CommandSender handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + GeyserLocale.loadGeyserLocale(locale()); + } + + @Override + public String name() { + return handle.getName(); + } + + @Override + public void sendMessage(String message) { + 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; + } + + @Override + public String locale() { + if (this.handle instanceof Player player) { + return player.getLocale(); + } + + return GeyserLocale.getDefaultLocale(); + } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } +} 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 981d00b9790..5eb99e10c33 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 @@ -41,12 +41,12 @@ import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPistonRetractEvent; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.cache.PistonCache; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; import java.util.List; import java.util.Map; @@ -105,13 +105,10 @@ private void onPistonAction(BlockPistonEvent event) { // 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 @@ private void onPistonAction(BlockPistonEvent event) { 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/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index 62a56bd2dbe..d486501de1d 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -33,10 +33,10 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; @AllArgsConstructor public class GeyserSpigotBlockPlaceListener implements Listener { 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 670070a68ed..00000000000 --- 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.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; - -/** - * 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 1936d608f31..00000000000 --- 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.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; - -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/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 2e0491db887..baffc96792c 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -32,9 +32,9 @@ import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.geyser.network.MinecraftProtocol; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; +import org.geysermc.geyser.session.GeyserSession; import java.util.List; @@ -50,7 +50,7 @@ public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) { IntList allBlockStates = adapter.getAllBlockStates(); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); - List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(), serverVersion.getVersion()); for (int oldBlockId : allBlockStates) { int newBlockId = oldBlockId; 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 2db01ab4f49..6b5d1ea1e77 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 @@ -28,10 +28,11 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; 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 int getBlockAt(GeyserSession session, int x, int y, int z) { } 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 a03549444a2..52f29dcfe5d 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,19 +33,17 @@ 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.network.MinecraftProtocol; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; -import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.level.GameRule; +import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.util.BlockEntityUtils; -import org.geysermc.geyser.level.GameRule; import java.util.ArrayList; import java.util.List; @@ -53,12 +51,7 @@ /** * 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 = MinecraftProtocol.getJavaProtocolVersion(); - +public class GeyserSpigotWorldManager extends WorldManager { private final Plugin plugin; public GeyserSpigotWorldManager(Plugin plugin) { @@ -77,10 +70,10 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { 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 boolean shouldExpectLecternHandled() { 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 int getGameRuleInt(GeyserSession session, GameRule gameRule) { if (!value.isEmpty()) { return Integer.parseInt(value); } - return (int) gameRule.getDefaultValue(); + return gameRule.getDefaultIntValue(); } @Override @@ -181,8 +174,6 @@ public boolean hasPermission(GeyserSession session, String permission) { } /** - * 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 aa27479799f..e28b8981d87 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -1,11 +1,11 @@ main: org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin -name: ${outputName}-Spigot -author: ${project.organization.name} -website: ${project.organization.url} -version: ${project.version} +name: ${name}-Spigot +author: ${author} +website: ${url} +version: ${version} softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser + usage: /geyser \ No newline at end of file diff --git a/bootstrap/sponge/build.gradle.kts b/bootstrap/sponge/build.gradle.kts new file mode 100644 index 00000000000..3d89e8649a7 --- /dev/null +++ b/bootstrap/sponge/build.gradle.kts @@ -0,0 +1,38 @@ +dependencies { + api(projects.core) +} + +platformRelocate("com.fasterxml.jackson") +platformRelocate("io.netty") +platformRelocate("it.unimi.dsi.fastutil") +platformRelocate("com.google.common") +platformRelocate("com.google.guava") +platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") +platformRelocate("net.kyori.adventure.nbt") + +// These dependencies are already present on the platform +provided(libs.sponge.api) + +application { + mainClass.set("org.geysermc.geyser.platform.sponge.GeyserSpongeMain") +} + +tasks.withType { + archiveBaseName.set("Geyser-Sponge") + + dependencies { + exclude(dependency("com.google.code.gson:.*")) + 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/pom.xml b/bootstrap/sponge/pom.xml deleted file mode 100644 index 25f709ec40d..00000000000 --- a/bootstrap/sponge/pom.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.6-SNAPSHOT - - bootstrap-sponge - - - - org.geysermc - core - 2.0.6-SNAPSHOT - compile - - - org.spongepowered - spongeapi - 7.1.0 - provided - - - - ${outputName}-Sponge - - - src/main/resources/ - true - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - org.geysermc.geyser.platform.sponge.GeyserSpongeMain - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - - - com.fasterxml.jackson - org.geysermc.geyser.platform.sponge.shaded.jackson - - - io.netty - org.geysermc.geyser.platform.sponge.shaded.netty - - - it.unimi.dsi.fastutil - org.geysermc.geyser.platform.sponge.shaded.fastutil - - - com.google.common - org.geysermc.geyser.platform.sponge.shaded.google.common - - - com.google.guava - org.geysermc.geyser.platform.sponge.shaded.google.guava - - - net.kyori - org.geysermc.geyser.platform.sponge.shaded.kyori - - - - - - - - - com.google.code.gson:* - org.yaml:* - org.slf4j:* - org.ow2.asm:* - - - - - - - \ 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 e65684af2f9..628c85fd1ee 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 @@ 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 4ab4e5346f1..2bed78ac9bc 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.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 7c01f18ce1a..f69a3ffb462 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 @@ -25,14 +25,15 @@ package org.geysermc.geyser.platform.sponge; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserPingInfo; -import org.geysermc.geyser.network.MinecraftProtocol; 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 @@ 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 GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { 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(), - MinecraftProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge + 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 f5d6613c771..1f954163161 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,106 +26,189 @@ 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.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; -import org.geysermc.geyser.command.CommandManager; +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.GeyserSpongeCommandManager; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; -import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager; -import org.slf4j.Logger; +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().getAddress().equalsIgnoreCase("auto")) { + 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()); } } if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + 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); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser), "geyser"); } - @Override - public void onDisable() { - geyser.shutdown(); + @Listener + public void onEngineStopping(StoppingEngineEvent event) { + onDisable(); } @Override @@ -139,8 +222,8 @@ public GeyserSpongeLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public GeyserCommandManager getGeyserCommandManager() { + return geyserCommandManager; } @Override @@ -150,17 +233,7 @@ public IGeyserPingPassthrough getGeyserPingPassthrough() { @Override public Path getConfigFolder() { - return configDir.toPath(); - } - - @Listener - public void onServerStart(GameStartedServerEvent event) { - onEnable(); - } - - @Listener - public void onServerStop(GameStoppedEvent event) { - onDisable(); + return configPath; } @Override @@ -170,6 +243,6 @@ public BootstrapDumpInfo getDumpInfo() { @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 825d0bf78c5..a1a0d99ad6a 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,83 +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.command.CommandExecutor; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.command.GeyserCommandExecutor; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; 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.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; -public class GeyserSpongeCommandExecutor extends CommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements org.spongepowered.api.command.Command.Raw { - public GeyserSpongeCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override - public CommandResult process(CommandSource source, String arguments) { - CommandSender commandSender = new SpongeCommandSender(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.getPermission())) { - // 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 SpongeCommandSender(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 dce39870d6b..d83e3a723cd 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.CommandManager; +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; -public class GeyserSpongeCommandManager extends CommandManager { - private final org.spongepowered.api.command.CommandManager handle; +import java.util.Optional; - public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserImpl geyser) { - super(geyser); +public class GeyserSpongeCommandManager extends GeyserCommandManager { - this.handle = handle; + public GeyserSpongeCommandManager(GeyserImpl geyser) { + super(geyser); } @Override - public String getDescription(String command) { - return handle.get(command).map(CommandMapping::getCallable) - .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) - .orElse(Text.EMPTY).toPlain(); + public String description(String command) { + 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/SpongeCommandSender.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java similarity index 68% rename from bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSender.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java index f57f3e276ed..31dccc1fbcc 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSender.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java @@ -26,30 +26,30 @@ package org.geysermc.geyser.platform.sponge.command; import lombok.AllArgsConstructor; - -import org.geysermc.geyser.command.CommandSender; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.command.source.ConsoleSource; -import org.spongepowered.api.text.Text; +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.CommandCause; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; @AllArgsConstructor -public class SpongeCommandSender implements CommandSender { +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 00000000000..2540f87b767 --- /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 00000000000..19e8dca7145 --- /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 new file mode 100644 index 00000000000..9c2194445e5 --- /dev/null +++ b/bootstrap/standalone/build.gradle.kts @@ -0,0 +1,29 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + +val terminalConsoleVersion = "1.2.0" +val jlineVersion = "3.21.0" + +dependencies { + api(projects.core) + + 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(libs.bundles.jline) + + implementation(libs.bundles.log4j) +} + +application { + mainClass.set("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap") +} + +tasks.withType { + archiveBaseName.set("Geyser-Standalone") + + transform(Log4j2PluginsCacheFileTransformer()) +} \ No newline at end of file diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml deleted file mode 100644 index 5d27c8a2a2d..00000000000 --- a/bootstrap/standalone/pom.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.6-SNAPSHOT - - bootstrap-standalone - - - 2.17.1 - - - - - org.geysermc - core - 2.0.6-SNAPSHOT - compile - - - net.minecrell - terminalconsoleappender - 1.2.0 - - - org.apache.logging.log4j - log4j-core - - - org.jline - jline-reader - - - org.jline - jline-terminal-jna - - - org.jline - jline-terminal - - - - - org.jline - jline-terminal - 3.21.0 - - - org.jline - jline-terminal-jna - 3.21.0 - - - org.jline - jline-reader - 3.21.0 - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j18-impl - ${log4j.version} - - - - ${outputName} - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - com.github.edwgiz - maven-shade-plugin.log4j2-cachefile-transformer - 2.8.1 - - - - - package - - shade - - - false - - - - - - - *:* - - META-INF/versions/9/module-info.class - - - - - - org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap - - true - - - - - - - - - - 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 ca41fbd720c..5cbbab9d480 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 @@ -39,18 +39,17 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.geysermc.common.PlatformType; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; 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.util.FileUtils; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; 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; @@ -180,6 +179,7 @@ public void onEnable() { logger.removeAppender(appender); } } + if (useGui && gui == null) { gui = new GeyserStandaloneGUI(); gui.redirectSystemStreams(); @@ -197,7 +197,7 @@ public void onEnable() { handleArgsConfigOptions(); - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); } @@ -216,8 +216,11 @@ public void onEnable() { // 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); + geyser = GeyserImpl.load(PlatformType.STANDALONE, this); + GeyserImpl.start(); + geyserCommandManager = new GeyserCommandManager(geyser); + geyserCommandManager.init(); if (gui != null) { gui.setupInterface(geyserLogger, geyserCommandManager); @@ -262,7 +265,7 @@ public GeyserStandaloneLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return geyserCommandManager; } 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 3bd2a3960c8..e7e24a46564 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 @@ -25,17 +25,17 @@ package org.geysermc.geyser.platform.standalone; -import lombok.extern.log4j.Log4j2; +import lombok.extern.slf4j.Slf4j; import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; -@Log4j2 -public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender { +@Slf4j +public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, GeyserCommandSource { @Override protected boolean isRunning() { @@ -44,7 +44,7 @@ protected boolean isRunning() { @Override protected void runCommand(String line) { - GeyserImpl.getInstance().getCommandManager().runCommand(this, line); + GeyserImpl.getInstance().commandManager().runCommand(this, line); } @Override @@ -54,12 +54,12 @@ protected void shutdown() { @Override public void severe(String message) { - log.fatal(ChatColor.DARK_RED + message); + log.error(ChatColor.DARK_RED + message); } @Override public void severe(String message, Throwable error) { - log.fatal(ChatColor.DARK_RED + message, error); + log.error(ChatColor.DARK_RED + message, error); } @Override @@ -95,24 +95,4 @@ public void setDebug(boolean debug) { 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 44faabdf55d..41cbafb2590 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 @@ -26,11 +26,12 @@ 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.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger; -import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; import javax.swing.*; import javax.swing.table.DefaultTableModel; @@ -259,29 +260,30 @@ public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, Geyser commandsMenu.removeAll(); optionsMenu.removeAll(); - for (Map.Entry command : geyserCommandManager.getCommands().entrySet()) { + for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list - if (!command.getValue().isExecutableOnConsole() || command.getValue().getAliases().contains(command.getKey())) { + if (!entry.getValue().isExecutableOnConsole() || entry.getValue().aliases().contains(entry.getKey())) { continue; } + GeyserCommand command = (GeyserCommand) entry.getValue(); // Create the button that runs the command - boolean hasSubCommands = command.getValue().hasSubCommands(); + boolean hasSubCommands = !entry.getValue().subCommands().isEmpty(); // Add an extra menu if there are more commands that can be run - JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); - commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); + JMenuItem commandButton = hasSubCommands ? new JMenu(entry.getValue().name()) : new JMenuItem(entry.getValue().name()); + commandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description()); if (!hasSubCommands) { - commandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ })); + commandButton.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{ })); } else { // Add a submenu that's the same name as the menu can't be pressed - JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName()); - otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); - otherCommandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ })); + JMenuItem otherCommandButton = new JMenuItem(entry.getValue().name()); + otherCommandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description()); + otherCommandButton.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{ })); commandButton.add(otherCommandButton); // Add a menu option for all possible subcommands - for (String subCommandName : command.getValue().getSubCommands()) { + for (String subCommandName : entry.getValue().subCommands()) { JMenuItem item = new JMenuItem(subCommandName); - item.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{subCommandName})); + item.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{subCommandName})); commandButton.add(item); } } diff --git a/bootstrap/standalone/src/main/resources/log4j2.xml b/bootstrap/standalone/src/main/resources/log4j2.xml index cd101f306d8..0738acdcd96 100644 --- a/bootstrap/standalone/src/main/resources/log4j2.xml +++ b/bootstrap/standalone/src/main/resources/log4j2.xml @@ -16,7 +16,7 @@ - + diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts new file mode 100644 index 00000000000..8908b2afd4a --- /dev/null +++ b/bootstrap/velocity/build.gradle.kts @@ -0,0 +1,67 @@ +dependencies { + annotationProcessor(libs.velocity.api) + api(projects.core) +} + +platformRelocate("com.fasterxml.jackson") +platformRelocate("it.unimi.dsi.fastutil") +platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") + +exclude("com.google.*:*") + +// Needed because Velocity provides every dependency except netty-resolver-dns +exclude("io.netty:netty-transport-native-epoll:*") +exclude("io.netty:netty-transport-native-unix-common:*") +exclude("io.netty:netty-transport-native-kqueue:*") +exclude("io.netty:netty-handler:*") +exclude("io.netty:netty-common:*") +exclude("io.netty:netty-buffer:*") +exclude("io.netty:netty-resolver:*") +exclude("io.netty:netty-transport:*") +exclude("io.netty:netty-codec:*") +exclude("io.netty:netty-codec-haproxy:*") +exclude("org.slf4j:*") +exclude("org.ow2.asm:*") + +// Exclude all Kyori dependencies except the legacy NBT serializer +exclude("net.kyori:adventure-api:*") +exclude("net.kyori:examination-api:*") +exclude("net.kyori:examination-string:*") +exclude("net.kyori:adventure-text-serializer-gson:*") +exclude("net.kyori:adventure-text-serializer-legacy:*") +exclude("net.kyori:adventure-nbt:*") + +// These dependencies are already present on the platform +provided(libs.velocity.api) + +application { + mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain") +} + +tasks.withType { + archiveBaseName.set("Geyser-Velocity") + + dependencies { + exclude(dependency("com.google.*:.*")) + // Needed because Velocity provides every dependency except netty-resolver-dns + exclude(dependency("io.netty:netty-transport-native-epoll:.*")) + exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) + exclude(dependency("io.netty:netty-transport-native-kqueue:.*")) + exclude(dependency("io.netty:netty-handler:.*")) + exclude(dependency("io.netty:netty-common:.*")) + exclude(dependency("io.netty:netty-buffer:.*")) + exclude(dependency("io.netty:netty-resolver:.*")) + exclude(dependency("io.netty:netty-transport:.*")) + exclude(dependency("io.netty:netty-codec:.*")) + exclude(dependency("io.netty:netty-codec-haproxy:.*")) + exclude(dependency("org.slf4j:.*")) + exclude(dependency("org.ow2.asm:.*")) + // Exclude all Kyori dependencies except the legacy NBT serializer + 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-nbt:.*")) + } +} \ No newline at end of file diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml deleted file mode 100644 index 0c530b21e03..00000000000 --- a/bootstrap/velocity/pom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.6-SNAPSHOT - - bootstrap-velocity - - - - org.geysermc - core - 2.0.6-SNAPSHOT - compile - - - com.velocitypowered - velocity-api - 3.0.0 - provided - - - - ${outputName}-Velocity - - - src/main/resources/ - true - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - org.geysermc.geyser.platform.velocity.GeyserVelocityMain - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - - - com.fasterxml.jackson - org.geysermc.geyser.platform.velocity.shaded.jackson - - - it.unimi.dsi.fastutil - org.geysermc.geyser.platform.velocity.shaded.fastutil - - - net.kyori.adventure.text.serializer.gson.legacyimpl - org.geysermc.geyser.platform.velocity.shaded.kyori.legacyimpl - - - - - - - - - com.google.*:* - - io.netty:netty-transport-native-epoll:* - io.netty:netty-transport-native-unix-common:* - io.netty:netty-transport-native-kqueue:* - io.netty:netty-handler:* - io.netty:netty-common:* - io.netty:netty-buffer:* - io.netty:netty-resolver:* - io.netty:netty-transport:* - io.netty:netty-codec:* - io.netty:netty-codec-haproxy:* - org.slf4j:* - org.ow2.asm:* - - net.kyori:adventure-api:* - net.kyori:examination-api:* - net.kyori:examination-string:* - net.kyori:adventure-text-serializer-gson:* - net.kyori:adventure-text-serializer-legacy:* - net.kyori:adventure-nbt:* - - - - - - - \ No newline at end of file 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 ffc7db2915b..45eb7abb9a5 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 @@ -28,8 +28,8 @@ import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; -import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; import java.util.ArrayList; import java.util.List; @@ -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 4a8a50da8af..5ac09416c15 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 @@ -39,13 +39,15 @@ 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; 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.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.Nullable; @@ -57,6 +59,7 @@ 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; @@ -111,7 +114,7 @@ public void onEnable() { InetSocketAddress javaAddr = proxyServer.getBoundAddress(); // By default this should be localhost but may need to be changed in some circumstances - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); // Don't use localhost if not listening on all interfaces if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { @@ -127,6 +130,8 @@ public void onEnable() { 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 @@ -137,7 +142,7 @@ public void onEnable() { } catch (ClassNotFoundException ignored) { } - if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { + if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; @@ -149,18 +154,34 @@ public void onEnable() { 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.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser)); + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); + + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); + } + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } + + proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override @@ -184,7 +205,7 @@ public GeyserVelocityLogger getGeyserLogger() { } @Override - public org.geysermc.geyser.command.CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } @@ -205,9 +226,14 @@ public void onShutdown(ProxyShutdownEvent event) { @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 00000000000..31e5846128b --- /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 30f6c2efdf9..c89c35b0648 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 @@ -27,40 +27,45 @@ import com.velocitypowered.api.command.SimpleCommand; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandExecutor; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.command.GeyserCommandExecutor; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; -public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand { +public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand { - public GeyserVelocityCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override public void execute(Invocation invocation) { - CommandSender sender = new VelocityCommandSender(invocation.source()); + GeyserCommandSource sender = new VelocityCommandSource(invocation.source()); GeyserSession session = getGeyserSession(sender); if (invocation.arguments().length > 0) { GeyserCommand command = getCommand(invocation.arguments()[0]); if (command != null) { - if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); + if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).permission())) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return; } if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); return; } command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale()); + sender.sendMessage(ChatColor.RED + message); } } else { getCommand("help").execute(session, sender, new String[0]); @@ -71,7 +76,7 @@ public void execute(Invocation invocation) { public List suggest(Invocation invocation) { // Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot. if (invocation.arguments().length == 0 || invocation.arguments().length == 1) { - return tabComplete(new VelocityCommandSender(invocation.source())); + return tabComplete(new VelocityCommandSource(invocation.source())); } return Collections.emptyList(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java similarity index 84% rename from bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index d5e4804eebc..00c99e92b51 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -28,20 +28,21 @@ 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.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; -public class VelocityCommandSender implements CommandSender { +public class VelocityCommandSource implements GeyserCommandSource { private final CommandSource handle; - public VelocityCommandSender(CommandSource handle) { + public VelocityCommandSource(CommandSource handle) { this.handle = handle; // Ensure even Java players' languages are loaded - GeyserLocale.loadGeyserLocale(getLocale()); + GeyserLocale.loadGeyserLocale(this.locale()); } @Override @@ -59,13 +60,19 @@ public void sendMessage(String message) { 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; } @Override - public String getLocale() { + public String locale() { if (handle instanceof Player) { Locale locale = ((Player) handle).getPlayerSettings().getLocale(); return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000000..e21806660f7 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,26 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +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("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" + } +} diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt new file mode 100644 index 00000000000..0c01913d248 --- /dev/null +++ b/build-logic/src/main/kotlin/extensions.kt @@ -0,0 +1,76 @@ +/* + * 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 + */ + +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 = + version.toString().endsWith("-SNAPSHOT") + +fun Project.relocate(pattern: String) { + tasks.named("shadowJar") { + relocate(pattern, "org.geysermc.geyser.shaded.$pattern") + } +} + +fun Project.exclude(group: String) { + tasks.named("shadowJar") { + exclude(group) + } +} + +fun Project.platformRelocate(pattern: String, exclusion: String = "") { + tasks.named("shadowJar") { + relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") { + exclude(exclusion) + } + } +} + +val providedDependencies = mutableMapOf>() + +fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) { + providedDependencies.getOrPut(project.name) { mutableSetOf() } + .add("${calcExclusion(pattern, 0b100, excludedOn)}:" + + "${calcExclusion(name, 0b10, excludedOn)}:" + + calcExclusion(version, 0b1, excludedOn)) + dependencies.add("compileOnlyApi", "$pattern:$name:$version") +} + +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.api-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts new file mode 100644 index 00000000000..7c8f9a3d7b7 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("geyser.publish-conventions") +} + +tasks { + shadowJar { + archiveBaseName.set(archiveBaseName.get() + "-api") + } +} \ 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 new file mode 100644 index 00000000000..44a74db3dc8 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -0,0 +1,34 @@ +plugins { + `java-library` + `maven-publish` +} + +dependencies { + compileOnly("org.checkerframework", "checker-qual", "3.19.0") +} + +tasks { + processResources { + // 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", + "name" to "Geyser", + "version" to project.version, + "description" to project.description, + "url" to "https://geysermc.org", + "author" to "GeyserMC" + ) + } + } + compileJava { + options.encoding = Charsets.UTF_8.name() + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_16 + targetCompatibility = JavaVersion.VERSION_16 + + withSourcesJar() +} diff --git a/build-logic/src/main/kotlin/geyser.build-logic.gradle.kts b/build-logic/src/main/kotlin/geyser.build-logic.gradle.kts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts new file mode 100644 index 00000000000..81d22490655 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts @@ -0,0 +1,4 @@ +plugins { + application + id("geyser.publish-conventions") +} \ 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 new file mode 100644 index 00000000000..7525f97fab0 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("geyser.shadow-conventions") + id("com.jfrog.artifactory") + id("maven-publish") +} + +publishing { + publications { + create("mavenJava") { + groupId = project.group as String + artifactId = project.name + version = project.version as String + + from(components["java"]) + } + } +} + +artifactory { + setContextUrl("https://repo.opencollab.dev/artifactory") + publish { + repository { + setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases") + setMavenCompatible(true) + } + defaults { + publications("mavenJava") + setPublishArtifacts(true) + setPublishPom(true) + setPublishIvy(false) + } + } +} diff --git a/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts new file mode 100644 index 00000000000..395beb10482 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts @@ -0,0 +1,32 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("geyser.base-conventions") + id("com.github.johnrengelman.shadow") +} + +tasks { + named("jar") { + archiveClassifier.set("unshaded") + from(project.rootProject.file("LICENSE")) + } + val shadowJar = named("shadowJar") { + archiveBaseName.set(project.name) + archiveVersion.set("") + archiveClassifier.set("") + + val sJar: ShadowJar = this + + doFirst { + providedDependencies[project.name]?.forEach { string -> + sJar.dependencies { + println("Excluding $string from ${project.name}") + exclude(dependency(string)) + } + } + } + } + named("build") { + dependsOn(shadowJar) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..06c2e987b00 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + `java-library` + id("geyser.build-logic") + id("io.freefair.lombok") version "6.3.0" apply false +} + +allprojects { + group = "org.geysermc" + version = "2.1.0-SNAPSHOT" + description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers." + + tasks.withType { + options.encoding = "UTF-8" + } +} + +val platforms = setOf( + projects.fabric, + projects.bungeecord, + projects.spigot, + projects.sponge, + projects.standalone, + projects.velocity +).map { it.dependencyProject } + +val api: Project = projects.api.dependencyProject + +subprojects { + apply { + plugin("java-library") + plugin("io.freefair.lombok") + plugin("geyser.build-logic") + } + + val relativePath = projectDir.relativeTo(rootProject.projectDir).path + + if (relativePath.contains("api")) { + plugins.apply("geyser.api-conventions") + } else { + group = rootProject.group as String + ".geyser" + when (this) { + in platforms -> plugins.apply("geyser.platform-conventions") + api -> plugins.apply("geyser.publish-conventions") + else -> plugins.apply("geyser.base-conventions") + } + } +} \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 00000000000..db3fe3a7742 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("geyser.publish-conventions") +} + +dependencies { + api(libs.cumulus) + api(libs.gson) +} diff --git a/common/pom.xml b/common/pom.xml deleted file mode 100644 index 5326ca01409..00000000000 --- a/common/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - - common - - - - 8 - 8 - - - - - org.geysermc.cumulus - cumulus - 1.1 - - - com.google.code.gson - gson - 2.8.9 - - - \ No newline at end of file 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 f06c0f9daf9..58281dec8b8 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 ef7709859bc..40620475916 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 new file mode 100644 index 00000000000..994325ea038 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,131 @@ +import net.kyori.blossom.BlossomExtension + +plugins { + id("net.kyori.blossom") + id("net.kyori.indra.git") + id("geyser.publish-conventions") +} + +dependencies { + api(projects.geyserApi) + api(projects.common) + + // Jackson JSON and YAML serialization + api(libs.bundles.jackson) + api(libs.guava) + + // Fastutil Maps + implementation(libs.bundles.fastutil) + + // Network libraries + implementation(libs.websocket) + + api(libs.protocol) { + exclude("com.nukkitx.network", "raknet") + } + + api(libs.mcauthlib) + api(libs.mcprotocollib) { + exclude("io.netty", "netty-all") + exclude("com.github.GeyserMC", "packetlib") + exclude("com.github.GeyserMC", "mcauthlib") + } + + api(libs.packetlib) { + exclude("io.netty", "netty-all") + } + + implementation(libs.raknet) { + exclude("io.netty", "*"); + } + + 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(libs.netty.handler) + + 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 + api(libs.bundles.adventure) + + // Test + testImplementation(libs.junit) + + // Annotation Processors + compileOnly(projects.ap) + + annotationProcessor(projects.ap) +} + +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") +} + +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 = + 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/pom.xml b/core/pom.xml deleted file mode 100644 index 8977854cb7f..00000000000 --- a/core/pom.xml +++ /dev/null @@ -1,394 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - - core - - - 4.12.0-20220629.025215-9 - 8.5.2 - 2.13.2 - 4.1.66.Final - - - - - - sonatype-s01 - https://s01.oss.sonatype.org/content/repositories/snapshots/ - - - - - - org.geysermc - ap - 2.0.6-SNAPSHOT - provided - - - org.geysermc - geyser-api - 2.0.6-SNAPSHOT - compile - - - org.geysermc - common - 2.0.6-SNAPSHOT - compile - - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version}.1 - compile - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - compile - - - com.google.guava - guava - 29.0-jre - compile - - - - com.nukkitx - nbt - - 2.2.1 - compile - - - com.nukkitx.fastutil - fastutil-int-int-maps - ${fastutil.version} - compile - - - com.nukkitx.fastutil - fastutil-int-long-maps - ${fastutil.version} - compile - - - com.nukkitx.fastutil - fastutil-int-byte-maps - ${fastutil.version} - compile - - - com.nukkitx.fastutil - fastutil-int-boolean-maps - ${fastutil.version} - compile - - - com.nukkitx.fastutil - fastutil-object-int-maps - ${fastutil.version} - compile - - - com.nukkitx.fastutil - fastutil-object-object-maps - ${fastutil.version} - compile - - - - org.java-websocket - Java-WebSocket - 1.5.1 - compile - - - com.github.CloudburstMC.Protocol - bedrock-v534 - a78a64b - compile - - - com.nukkitx.network - raknet - - - com.nukkitx - nbt - - - - - com.nukkitx.network - raknet - 1.6.28-20220125.214016-6 - compile - - - io.netty - * - - - - - com.github.GeyserMC - MCAuthLib - d9d773e - compile - - - com.github.GeyserMC - MCProtocolLib - 9f78bd5 - compile - - - com.github.GeyserMC - packetlib - - - com.github.GeyserMC - mcauthlib - - - - net.kyori - * - - - - - com.github.steveice10 - packetlib - 3.0 - compile - - - io.netty - netty-all - - - - io.netty.incubator - netty-incubator-transport-native-io_uring - - - - - io.netty - netty-resolver-dns - ${netty.version} - compile - - - io.netty - netty-resolver-dns-native-macos - ${netty.version} - compile - osx-x86_64 - - - io.netty - netty-codec-haproxy - ${netty.version} - compile - - - - io.netty - netty-handler - ${netty.version} - compile - - - io.netty - netty-transport-native-epoll - ${netty.version} - compile - linux-x86_64 - - - io.netty - netty-transport-native-epoll - ${netty.version} - compile - linux-aarch_64 - - - io.netty - netty-transport-native-kqueue - ${netty.version} - compile - osx-x86_64 - - - - net.kyori - adventure-text-serializer-legacy - ${adventure.version} - compile - - - net.kyori - adventure-text-serializer-plain - ${adventure.version} - compile - - - - net.kyori - adventure-text-serializer-gson - ${adventure.version} - compile - - - net.kyori - adventure-text-serializer-gson-legacy-impl - ${adventure.version} - compile - - - - junit - junit - 4.13.1 - test - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - **/services/javax.annotation.processing.Processor - - - - - pl.project13.maven - git-commit-id-plugin - 4.0.0 - - - get-the-git-infos - - revision - - - - - true - ${project.build.outputDirectory}/git.properties - properties - false - false - false - true - false - - git.user.* - git.*.user.* - git.closest.* - git.commit.id.describe - git.commit.id.describe-short - git.commit.message.short - - flat - - true - - - - - com.google.code.maven-replacer-plugin - replacer - 1.5.3 - - - add-version - process-sources - - replace - - - - ${project.basedir}/src/main/java/org/geysermc/geyser/GeyserImpl.java - - - - String VERSION = ".*" - String VERSION = "${project.version} (" + GIT_VERSION + ")" - - - String GIT_VERSION = ".*" - - String GIT_VERSION = "git-${git.branch}-${git.commit.id.abbrev}" - - - - - - - remove-version - process-classes - - replace - - - - ${project.basedir}/src/main/java/org/geysermc/geyser/GeyserImpl.java - - - - String VERSION = ".*" - String VERSION = "DEV" - - - String GIT_VERSION = ".*" - String GIT_VERSION = "DEV" - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.0 - - - -Dfile.encoding=${project.build.sourceEncoding} - - - - - diff --git a/core/src/main/java/org/geysermc/connector/GeyserConnector.java b/core/src/main/java/org/geysermc/connector/GeyserConnector.java index b3307a1342d..bd14ebb2553 100644 --- a/core/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/core/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -26,10 +26,10 @@ package org.geysermc.connector; import com.nukkitx.protocol.bedrock.BedrockServer; +import org.geysermc.api.Geyser; import org.geysermc.common.PlatformType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.api.Geyser; import java.util.UUID; @@ -91,6 +91,6 @@ public GeyserSession getPlayerByUuid(UUID uuid) { } public boolean isProductionEnvironment() { - return GeyserImpl.getInstance().productionEnvironment(); + return GeyserImpl.getInstance().isProductionEnvironment(); } } 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 890290a01e9..258787e78da 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 @@ -56,11 +56,11 @@ public boolean isClosed() { } public String getRemoteAddress() { - return this.handle.getRemoteAddress(); + return this.handle.remoteServer().address(); } public int getRemotePort() { - return this.handle.getRemotePort(); + return this.handle.remoteServer().port(); } public int getRenderDistance() { @@ -128,7 +128,7 @@ public void executeInEventLoop(Runnable runnable) { } public String getName() { - return this.handle.name(); + return this.handle.bedrockUsername(); } public boolean isConsole() { @@ -136,7 +136,7 @@ public boolean isConsole() { } public String getLocale() { - return this.handle.getLocale(); + return this.handle.locale(); } public void sendUpstreamPacket(BedrockPacket packet) { diff --git a/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index 6b8e53d8e11..cca7aa48c43 100644 --- a/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -33,6 +33,7 @@ * * @deprecated legacy code */ +@Deprecated public class AuthData { private final org.geysermc.geyser.session.auth.AuthData handle; diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 23fb76d1653..6a53c37de86 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"; static { diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java index 0aa1d39c3fb..8b51228c845 100644 --- a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; import java.nio.file.Files; @@ -34,7 +34,7 @@ public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { - if (config.getRemote().getAuthType() != AuthType.FLOODGATE) { + if (config.getRemote().authType() != AuthType.FLOODGATE) { return geyserDataFolder.resolve(config.getFloodgateKeyFile()); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index d4006031035..261c7416bfc 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -25,7 +25,7 @@ package org.geysermc.geyser; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; @@ -72,7 +72,7 @@ public interface GeyserBootstrap { * * @return The current CommandManager */ - CommandManager getGeyserCommandManager(); + GeyserCommandManager getGeyserCommandManager(); /** * Returns the current PingPassthrough manager diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 4322dde5916..a10e54f9049 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -41,19 +41,34 @@ 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; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; -import org.geysermc.geyser.command.CommandManager; +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; +import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.BedrockListener; +import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.event.GeyserEventBus; +import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.ConnectorServerEventHandler; import org.geysermc.geyser.pack.ResourcePack; @@ -63,10 +78,8 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; -import org.geysermc.geyser.session.auth.AuthType; 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; @@ -76,7 +89,6 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -99,8 +111,13 @@ public class GeyserImpl implements GeyserApi { .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); public static final String NAME = "Geyser"; - public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs - public static final String VERSION = "DEV"; // A fallback for running in IDEs + public static final String GIT_VERSION = "${gitVersion}"; // A fallback for running in IDEs + public static final String VERSION = "${version}"; // A fallback for running in IDEs + + 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 @@ -129,6 +146,9 @@ public class GeyserImpl implements GeyserApi { private final PlatformType platformType; private final GeyserBootstrap bootstrap; + private final EventBus eventBus; + private final GeyserExtensionManager extensionManager; + private Metrics metrics; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; @@ -145,9 +165,20 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { this.platformType = platformType; this.bootstrap = bootstrap; + GeyserLocale.finalizeDefaultLocale(this); + + /* Initialize event bus */ + this.eventBus = new GeyserEventBus(); + + /* Load Extensions */ + this.extensionManager = new GeyserExtensionManager(); + this.extensionManager.init(); + this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); + } + + public void initialize() { long startupTime = System.currentTimeMillis(); - GeyserLocale.finalizeDefaultLocale(this); GeyserLogger logger = bootstrap.getGeyserLogger(); logger.info("******************************************"); @@ -156,16 +187,17 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info(""); logger.info("******************************************"); - /* Initialize translators and registries */ - BlockRegistries.init(); + /* Initialize registries */ Registries.init(); + BlockRegistries.init(); + /* Initialize translators */ EntityDefinitions.init(); ItemTranslator.init(); MessageTranslator.init(); MinecraftLocale.init(); - start(); + startInstance(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -192,12 +224,12 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { if (platformType == PlatformType.STANDALONE) { logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); - } else if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { + } else if (config.getRemote().authType() == AuthType.FLOODGATE) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } } - private void start() { + private void startInstance() { this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); GeyserLogger logger = bootstrap.getGeyserLogger(); @@ -209,7 +241,7 @@ private void start() { ResourcePack.loadPacks(); - if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { + if (platformType != PlatformType.STANDALONE && config.getRemote().address().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress()); @@ -221,7 +253,7 @@ private void start() { config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress()); } } - String remoteAddress = config.getRemote().getAddress(); + 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")) { String[] record = WebUtils.findSrvRecord(this, remoteAddress); @@ -236,27 +268,9 @@ private void start() { // Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false; - String branch = "unknown"; - int buildNumber = -1; - if (this.productionEnvironment()) { - try (InputStream stream = bootstrap.getResource("git.properties")) { - Properties gitProperties = new Properties(); - gitProperties.load(stream); - branch = gitProperties.getProperty("git.branch"); - String build = gitProperties.getProperty("git.build.number"); - if (build != null) { - buildNumber = Integer.parseInt(build); - } - } catch (Throwable e) { - logger.error("Failed to read git.properties", e); - } - } else { - logger.debug("Not getting git properties for the news handler as we are in a development environment."); - } - pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); - this.newsHandler = new NewsHandler(branch, buildNumber); + this.newsHandler = new NewsHandler(BRANCH, this.buildNumber()); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether @@ -273,7 +287,7 @@ private void start() { boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol(); bedrockServer = new BedrockServer( - new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()), + new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port()), bedrockThreadCount, EventLoops.commonGroup(), enableProxyProtocol @@ -296,21 +310,21 @@ private void start() { if (shouldStartListener) { bedrockServer.bind().whenComplete((avoid, throwable) -> { if (throwable == null) { - logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), - String.valueOf(config.getBedrock().getPort()))); + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().address(), + String.valueOf(config.getBedrock().port()))); } else { - String address = config.getBedrock().getAddress(); - int port = config.getBedrock().getPort(); + String address = config.getBedrock().address(); + int port = config.getBedrock().port(); logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port))); if (!"0.0.0.0".equals(address)) { - logger.info(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(); } - if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { + if (config.getRemote().authType() == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); @@ -328,7 +342,7 @@ private void start() { metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size)); // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase(Locale.ROOT))); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT))); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); @@ -412,7 +426,7 @@ private void start() { metrics = null; } - if (config.getRemote().getAuthType() == AuthType.ONLINE) { + if (config.getRemote().authType() == AuthType.ONLINE) { if (config.getUserAuths() != null && !config.getUserAuths().isEmpty()) { getLogger().warning("The 'userAuths' config section is now deprecated, and will be removed in the near future! " + "Please migrate to the new 'saved-user-logins' config option: " + @@ -454,22 +468,26 @@ private void start() { } newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); + + this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); + if (config.isNotifyOnNewBedrockUpdate()) { + VersionCheckUtils.checkForGeyserUpdate(this::getLogger); + } } @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; - } - } + public @NonNull List onlineConnections() { + return sessionManager.getAllSessions(); + } - return null; + @Override + public int onlineConnectionsCount() { + return sessionManager.size(); } @Override - public @NonNull List onlineConnections() { - return this.sessionManager.getAllSessions(); + public @MonotonicNonNull String usernamePrefix() { + return null; } @Override @@ -479,16 +497,40 @@ private void start() { @Override public @Nullable GeyserSession connectionByXuid(@NonNull String xuid) { - for (GeyserSession session : sessionManager.getAllSessions()) { - if (session.xuid().equals(xuid)) { - return session; - } + return sessionManager.sessionByXuid(xuid); + } + + @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); + } - return null; + @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() { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown")); shuttingDown = true; @@ -505,16 +547,19 @@ public void shutdown() { skinUploader.close(); } newsHandler.shutdown(); - this.getCommandManager().getCommands().clear(); + this.commandManager().getCommands().clear(); ResourcePack.PACKS.clear(); + this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); + this.extensionManager.disableExtensions(); + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } - @Override public void reload() { shutdown(); + this.extensionManager.enableExtensions(); bootstrap.onEnable(); } @@ -524,24 +569,73 @@ public void reload() { * * @return true if the version number is not 'DEV'. */ + public boolean isProductionEnvironment() { + // First is if Blossom runs, second is if Blossom doesn't run + // noinspection ConstantConditions - changes in production + return !("git-local/dev-0000000".equals(GeyserImpl.GIT_VERSION) || "${gitVersion}".equals(GeyserImpl.GIT_VERSION)); + } + + @Override + @NonNull + public GeyserExtensionManager extensionManager() { + return this.extensionManager; + } + + @NonNull + public GeyserCommandManager commandManager() { + return this.bootstrap.getGeyserCommandManager(); + } + @Override - public boolean productionEnvironment() { - //noinspection ConstantConditions - changes in production - return !"DEV".equals(GeyserImpl.VERSION); + public @NonNull R provider(@NonNull Class apiClass, @Nullable Object... args) { + return (R) Registries.PROVIDERS.get(apiClass).create(args); } - public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { + @Override + @NonNull + public EventBus eventBus() { + return this.eventBus; + } + + @NonNull + public RemoteServer defaultRemoteServer() { + return getConfig().getRemote(); + } + + @Override + @NonNull + public BedrockListener bedrockListener() { + return getConfig().getBedrock(); + } + + public int buildNumber() { + if (!this.isProductionEnvironment()) { + return 0; + } + + return Integer.parseInt(BUILD_NUMBER); + } + + 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() { @@ -552,10 +646,6 @@ public GeyserConfiguration getConfig() { return bootstrap.getGeyserConfig(); } - public CommandManager getCommandManager() { - return bootstrap.getGeyserCommandManager(); - } - public WorldManager getWorldManager() { return bootstrap.getWorldManager(); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index b47801cb5e0..88220eec9d7 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 @@ default void debug(@Nullable Object object) { * 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/CommandManager.java b/core/src/main/java/org/geysermc/geyser/command/CommandManager.java deleted file mode 100644 index 38a86fdd0fb..00000000000 --- a/core/src/main/java/org/geysermc/geyser/command/CommandManager.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.command; - -import lombok.Getter; - -import org.geysermc.common.PlatformType; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.defaults.*; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.*; - -public abstract class CommandManager { - - @Getter - private final Map commands = new HashMap<>(); - - private final GeyserImpl geyser; - - public CommandManager(GeyserImpl geyser) { - this.geyser = geyser; - - registerCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help")); - registerCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); - registerCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); - registerCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); - registerCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); - registerCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); - registerCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); - registerCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); - registerCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); - registerCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest")); - if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { - registerCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); - } - } - - public void registerCommand(GeyserCommand command) { - commands.put(command.getName(), command); - geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.getName())); - - if (command.getAliases().isEmpty()) - return; - - for (String alias : command.getAliases()) - commands.put(alias, command); - } - - public void runCommand(CommandSender sender, String command) { - if (!command.startsWith("geyser ")) - return; - - command = command.trim().replace("geyser ", ""); - String label; - String[] args; - - if (!command.contains(" ")) { - label = command.toLowerCase(); - args = new String[0]; - } else { - label = command.substring(0, command.indexOf(" ")).toLowerCase(); - String argLine = command.substring(command.indexOf(" ") + 1); - args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; - } - - GeyserCommand cmd = commands.get(label); - if (cmd == null) { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); - return; - } - - if (sender instanceof GeyserSession) { - cmd.execute((GeyserSession) sender, sender, args); - } else { - if (!cmd.isBedrockOnly()) { - cmd.execute(null, sender, args); - } else { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); - } - } - } - - /** - * @return a list of all subcommands under {@code /geyser}. - */ - public List getCommandNames() { - return Arrays.asList(geyser.getCommandManager().getCommands().keySet().toArray(new String[0])); - } - - /** - * Returns the description of the given command - * - * @param command Command to get the description for - * @return Command description - */ - public abstract String getDescription(String command); -} 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 a22c69c0471..5808dbc2ce5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -27,17 +27,19 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; +import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.NonNull; +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; +@Accessors(fluent = true) @Getter @RequiredArgsConstructor -public abstract class GeyserCommand { +public abstract class GeyserCommand implements Command { protected final String name; /** @@ -46,16 +48,16 @@ public abstract class GeyserCommand { protected final String description; protected final String permission; - @Setter - private List aliases = new ArrayList<>(); + private List aliases = Collections.emptyList(); - public abstract void execute(@Nullable GeyserSession session, CommandSender sender, String[] args); + public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args); /** * If false, hides the command from being shown on the Geyser Standalone GUI. * * @return true if the command can be run on the server console */ + @Override public boolean isExecutableOnConsole() { return true; } @@ -65,26 +67,23 @@ public boolean isExecutableOnConsole() { * * @return a list of all possible subcommands, or empty if none. */ - public List getSubCommands() { + @NonNull + @Override + public List subCommands() { return Collections.emptyList(); } /** - * Shortcut to {@link #getSubCommands()}{@code .isEmpty()}. + * Shortcut to {@link #subCommands()} ()}{@code .isEmpty()}. * * @return true if there are subcommand present for this command. */ public boolean hasSubCommands() { - return !getSubCommands().isEmpty(); + return !this.subCommands().isEmpty(); } - /** - * Used to send a deny message to Java players if this command can only be used by Bedrock players. - * - * @return true if this command can only be used by Bedrock players. - */ - public boolean isBedrockOnly() { - return false; + public void setAliases(List aliases) { + this.aliases = aliases; } /** @@ -92,6 +91,7 @@ public boolean isBedrockOnly() { * * @return if this command is designated to be used only by server operators. */ + @Override public boolean isSuggestedOpOnly() { return false; } diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java similarity index 85% rename from core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java rename to core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java index 5fa5f688bbf..a9b1c734fab 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java @@ -27,6 +27,7 @@ import lombok.AllArgsConstructor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.session.GeyserSession; import javax.annotation.Nullable; @@ -36,19 +37,20 @@ import java.util.Map; /** - * Represents helper functions for listening to {@code /geyser} commands. + * Represents helper functions for listening to {@code /geyser} or {@code /geyserext} commands. */ @AllArgsConstructor -public class CommandExecutor { +public class GeyserCommandExecutor { protected final GeyserImpl geyser; + private final Map commands; public GeyserCommand getCommand(String label) { - return geyser.getCommandManager().getCommands().get(label); + return (GeyserCommand) commands.get(label); } @Nullable - public GeyserSession getGeyserSession(CommandSender sender) { + public GeyserSession getGeyserSession(GeyserCommandSource sender) { if (sender.isConsole()) { return null; } @@ -70,20 +72,18 @@ public GeyserSession getGeyserSession(CommandSender sender) { * If the command sender does not have the permission for a given command, the command will not be shown. * @return A list of command names to include in the tab complete */ - public List tabComplete(CommandSender sender) { + public List tabComplete(GeyserCommandSource sender) { if (getGeyserSession(sender) != null) { // Bedrock doesn't get tab completions or argument suggestions return Collections.emptyList(); } List availableCommands = new ArrayList<>(); - Map commands = geyser.getCommandManager().getCommands(); // Only show commands they have permission to use - for (Map.Entry entry : commands.entrySet()) { - GeyserCommand geyserCommand = entry.getValue(); - if (sender.hasPermission(geyserCommand.getPermission())) { - + for (Map.Entry entry : commands.entrySet()) { + Command geyserCommand = entry.getValue(); + if (sender.hasPermission(geyserCommand.permission())) { if (geyserCommand.isBedrockOnly()) { // Don't show commands the JE player can't run continue; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java new file mode 100644 index 00000000000..d28f9d24edc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.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.CommandSource; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; +import org.geysermc.geyser.command.defaults.AdvancementsCommand; +import org.geysermc.geyser.command.defaults.ConnectionTestCommand; +import org.geysermc.geyser.command.defaults.DumpCommand; +import org.geysermc.geyser.command.defaults.ExtensionsCommand; +import org.geysermc.geyser.command.defaults.HelpCommand; +import org.geysermc.geyser.command.defaults.ListCommand; +import org.geysermc.geyser.command.defaults.OffhandCommand; +import org.geysermc.geyser.command.defaults.ReloadCommand; +import org.geysermc.geyser.command.defaults.SettingsCommand; +import org.geysermc.geyser.command.defaults.StatisticsCommand; +import org.geysermc.geyser.command.defaults.StopCommand; +import org.geysermc.geyser.command.defaults.VersionCommand; +import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; +import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import 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 class GeyserCommandManager { + + @Getter + private final Map commands = new Object2ObjectOpenHashMap<>(12); + private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); + + private final GeyserImpl geyser; + + public void init() { + registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); + registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerBuiltInCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); + registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); + registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); + registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest")); + if (this.geyser.getPlatformType() == PlatformType.STANDALONE) { + registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + } + + if (this.geyser.extensionManager().extensions().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())); + } + } + + /** + * For internal Geyser commands + */ + public void registerBuiltInCommand(GeyserCommand command) { + register(command, this.commands); + } + + public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) { + register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); + } + + private void register(Command command, Map commands) { + commands.put(command.name(), command); + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); + + if (command.aliases().isEmpty()) { + return; + } + + for (String alias : command.aliases()) { + commands.put(alias, command); + } + } + + @NotNull + public Map commands() { + return Collections.unmodifiableMap(this.commands); + } + + @NotNull + public Map> extensionCommands() { + return Collections.unmodifiableMap(this.extensionCommands); + } + + public boolean runCommand(GeyserCommandSource sender, String command) { + Extension extension = null; + for (Extension loopedExtension : this.extensionCommands.keySet()) { + if (command.startsWith(loopedExtension.description().id() + " ")) { + extension = loopedExtension; + break; + } + } + + if (!command.startsWith("geyser ") && extension == null) { + return false; + } + + command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", ""); + String label; + String[] args; + + if (!command.contains(" ")) { + label = command.toLowerCase(Locale.ROOT); + args = new String[0]; + } else { + label = command.substring(0, command.indexOf(" ")).toLowerCase(Locale.ROOT); + String argLine = command.substring(command.indexOf(" ") + 1); + args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; + } + + Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label); + if (cmd == null) { + sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); + return false; + } + + if (cmd instanceof GeyserCommand) { + if (sender instanceof GeyserSession) { + ((GeyserCommand) cmd).execute((GeyserSession) sender, sender, args); + } else { + if (!cmd.isBedrockOnly()) { + ((GeyserCommand) cmd).execute(null, sender, args); + } else { + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); + } + } + } + + return true; + } + + /** + * Returns the description of the given command + * + * @param command Command to get the description for + * @return Command description + */ + public String description(String command) { + return ""; + } + + @RequiredArgsConstructor + public static class CommandBuilder implements Command.Builder { + private final Extension extension; + private Class sourceType; + private String name; + private String description = ""; + private String permission = ""; + private List aliases; + private boolean suggestedOpOnly = false; + private boolean executableOnConsole = true; + private List subCommands; + private boolean bedrockOnly; + private CommandExecutor executor; + + @Override + public Command.Builder source(@NonNull Class sourceType) { + this.sourceType = sourceType; + return this; + } + + public CommandBuilder name(@NonNull String name) { + this.name = name; + return this; + } + + public CommandBuilder description(@NonNull String description) { + this.description = description; + return this; + } + + public CommandBuilder permission(@NonNull String permission) { + this.permission = permission; + return this; + } + + public CommandBuilder aliases(@NonNull List aliases) { + this.aliases = aliases; + return this; + } + + @Override + public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { + this.suggestedOpOnly = suggestedOpOnly; + return this; + } + + public CommandBuilder executableOnConsole(boolean executableOnConsole) { + this.executableOnConsole = executableOnConsole; + return this; + } + + public CommandBuilder subCommands(@NonNull List subCommands) { + this.subCommands = subCommands; + return this; + } + + public CommandBuilder bedrockOnly(boolean bedrockOnly) { + this.bedrockOnly = bedrockOnly; + return this; + } + + public CommandBuilder executor(@NonNull CommandExecutor executor) { + this.executor = executor; + return this; + } + + @NonNull + public GeyserExtensionCommand build() { + if (this.name == null || this.name.isBlank()) { + throw new IllegalArgumentException("Command cannot be null or blank!"); + } + + if (this.sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); + } + + return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { + + @SuppressWarnings("unchecked") + @Override + public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { + Class sourceType = CommandBuilder.this.sourceType; + CommandExecutor executor = CommandBuilder.this.executor; + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } + + if (sourceType.isInstance(sender)) { + executor.execute((T) sender, this, args); + return; + } + + GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); + } + + @NonNull + @Override + public List aliases() { + return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases; + } + + @Override + public boolean isSuggestedOpOnly() { + return CommandBuilder.this.suggestedOpOnly; + } + + @NonNull + @Override + public List subCommands() { + return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands; + } + + @Override + public boolean isBedrockOnly() { + return CommandBuilder.this.bedrockOnly; + } + + @Override + public boolean isExecutableOnConsole() { + return CommandBuilder.this.executableOnConsole; + } + }; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java similarity index 64% rename from core/src/main/java/org/geysermc/geyser/command/CommandSender.java rename to core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index d9d1bcfbc8a..88d148b1147 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -25,43 +25,25 @@ 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. * For example, it wraps around Spigot's CommandSender class. */ -public interface CommandSender { - - String name(); - - default void sendMessage(String[] messages) { - for (String message : messages) { - sendMessage(message); - } - } - - void sendMessage(String message); - - /** - * @return true if the specified sender is from the console. - */ - boolean isConsole(); +public interface GeyserCommandSource extends CommandSource { /** - * Returns the locale of the command sender. Defaults to the default locale at {@link GeyserLocale#getDefaultLocale()}. - * - * @return the locale of the command sender. + * {@inheritDoc} */ - default String getLocale() { + default String locale() { return GeyserLocale.getDefaultLocale(); } - /** - * Checks if the CommandSender has a permission - * - * @param permission The permission node to check - * @return true if the CommandSender has the requested permission, false if not - */ - boolean hasPermission(String permission); + default void sendMessage(Component message) { + sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 18546c91421..466515b3f97 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.MinecraftLocale; @@ -36,11 +36,11 @@ public AdvancedTooltipsCommand(String name, String description, String permissio } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (session != null) { String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; session.setAdvancedTooltips(!session.isAdvancedTooltips()); - session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.getLocale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.getLocale())); + session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 16915857208..28253433f2c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; public class AdvancementsCommand extends GeyserCommand { @@ -35,7 +35,7 @@ public AdvancementsCommand(String name, String description, String permission) { } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (session != null) { session.getAdvancementsCache().buildAndShowMenuForm(); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 576d1712866..95c1157694c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -28,8 +28,8 @@ import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; 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; @@ -47,10 +47,10 @@ public ConnectionTestCommand(GeyserImpl geyser, String name, String description, } @Override - public void execute(@Nullable GeyserSession session, CommandSender sender, String[] args) { + 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.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return; } @@ -69,13 +69,13 @@ public void execute(@Nullable GeyserSession session, CommandSender sender, Strin } // Issue: do the ports not line up? - if (port != geyser.getConfig().getBedrock().getPort()) { + 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().getPort() + "). You can change it under `bedrock` `port`."); + + geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`."); } // Issue: is the `bedrock` `address` in the config different? - if (!geyser.getConfig().getBedrock().getAddress().equals("0.0.0.0")) { + 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."); } @@ -129,7 +129,7 @@ public void execute(@Nullable GeyserSession session, CommandSender sender, Strin }); } - private void sendLinks(CommandSender sender) { + 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"); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 0bac381ba72..60683d34ab3 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -29,14 +29,15 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.dump.DumpInfo; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; @@ -58,10 +59,10 @@ public DumpCommand(GeyserImpl geyser, String name, String description, String pe } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { // Only allow the console to create dumps on Geyser Standalone if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return; } @@ -80,7 +81,7 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale())); String dumpData; try { if (offlineDump) { @@ -92,7 +93,7 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog)); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -100,21 +101,21 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) String uploadedDumpUrl = ""; if (offlineDump) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.locale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale())); String response; JsonNode responseNode; @@ -122,27 +123,28 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); if (!sender.isConsole()) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl)); } } + @NonNull @Override - public List getSubCommands() { + public List subCommands() { return Arrays.asList("offline", "full", "logs"); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java new file mode 100644 index 00000000000..30d422b2337 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -0,0 +1,66 @@ +/* + * 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 org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; +import org.jetbrains.annotations.Nullable; + +import java.util.Comparator; +import java.util.List; + +public class ExtensionsCommand extends GeyserCommand { + private final GeyserImpl geyser; + + public ExtensionsCommand(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) { + // TODO: Pagination + int page = 1; + int maxPage = 1; + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", sender.locale(), page, maxPage); + sender.sendMessage(header); + + this.geyser.extensionManager().extensions().stream().sorted(Comparator.comparing(Extension::name)).forEach(extension -> { + String extensionName = (extension.isEnabled() ? ChatColor.GREEN : ChatColor.RED) + extension.name(); + sender.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors())); + }); + } + + private String formatAuthors(List authors) { + return authors.isEmpty() ? "" : " by: " + String.join(", ", authors); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 85682b29414..6e7ad2f04b9 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 @@ -27,10 +27,11 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import java.util.Collections; @@ -38,10 +39,15 @@ public class HelpCommand extends GeyserCommand { private final GeyserImpl geyser; + private final String baseCommand; + private final Map commands; - public HelpCommand(GeyserImpl geyser, String name, String description, String permission) { + public HelpCommand(GeyserImpl geyser, String name, String description, String permission, + String baseCommand, Map commands) { super(name, description, permission); this.geyser = geyser; + this.baseCommand = baseCommand; + this.commands = commands; this.setAliases(Collections.singletonList("?")); } @@ -54,26 +60,25 @@ public HelpCommand(GeyserImpl geyser, String name, String description, String pe * @param args Not used. */ @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { int page = 1; int maxPage = 1; - String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); sender.sendMessage(header); - Map cmds = geyser.getCommandManager().getCommands(); - for (Map.Entry entry : cmds.entrySet()) { - GeyserCommand cmd = entry.getValue(); + 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.getPermission())) { + 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 + "/geyser " + entry.getKey() + ChatColor.WHITE + ": " + - GeyserLocale.getPlayerLocaleString(cmd.getDescription(), sender.getLocale())); + 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 0a4cfa02338..90446fbb6b2 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.command.defaults; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -44,10 +44,10 @@ public ListCommand(GeyserImpl geyser, String name, String description, String pe } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + 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 48afd21fe33..0015149bec9 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 @@ -30,8 +30,8 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; import com.nukkitx.math.vector.Vector3i; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; public class OffhandCommand extends GeyserCommand { @@ -41,13 +41,13 @@ public OffhandCommand(GeyserImpl geyser, String name, String description, String } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (session == null) { return; } 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/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index e970e5d3d28..843e93de0e2 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -27,8 +27,8 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -42,12 +42,12 @@ public ReloadCommand(GeyserImpl geyser, String name, String description, String } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { return; } - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.locale()); sender.sendMessage(message); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 58d778ba9f7..7828cf1d200 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.command.defaults; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; @@ -37,7 +37,7 @@ public SettingsCommand(GeyserImpl geyser, String name, String description, Strin } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (session != null) { session.sendForm(SettingsUtils.buildForm(session)); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index e54b0fb9b28..ea2da51df52 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -28,8 +28,8 @@ import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; public class StatisticsCommand extends GeyserCommand { @@ -39,7 +39,7 @@ public StatisticsCommand(GeyserImpl geyser, String name, String description, Str } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (session == null) return; session.setWaitingForStatistics(true); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 9c7bd814074..151aa2d8448 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -27,8 +27,8 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -46,9 +46,9 @@ public StopCommand(GeyserImpl geyser, String name, String description, String pe } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index f4f62892acb..fbe4fb4f659 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -28,20 +28,18 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; import java.io.IOException; -import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Properties; public class VersionCommand extends GeyserCommand { @@ -54,49 +52,46 @@ public VersionCommand(GeyserImpl geyser, String name, String description, String } @Override - public void execute(GeyserSession session, CommandSender sender, String[] args) { + public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { String bedrockVersions; - List supportedCodecs = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS; + List supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS; if (supportedCodecs.size() > 1) { bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion(); } else { - bedrockVersions = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.get(0).getMinecraftVersion(); + bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.get(0).getMinecraftVersion(); } String javaVersions; - List supportedJavaVersions = MinecraftProtocol.getJavaVersions(); + List supportedJavaVersions = GameProtocol.getJavaVersions(); if (supportedJavaVersions.size() > 1) { javaVersions = supportedJavaVersions.get(0) + " - " + supportedJavaVersions.get(supportedJavaVersions.size() - 1); } else { javaVersions = supportedJavaVersions.get(0); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.locale(), GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions)); // Disable update checking in dev mode and for players in Geyser Standalone - if (GeyserImpl.getInstance().productionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { - Properties gitProp = new Properties(); - gitProp.load(stream); - + if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); + try { String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + - URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); + URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); if (buildXML.startsWith("")) { int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); - int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); + int buildNum = this.geyser.buildNumber(); if (latestBuildNum == buildNum) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); } else { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated", - sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); + sender.locale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); } } else { throw new AssertionError("buildNumber missing"); } - } catch (IOException | AssertionError | NumberFormatException e) { + } catch (IOException e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale())); } } } 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 1f188cf40b2..8a366baae15 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -27,8 +27,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.BedrockListener; +import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.network.CIDRMatcher; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.GeyserLocale; import java.nio.file.Path; @@ -105,23 +107,19 @@ public interface GeyserConfiguration { int getCustomSkullRenderDistance(); - IMetricsInfo getMetrics(); - - int getPendingAuthenticationTimeout(); - - interface IBedrockConfiguration { + boolean isLogPlayerIpAddresses(); - String getAddress(); + boolean isNotifyOnNewBedrockUpdate(); - int getPort(); + String getUnusableSpaceBlock(); - boolean isCloneRemotePort(); + IMetricsInfo getMetrics(); - String getMotd1(); + int getPendingAuthenticationTimeout(); - String getMotd2(); + interface IBedrockConfiguration extends BedrockListener { - String getServerName(); + boolean isCloneRemotePort(); int getCompressionLevel(); @@ -135,23 +133,25 @@ interface IBedrockConfiguration { List getWhitelistedIPsMatchers(); } - interface IRemoteConfiguration { - - String getAddress(); - - int getPort(); + interface IRemoteConfiguration extends RemoteServer { void setAddress(String address); void setPort(int port); - AuthType getAuthType(); - boolean isPasswordAuthentication(); boolean isUseProxyProtocol(); boolean isForwardHost(); + + default String minecraftVersion() { + return GameProtocol.getJavaMinecraftVersion(); + } + + default int protocolVersion() { + return GameProtocol.getJavaProtocolVersion(); + } } interface IUserAuthenticationInfo { 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 30a947e5310..229895c3c55 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -35,8 +35,8 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.network.CIDRMatcher; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.GeyserLocale; @@ -148,29 +148,68 @@ 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") private int pendingAuthenticationTimeout = 120; - @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "0.0.0.0"; + @Override + public String address() { + return address; + } + @Setter + @JsonProperty("port") private int port = 19132; + @Override + public int port() { + return port; + } + + @Getter @JsonProperty("clone-remote-port") private boolean cloneRemotePort = false; + @JsonProperty("motd1") private String motd1 = "GeyserMC"; + + @Override + public String primaryMotd() { + return motd1; + } + + @JsonProperty("motd2") private String motd2 = "Geyser"; + @Override + public String secondaryMotd() { + return motd2; + } + @JsonProperty("server-name") private String serverName = GeyserImpl.NAME; + @Override + public String serverName() { + return serverName; + } + @JsonProperty("compression-level") private int compressionLevel = 6; @@ -178,9 +217,11 @@ public int getCompressionLevel() { return Math.max(-1, Math.min(compressionLevel, 9)); } + @Getter @JsonProperty("enable-proxy-protocol") private boolean enableProxyProtocol = false; + @Getter @JsonProperty("proxy-protocol-whitelisted-ips") private List proxyProtocolWhitelistedIPs = Collections.emptyList(); @@ -202,28 +243,47 @@ public List getWhitelistedIPsMatchers() { } } - @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "auto"; + @Override + public String address() { + return address; + } + @JsonDeserialize(using = PortDeserializer.class) @Setter + @JsonProperty("port") private int port = 25565; + @Override + public int port() { + return port; + } + @Setter - @JsonDeserialize(using = AuthType.Deserializer.class) + @JsonDeserialize(using = AuthTypeDeserializer.class) @JsonProperty("auth-type") private AuthType authType = AuthType.ONLINE; + @Override + public AuthType authType() { + return authType; + } + + @Getter @JsonProperty("allow-password-authentication") private boolean passwordAuthentication = true; + @Getter @JsonProperty("use-proxy-protocol") private boolean useProxyProtocol = false; + @Getter @JsonProperty("forward-hostname") private boolean forwardHost = false; } @@ -293,4 +353,11 @@ public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOE } } } + + public static class AuthTypeDeserializer extends JsonDeserializer { + @Override + public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return AuthType.getByName(p.getValueAsString()); + } + } } 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 0bbc1c0edac..fda0566fd32 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.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 static class PluginInfo { @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 1c9be8c3ef8..5197f21077a 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; @@ -35,20 +36,21 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.AllArgsConstructor; import lombok.Getter; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.FloodgateInfoHolder; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.util.CpuUtils; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; -import org.geysermc.floodgate.util.DeviceOs; -import org.geysermc.floodgate.util.FloodgateInfoHolder; 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; @@ -68,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; @@ -77,6 +79,7 @@ public class DumpInfo { private LogsInfo logsInfo; private final BootstrapDumpInfo bootstrapInfo; private final FlagsInfo flagsInfo; + private final List extensionInfo; public DumpInfo(boolean addLog) { this.versionInfo = new VersionInfo(); @@ -86,11 +89,7 @@ public DumpInfo(boolean addLog) { 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(); @@ -129,6 +128,11 @@ public DumpInfo(boolean addLog) { this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo(); this.flagsInfo = new FlagsInfo(); + + this.extensionInfo = new ArrayList<>(); + for (Extension extension : GeyserApi.api().extensionManager().extensions()) { + this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors())); + } } @Getter @@ -215,11 +219,11 @@ public static class MCInfo { private final int javaProtocol; MCInfo() { - this.bedrockVersions = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getMinecraftVersion).toList(); - this.bedrockProtocols = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getProtocolVersion).toList(); - this.defaultBedrockProtocol = MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion(); - this.javaVersions = MinecraftProtocol.getJavaVersions(); - this.javaProtocol = MinecraftProtocol.getJavaProtocolVersion(); + this.bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getMinecraftVersion).toList(); + this.bedrockProtocols = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getProtocolVersion).toList(); + this.defaultBedrockProtocol = GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion(); + this.javaVersions = GameProtocol.getJavaVersions(); + this.javaProtocol = GameProtocol.getJavaProtocolVersion(); } } @@ -281,4 +285,29 @@ public static class FlagsInfo { this.flags = ManagementFactory.getRuntimeMXBean().getInputArguments(); } } + + @Getter + @AllArgsConstructor + public static class ExtensionInfo { + public boolean enabled; + public String name; + public String version; + public String apiVersion; + 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 a552d0875d1..b97e2384762 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; @@ -859,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/AreaEffectCloudEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java index 164fbf7058a..a38a4dd16f7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java @@ -32,8 +32,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; 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 ab43bf7b373..c562df4764d 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 void spawnEntity() { @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 144d1cbf9cf..663dd3c33eb 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 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 void moveRelative(double relX, double relY, double relZ, float yaw, float } 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 void setFlags(ByteEntityMetadata entityMetadata) { 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); @@ -366,7 +391,7 @@ protected short getMaxAir() { public void setDisplayName(EntityMetadata, ?> entityMetadata) { Optional name = entityMetadata.getValue(); if (name.isPresent()) { - nametag = MessageTranslator.convertMessage(name.get(), session.getLocale()); + nametag = MessageTranslator.convertMessage(name.get(), session.locale()); dirtyMetadata.put(EntityData.NAMETAG, nametag); } else if (!nametag.isEmpty()) { // Clear nametag diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java index fa22422baa1..12498f75237 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java @@ -36,12 +36,12 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; +import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.player.PlayerEntity; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.FireworkColor; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.MathUtils; -import org.geysermc.floodgate.util.DeviceOs; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java index dbd9bf91ff1..8074cd5ab55 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java @@ -30,8 +30,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index f36a7c732ff..89db9b0c892 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -35,9 +35,9 @@ import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.level.block.BlockStateValues; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index bc7736e9b7d..8e4a5323ac4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -40,7 +40,6 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.util.InteractionResult; @@ -114,7 +113,9 @@ public void setItemInFrame(EntityMetadata entityMetadata) { if (entityMetadata.getValue() != null) { this.heldItem = entityMetadata.getValue(); ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem); - ItemMapping mapping = session.getItemMappings().getMapping(entityMetadata.getValue()); + + String customIdentifier = session.getItemMappings().getCustomIdMappings().get(itemData.getId()); + NbtMapBuilder builder = NbtMap.builder(); builder.putByte("Count", (byte) itemData.getCount()); @@ -122,7 +123,7 @@ public void setItemInFrame(EntityMetadata entityMetadata) { builder.put("tag", itemData.getTag()); } builder.putShort("Damage", (short) itemData.getDamage()); - builder.putString("Name", mapping.getBedrockIdentifier()); + builder.putString("Name", customIdentifier != null ? customIdentifier : session.getItemMappings().getMapping(entityMetadata.getValue()).getBedrockIdentifier()); NbtMapBuilder tag = getDefaultTag().toBuilder(); tag.put("Item", builder.build()); tag.putFloat("ItemDropChance", 1.0f); 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 2550643d312..f7e055417cd 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 void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { // 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/SpawnerMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java index 5f7c906e9ed..cd5df1bf43a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java @@ -28,8 +28,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index ad8b60bdbf3..3652860b354 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -32,8 +32,8 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java index 6f6125f2daa..fcfc4ff1205 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java @@ -34,9 +34,9 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.EnumSet; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java index d296019c14c..95118f92850 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java @@ -29,8 +29,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; 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 c368deb6da2..8ab882f4b84 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 @@ -167,6 +167,7 @@ public void setArmorStandFlags(ByteEntityMetadata entityMetadata) { // 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) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index 552f6a46c84..6b235a8e553 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -30,8 +30,8 @@ import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.Tickable; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java index 0da53b7c6c1..16a72a23513 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java @@ -32,8 +32,8 @@ import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.living.AgeableEntity; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java index ca7ee57a2b6..74652da809c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -33,8 +33,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InteractionResult; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java index 09b1b73c5a5..ce02905b955 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java @@ -33,8 +33,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java index c5fad8bb818..2185d158b64 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java @@ -27,8 +27,8 @@ import com.nukkitx.math.vector.Vector3f; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java index 5ae3bd5243a..8e350e6859e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java index af1fe0bade1..a44a0e9f953 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java @@ -30,8 +30,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java index 51f595526db..5e8d9c16f8f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java @@ -35,8 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java index db8a1ccd86e..3b424b45650 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java @@ -30,8 +30,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java index b677c135ee1..1c5c47261d4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java @@ -27,8 +27,8 @@ import com.nukkitx.math.vector.Vector3f; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java index 966e500b40d..c49c9beb32f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java index f8d54929966..fdbaad99730 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java @@ -29,11 +29,11 @@ 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.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index 79a7b8f50e0..1aa0d4fc970 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -29,8 +29,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; 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 00000000000..408e2ec2189 --- /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/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java index 0225355928b..cca62f543d9 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java @@ -34,8 +34,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; @@ -50,6 +50,13 @@ public CatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Default value (minecraft:black). + dirtyMetadata.put(EntityData.VARIANT, 1); + } + @Override public void updateRotation(float yaw, float pitch, boolean isOnGround) { moveRelative(0, 0, 0, yaw, pitch, yaw, isOnGround); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java index 2b49168dde5..51582e087a4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -30,8 +30,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java index 33b2144e8ce..c95556cb48c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import lombok.Getter; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; import org.geysermc.geyser.session.GeyserSession; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index bc5209bcb4c..d6825e8a15c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -34,8 +34,8 @@ 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; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.ItemUtils; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java index a169081fca7..5631a68c92a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java @@ -28,8 +28,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.session.GeyserSession; public class EnderDragonPartEntity extends Entity { 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 03492d51873..04b46997d62 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 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 EndermanEntity(GeyserSession session, int entityId, long geyserId, UUID u 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/living/monster/GuardianEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java index fe1f3038b68..e2454123fce 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java @@ -28,8 +28,8 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java index d6926996ea8..81aa1ed99a5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java @@ -28,8 +28,8 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java index 4359c425438..716c54de1c4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java @@ -28,8 +28,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; 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 f16f46e2eb9..74b95b73c1d 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 void setFlags(ByteEntityMetadata entityMetadata) { 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/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java new file mode 100644 index 00000000000..9593e327ebe --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -0,0 +1,72 @@ +/* + * 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 org.checkerframework.checker.nullness.qual.NonNull; +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.EventRegistrar; +import org.geysermc.geyser.api.event.EventSubscriber; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@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 + ); + } + + @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 + public Set> subscribers(@NonNull Class eventClass) { + return castGenericSet(super.subscribers(eventClass)); + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java similarity index 74% rename from bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java rename to core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java index 019544c28c5..85c36a13212 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java @@ -23,19 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.bungeecord.command; +package org.geysermc.geyser.event; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.api.event.EventRegistrar; -public class GeyserBungeeCommandManager extends CommandManager { - - public GeyserBungeeCommandManager(GeyserImpl geyser) { - super(geyser); - } +public record GeyserEventRegistrar(Object owner) implements EventRegistrar { @Override - public String getDescription(String command) { - return ""; // no support for command descriptions in bungee + public String toString() { + return "GeyserEventRegistrar{" + + "owner=" + this.owner + + '}'; } } diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java new file mode 100644 index 00000000000..d33de8cdd79 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java @@ -0,0 +1,58 @@ +/* + * 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 org.checkerframework.checker.nullness.qual.NonNull; +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; + +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); + } + + 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 00000000000..e07a62d8a14 --- /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 00000000000..65fd7ea0d9e --- /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 new file mode 100644 index 00000000000..b94e70ed0c1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.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.extension; + +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; + +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class GeyserExtensionClassLoader extends URLClassLoader { + private final GeyserExtensionLoader loader; + private final Object2ObjectMap> classes = new Object2ObjectOpenHashMap<>(); + + public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path) throws MalformedURLException { + super(new URL[] { path.toUri().toURL() }, parent); + this.loader = loader; + } + + public Extension load(ExtensionDescription description) throws InvalidExtensionException { + try { + Class jarClass; + try { + jarClass = Class.forName(description.main(), true, this); + } catch (ClassNotFoundException ex) { + throw new InvalidExtensionException("Class " + description.main() + " not found, extension cannot be loaded", ex); + } + + Class extensionClass; + try { + extensionClass = jarClass.asSubclass(Extension.class); + } catch (ClassCastException ex) { + throw new InvalidExtensionException("Main class " + description.main() + " should implement Extension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); + } + + return extensionClass.getConstructor().newInstance(); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { + throw new InvalidExtensionException("No public constructor", ex); + } catch (InstantiationException ex) { + throw new InvalidExtensionException("Abnormal extension type", ex); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return this.findClass(name, true); + } + + protected Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { + if (name.startsWith("org.geysermc.geyser.") || name.startsWith("net.minecraft.")) { + throw new ClassNotFoundException(name); + } + + Class result = this.classes.get(name); + if (result == null) { + if (checkGlobal) { + result = this.loader.classByName(name); + } + + if (result == null) { + result = super.findClass(name); + if (result != null) { + this.loader.setClass(name, result); + } + } + + this.classes.put(name, result); + } + return result; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java new file mode 100644 index 00000000000..a26415a139b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java @@ -0,0 +1,52 @@ +/* + * 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.extension; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import org.geysermc.geyser.api.event.ExtensionEventBus; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.api.extension.ExtensionLogger; + +import java.nio.file.Path; + +@Accessors(fluent = true) +@Getter +@RequiredArgsConstructor +public class GeyserExtensionContainer { + private final Extension extension; + private final Path dataFolder; + private final ExtensionDescription description; + private final ExtensionLoader loader; + private final ExtensionLogger logger; + private final ExtensionEventBus eventBus; + + @Getter(AccessLevel.NONE) protected boolean enabled; +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java new file mode 100644 index 00000000000..716b763f5ac --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -0,0 +1,119 @@ +/* + * 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.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.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(@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 { + Source source; + try { + source = YAML.loadAs(reader, Source.class); + } catch (Exception e) { + throw new InvalidDescriptionException(e); + } + + 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 (source.author != null) { + authors.add(source.author); + } + if (source.authors != null) { + authors.addAll(source.authors); + } + + return new GeyserExtensionDescription(id, name, main, majorApi, minorApi, patchApi, 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 new file mode 100644 index 00000000000..7e998e41333 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -0,0 +1,222 @@ +/* + * 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.extension; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.api.Geyser; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.ExtensionEventBus; +import org.geysermc.geyser.api.extension.*; +import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; +import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; +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.regex.Pattern; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class GeyserExtensionLoader extends ExtensionLoader { + private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; + + 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 { + if (path == null) { + throw new InvalidExtensionException("Path is null"); + } + + if (Files.notExists(path)) { + throw new InvalidExtensionException(new NoSuchFileException(path.toString()) + " does not exist"); + } + + Path parentFile = path.getParent(); + Path dataFolder = parentFile.resolve(description.name()); + if (Files.exists(dataFolder) && !Files.isDirectory(dataFolder)) { + throw new InvalidExtensionException("The folder " + dataFolder + " is not a directory and is the data folder for the extension " + description.name() + "!"); + } + + final GeyserExtensionClassLoader loader; + try { + loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), path); + } catch (Throwable e) { + throw new InvalidExtensionException(e); + } + + this.classLoaders.put(description.name(), loader); + + final Extension extension = loader.load(description); + return this.setup(extension, description, dataFolder, new GeyserExtensionEventBus(GeyserImpl.getInstance().eventBus(), extension)); + } + + private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder, ExtensionEventBus eventBus) { + GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); + return new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus); + } + + public GeyserExtensionDescription extensionDescription(Path path) throws InvalidDescriptionException { + Map environment = new HashMap<>(); + try (FileSystem fileSystem = FileSystems.newFileSystem(path, environment, null)) { + Path extensionYml = fileSystem.getPath("extension.yml"); + try (Reader reader = Files.newBufferedReader(extensionYml)) { + return GeyserExtensionDescription.fromYaml(reader); + } + } catch (IOException ex) { + throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); + } + } + + public Pattern[] extensionFilters() { + return EXTENSION_FILTERS; + } + + public Class classByName(final String name) throws ClassNotFoundException{ + Class clazz = this.classes.get(name); + if (clazz != null) { + return clazz; + } + + for (GeyserExtensionClassLoader loader : this.classLoaders.values()) { + clazz = loader.findClass(name, false); + if (clazz != null) { + break; + } + } + + return clazz; + } + + void setClass(String name, final Class clazz) { + this.classes.putIfAbsent(name, clazz); + } + + @Override + protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { + try { + if (Files.notExists(extensionsDirectory)) { + Files.createDirectory(extensionsDirectory); + } + + Map extensions = new LinkedHashMap<>(); + Map loadedExtensions = new LinkedHashMap<>(); + + Pattern[] extensionFilters = this.extensionFilters(); + try (Stream entries = Files.walk(extensionsDirectory)) { + entries.forEach(path -> { + if (Files.isDirectory(path)) { + return; + } + + for (Pattern filter : extensionFilters) { + if (!filter.matcher(path.getFileName().toString()).matches()) { + return; + } + } + + try { + GeyserExtensionDescription description = this.extensionDescription(path); + + String name = description.name(); + if (extensions.containsKey(name) || extensionManager.extension(name) != null) { + GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, path.toString())); + return; + } + + // Completely different API version + if (description.majorApiVersion() != Geyser.api().majorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); + return; + } + + // If the extension requires new API features, being backwards compatible + if (description.minorApiVersion() > Geyser.api().minorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); + return; + } + + extensions.put(name, path); + loadedExtensions.put(name, this.loadExtension(path, description)); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", path.getFileName(), path.toAbsolutePath()), e); + } + }); + } + + for (GeyserExtensionContainer container : loadedExtensions.values()) { + this.extensionContainers.put(container.extension(), container); + this.register(container.extension(), extensionManager); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @Override + protected boolean isEnabled(@NonNull Extension extension) { + return this.extensionContainers.get(extension).enabled; + } + + @Override + protected void setEnabled(@NonNull Extension extension, boolean enabled) { + this.extensionContainers.get(extension).enabled = enabled; + } + + @NonNull + @Override + protected Path dataFolder(@NonNull Extension extension) { + return this.extensionContainers.get(extension).dataFolder(); + } + + @NonNull + @Override + protected ExtensionDescription description(@NonNull Extension extension) { + return this.extensionContainers.get(extension).description(); + } + + @NonNull + @Override + protected ExtensionEventBus eventBus(@NonNull Extension extension) { + return this.extensionContainers.get(extension).eventBus(); + } + + @NonNull + @Override + protected ExtensionLogger logger(@NonNull Extension extension) { + return this.extensionContainers.get(extension).logger(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java new file mode 100644 index 00000000000..fe23417f8dd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java @@ -0,0 +1,88 @@ +/* + * 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.extension; + +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.extension.ExtensionLogger; + +public class GeyserExtensionLogger implements ExtensionLogger { + private final GeyserLogger logger; + private final String loggerPrefix; + + public GeyserExtensionLogger(GeyserLogger logger, String prefix) { + this.logger = logger; + this.loggerPrefix = prefix; + } + + @Override + public String prefix() { + return this.loggerPrefix; + } + + private String addPrefix(String message) { + return "[" + this.loggerPrefix + "] " + message; + } + + @Override + public void severe(String message) { + this.logger.severe(this.addPrefix(message)); + } + + @Override + public void severe(String message, Throwable error) { + this.logger.severe(this.addPrefix(message), error); + } + + @Override + public void error(String message) { + this.logger.error(this.addPrefix(message)); + } + + @Override + public void error(String message, Throwable error) { + this.logger.error(this.addPrefix(message), error); + } + + @Override + public void warning(String message) { + this.logger.warning(this.addPrefix(message)); + } + + @Override + public void info(String message) { + this.logger.info(this.addPrefix(message)); + } + + @Override + public void debug(String message) { + this.logger.debug(this.addPrefix(message)); + } + + @Override + public boolean isDebug() { + return this.logger.isDebug(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java new file mode 100644 index 00000000000..5dd92430119 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -0,0 +1,126 @@ +/* + * 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.extension; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.api.extension.ExtensionManager; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class GeyserExtensionManager extends ExtensionManager { + private final GeyserExtensionLoader extensionLoader = new GeyserExtensionLoader(); + private final Map extensions = new LinkedHashMap<>(); + + public void init() { + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); + + loadAllExtensions(this.extensionLoader); + enableExtensions(); + + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); + } + + @Override + public Extension extension(@NonNull String name) { + return this.extensions.get(name); + } + + @Override + public void enable(@NonNull Extension extension) { + if (!extension.isEnabled()) { + try { + this.enableExtension(extension); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e); + this.disable(extension); + } + } + } + + @Override + public void disable(@NonNull Extension extension) { + if (extension.isEnabled()) { + try { + this.disableExtension(extension); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.failed", extension.name()), e); + } + } + } + + public void enableExtension(Extension extension) { + if (!extension.isEnabled()) { + extension.setEnabled(true); + GeyserImpl.getInstance().eventBus().register(extension, extension); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); + } + } + + public void enableExtensions() { + for (Extension extension : this.extensions()) { + this.enable(extension); + } + } + + private void disableExtension(@NonNull Extension extension) { + if (extension.isEnabled()) { + GeyserImpl.getInstance().eventBus().unregisterAll(extension); + + extension.setEnabled(false); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); + } + } + + public void disableExtensions() { + for (Extension extension : this.extensions()) { + this.disable(extension); + } + } + + @NonNull + @Override + public Collection extensions() { + return Collections.unmodifiableCollection(this.extensions.values()); + } + + @Override + public @Nullable ExtensionLoader extensionLoader() { + return this.extensionLoader; + } + + @Override + public void register(@NonNull Extension extension) { + this.extensions.put(extension.name(), extension); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java new file mode 100644 index 00000000000..4a7830c901d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.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.extension.command; + +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.GeyserCommand; + +public abstract class GeyserExtensionCommand extends GeyserCommand { + private final Extension extension; + + public GeyserExtensionCommand(Extension extension, String name, String description, String permission) { + super(name, description, permission); + + this.extension = extension; + } + + 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 new file mode 100644 index 00000000000..f56b254a6c5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.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.extension.event; + +import org.checkerframework.checker.nullness.qual.NonNull; +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.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 { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void unsubscribe(@NonNull EventSubscriber subscription) { + eventBus.unsubscribe((EventSubscriber) subscription); + } + + @Override + public boolean fire(@NonNull Event event) { + return eventBus.fire(event); + } + + @Override + 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 688151a9ea3..471aff8b248 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -75,8 +75,8 @@ public String checkForRename(GeyserSession session, String rename) { String originalName = ItemUtils.getCustomName(getInput().getNbt()); - String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale()); - String plainNewName = MessageTranslator.convertToPlainText(rename, session.getLocale()); + String plainOriginalName = MessageTranslator.convertToPlainText(originalName, 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/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java index e249f016744..0fb70a1a2ba 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java @@ -29,9 +29,9 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import lombok.Data; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; import javax.annotation.Nonnull; 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 ca7e90a251d..f01d242adf2 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -34,7 +34,6 @@ import lombok.Setter; import lombok.ToString; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.jetbrains.annotations.Range; @@ -45,7 +44,7 @@ @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. @@ -94,15 +93,23 @@ protected Inventory(int id, int size, ContainerType containerType) { 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); @@ -136,9 +143,9 @@ public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession se protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { if (!newItem.isEmpty()) { - ItemMapping oldMapping = ItemTranslator.getBedrockItemMapping(session, oldItem); - ItemMapping newMapping = ItemTranslator.getBedrockItemMapping(session, newItem); - if (oldMapping.getBedrockId() == newMapping.getBedrockId()) { + int oldMapping = ItemTranslator.getBedrockItemId(session, oldItem); + int newMapping = ItemTranslator.getBedrockItemId(session, newItem); + if (oldMapping == newMapping) { newItem.setNetId(oldItem.getNetId()); } else { newItem.setNetId(session.getNextItemNetId()); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java index 315e6cb1851..93c1917d28f 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.inventory; -import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket; import lombok.Getter; import lombok.Setter; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java b/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java index 027c7a7ce4f..d7068920e2b 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.inventory.click; -import com.github.steveice10.mc.protocol.data.game.inventory.ClickItemAction; -import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType; import com.github.steveice10.mc.protocol.data.game.inventory.*; import lombok.AllArgsConstructor; 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 ec36645da9e..bfe5a7d9d3a 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.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 void execute(boolean refresh) { } 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 void execute(boolean refresh) { } 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 fd26cc1701f..3e0892be426 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.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 BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerT 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 void prepareInventory(InventoryTranslator translator, GeyserSession sessi 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 void prepareInventory(InventoryTranslator translator, GeyserSession sessi inventory.setHolderPosition(position); setCustomName(session, position, inventory, defaultJavaBlockState); + + return true; } /** @@ -133,7 +138,7 @@ protected void setCustomName(GeyserSession session, Vector3i position, Inventory @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 void closeInventory(InventoryTranslator translator, GeyserSession session // 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 fe54e1dc047..986df53dbdc 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.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/recipe/GeyserShapedRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java index a011fef6d04..d61945ad8d7 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java @@ -27,7 +27,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, ItemStack result) implements GeyserRecipe { diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index 655d0f215ce..e83971443b2 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -27,7 +27,10 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; @@ -40,12 +43,12 @@ import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.EnchantmentData; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ItemUtils; import java.util.Objects; @@ -115,7 +118,7 @@ private void updateInventoryState(GeyserSession session, AnvilContainer anvilCon // Changing the item in the input slot resets the name field on Bedrock, but // does not result in a FilterTextPacket - String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.getLocale()); + String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.locale()); ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName); session.sendDownstreamPacket(renameItemPacket); @@ -427,7 +430,7 @@ private boolean isRenaming(GeyserSession session, AnvilContainer anvilContainer, String originalName = ItemUtils.getCustomName(anvilContainer.getInput().getNbt()); if (bedrock && originalName != null && anvilContainer.getNewName() != null) { // Check text and formatting - String legacyOriginalName = MessageTranslator.convertMessageLenient(originalName, session.getLocale()); + String legacyOriginalName = MessageTranslator.convertMessageLenient(originalName, session.locale()); return !legacyOriginalName.equals(anvilContainer.getNewName()); } return !Objects.equals(originalName, ItemUtils.getCustomName(anvilContainer.getResult().getNbt())); 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 1e5c6946de1..8a174c2c51a 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 @@ -31,9 +31,9 @@ import lombok.AllArgsConstructor; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.util.InventoryUtils; -import org.geysermc.geyser.text.GeyserLocale; import java.util.ArrayList; import java.util.List; @@ -59,7 +59,7 @@ public void updateInventory(InventoryTranslator translator, GeyserSession sessio } InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); + contentPacket.setContainerId(inventory.getBedrockId()); contentPacket.setContents(bedrockItems); session.sendUpstreamPacket(contentPacket); } @@ -70,7 +70,7 @@ public boolean updateSlot(InventoryTranslator translator, GeyserSession session, 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 705a8b242c5..c943a62b4e3 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 void updateInventory(InventoryTranslator translator, GeyserSession sessio } 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 boolean updateSlot(InventoryTranslator translator, GeyserSession session, 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 fa680c2017b..20ce7e46737 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 void updateInventory(InventoryTranslator translator, GeyserSession sessio } 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/item/GeyserCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java new file mode 100644 index 00000000000..ddea9937c41 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.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.item; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.jetbrains.annotations.NotNull; + +@EqualsAndHashCode +@ToString +public class GeyserCustomItemData implements CustomItemData { + private final String name; + private final CustomItemOptions customItemOptions; + private final String displayName; + private final String icon; + private final boolean allowOffhand; + private final int textureSize; + private final CustomRenderOffsets renderOffsets; + + public GeyserCustomItemData(String name, + CustomItemOptions customItemOptions, + String displayName, + String icon, + boolean allowOffhand, + int textureSize, + CustomRenderOffsets renderOffsets) { + this.name = name; + this.customItemOptions = customItemOptions; + this.displayName = displayName; + this.icon = icon; + this.allowOffhand = allowOffhand; + this.textureSize = textureSize; + this.renderOffsets = renderOffsets; + } + + public @NotNull String name() { + return name; + } + + public CustomItemOptions customItemOptions() { + return customItemOptions; + } + + public @NotNull String displayName() { + return displayName; + } + + public @NotNull String icon() { + return icon; + } + + public boolean allowOffhand() { + return allowOffhand; + } + + public int textureSize() { + return textureSize; + } + + public CustomRenderOffsets renderOffsets() { + return renderOffsets; + } + + public static class CustomItemDataBuilder implements Builder { + protected String name = null; + protected CustomItemOptions customItemOptions = null; + + protected String displayName = null; + protected String icon = null; + protected boolean allowOffhand = true; // Bedrock doesn't give items offhand allowance unless they serve gameplay purpose, but we want to be friendly with Java + protected int textureSize = 16; + protected CustomRenderOffsets renderOffsets = null; + + @Override + public Builder name(@NonNull String name) { + this.name = name; + return this; + } + + @Override + public Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) { + this.customItemOptions = customItemOptions; + return this; + } + + @Override + public Builder displayName(@NonNull String displayName) { + this.displayName = displayName; + return this; + } + + @Override + public Builder icon(@NonNull String icon) { + this.icon = icon; + return this; + } + + @Override + public Builder allowOffhand(boolean allowOffhand) { + this.allowOffhand = allowOffhand; + return this; + } + + @Override + public Builder textureSize(int textureSize) { + this.textureSize = textureSize; + return this; + } + + @Override + public Builder renderOffsets(CustomRenderOffsets renderOffsets) { + this.renderOffsets = renderOffsets; + return this; + } + + @Override + public CustomItemData build() { + if (this.name == null || this.customItemOptions == null) { + throw new IllegalArgumentException("Name and custom item options must be set"); + } + + if (this.displayName == null) { + this.displayName = this.name; + } + if (this.icon == null) { + this.icon = this.name; + } + return new GeyserCustomItemData(this.name, this.customItemOptions, this.displayName, this.icon, this.allowOffhand, this.textureSize, this.renderOffsets); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java new file mode 100644 index 00000000000..dd4ae01de0e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java @@ -0,0 +1,69 @@ +/* + * 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.item; + +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.util.TriState; + +import java.util.OptionalInt; + +public record GeyserCustomItemOptions(TriState unbreakable, + OptionalInt customModelData, + OptionalInt damagePredicate) implements CustomItemOptions { + + public static class CustomItemOptionsBuilder implements CustomItemOptions.Builder { + private TriState unbreakable = TriState.NOT_SET; + private OptionalInt customModelData = OptionalInt.empty(); + private OptionalInt damagePredicate = OptionalInt.empty(); + + @Override + public Builder unbreakable(boolean unbreakable) { + if (unbreakable) { + this.unbreakable = TriState.TRUE; + } else { + this.unbreakable = TriState.FALSE; + } + return this; + } + + @Override + public Builder customModelData(int customModelData) { + this.customModelData = OptionalInt.of(customModelData); + return this; + } + + @Override + public Builder damagePredicate(int damagePredicate) { + this.damagePredicate = OptionalInt.of(damagePredicate); + return this; + } + + @Override + public CustomItemOptions build() { + return new GeyserCustomItemOptions(unbreakable, customModelData, damagePredicate); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java new file mode 100644 index 00000000000..3829db3c36e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java @@ -0,0 +1,32 @@ +/* + * 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.item; + +import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; + +public record GeyserCustomMappingData(ComponentItemData componentItemData, StartGamePacket.ItemEntry startGamePacketItemEntry, String stringId, int integerId) { +} diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java new file mode 100644 index 00000000000..efdc1fdcffa --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java @@ -0,0 +1,303 @@ +/* + * 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.item; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.jetbrains.annotations.NotNull; + +import java.util.OptionalInt; +import java.util.Set; + +@EqualsAndHashCode(callSuper = true) +@ToString +public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData implements NonVanillaCustomItemData { + private final String identifier; + private final int javaId; + private final int stackSize; + private final int maxDamage; + private final String toolType; + private final String toolTier; + private final String armorType; + private final int protectionValue; + private final String translationString; + private final Set repairMaterials; + private final OptionalInt creativeCategory; + private final String creativeGroup; + private final boolean isHat; + private final boolean isTool; + + public GeyserNonVanillaCustomItemData(NonVanillaCustomItemDataBuilder builder) { + super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand, + builder.textureSize, builder.renderOffsets); + + this.identifier = builder.identifier; + this.javaId = builder.javaId; + this.stackSize = builder.stackSize; + this.maxDamage = builder.maxDamage; + this.toolType = builder.toolType; + this.toolTier = builder.toolTier; + this.armorType = builder.armorType; + this.protectionValue = builder.protectionValue; + this.translationString = builder.translationString; + this.repairMaterials = builder.repairMaterials; + this.creativeCategory = builder.creativeCategory; + this.creativeGroup = builder.creativeGroup; + this.isHat = builder.hat; + this.isTool = builder.tool; + } + + @Override + public @NotNull String identifier() { + return identifier; + } + + @Override + public int javaId() { + return javaId; + } + + @Override + public int stackSize() { + return stackSize; + } + + @Override + public int maxDamage() { + return maxDamage; + } + + @Override + public String toolType() { + return toolType; + } + + @Override + public String toolTier() { + return toolTier; + } + + @Override + public @Nullable String armorType() { + return armorType; + } + + @Override + public int protectionValue() { + return protectionValue; + } + + @Override + public String translationString() { + return translationString; + } + + @Override + public Set repairMaterials() { + return repairMaterials; + } + + @Override + public @NotNull OptionalInt creativeCategory() { + return creativeCategory; + } + + @Override + public String creativeGroup() { + return creativeGroup; + } + + @Override + public boolean isHat() { + return isHat; + } + + @Override + public boolean isTool() { + return isTool; + } + + public static class NonVanillaCustomItemDataBuilder extends GeyserCustomItemData.CustomItemDataBuilder implements NonVanillaCustomItemData.Builder { + private String identifier = null; + private int javaId = -1; + + private int stackSize = 64; + + private int maxDamage = 0; + + private String toolType = null; + private String toolTier = null; + + private String armorType = null; + private int protectionValue = 0; + + private String translationString; + + private Set repairMaterials; + + private OptionalInt creativeCategory = OptionalInt.empty(); + private String creativeGroup = null; + + private boolean hat = false; + private boolean tool = false; + + @Override + public NonVanillaCustomItemData.Builder name(@NonNull String name) { + return (NonVanillaCustomItemData.Builder) super.name(name); + } + + @Override + public NonVanillaCustomItemData.Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) { + //Do nothing, as that value won't be read + return this; + } + + @Override + public NonVanillaCustomItemData.Builder allowOffhand(boolean allowOffhand) { + return (NonVanillaCustomItemData.Builder) super.allowOffhand(allowOffhand); + } + + @Override + public NonVanillaCustomItemData.Builder displayName(@NonNull String displayName) { + return (NonVanillaCustomItemData.Builder) super.displayName(displayName); + } + + @Override + public NonVanillaCustomItemData.Builder icon(@NonNull String icon) { + return (NonVanillaCustomItemData.Builder) super.icon(icon); + } + + @Override + public NonVanillaCustomItemData.Builder textureSize(int textureSize) { + return (NonVanillaCustomItemData.Builder) super.textureSize(textureSize); + } + + @Override + public NonVanillaCustomItemData.Builder renderOffsets(CustomRenderOffsets renderOffsets) { + return (NonVanillaCustomItemData.Builder) super.renderOffsets(renderOffsets); + } + + @Override + public NonVanillaCustomItemData.Builder identifier(@NonNull String identifier) { + this.identifier = identifier; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder javaId(int javaId) { + this.javaId = javaId; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder stackSize(int stackSize) { + this.stackSize = stackSize; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder maxDamage(int maxDamage) { + this.maxDamage = maxDamage; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder toolType(@Nullable String toolType) { + this.toolType = toolType; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder toolTier(@Nullable String toolTier) { + this.toolTier = toolTier; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder armorType(@Nullable String armorType) { + this.armorType = armorType; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder protectionValue(int protectionValue) { + this.protectionValue = protectionValue; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder translationString(@Nullable String translationString) { + this.translationString = translationString; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder repairMaterials(@Nullable Set repairMaterials) { + this.repairMaterials = repairMaterials; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder creativeCategory(int creativeCategory) { + this.creativeCategory = OptionalInt.of(creativeCategory); + return this; + } + + @Override + public NonVanillaCustomItemData.Builder creativeGroup(@Nullable String creativeGroup) { + this.creativeGroup = creativeGroup; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder hat(boolean isHat) { + this.hat = isHat; + return this; + } + + @Override + public NonVanillaCustomItemData.Builder tool(boolean isTool) { + this.tool = isTool; + return this; + } + + @Override + public NonVanillaCustomItemData build() { + if (identifier == null || javaId == -1) { + throw new IllegalArgumentException("Identifier and javaId must be set"); + } + + super.customItemOptions(CustomItemOptions.builder().build()); + super.build(); + return new GeyserNonVanillaCustomItemData(this); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java b/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java new file mode 100644 index 00000000000..6330043e540 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java @@ -0,0 +1,174 @@ +/* + * 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.item.components; + +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; + +import java.util.ArrayList; +import java.util.List; + +public class ToolBreakSpeedsUtils { + public static int toolTierToSpeed(String toolTier) { + ToolTier tier = ToolTier.getByName(toolTier); + if (tier != null) { + return tier.getSpeed(); + } + + return 0; + } + + private static NbtMap createTagBreakSpeed(int speed, String... tags) { + StringBuilder builder = new StringBuilder("query.any_tag('"); + builder.append(tags[0]); + for (int i = 1; i < tags.length; i++) { + builder.append("', '").append(tags[i]); + } + builder.append("')"); + + return NbtMap.builder() + .putCompound("block", NbtMap.builder() + .putString("tags", builder.toString()) + .build()) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putInt("speed", speed) + .build(); + } + + private static NbtMap createBreakSpeed(int speed, String block) { + return NbtMap.builder() + .putCompound("block", NbtMap.builder() + .putString("name", block).build()) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putInt("speed", speed) + .build(); + } + + private static NbtMap createDigger(List speeds) { + return NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speeds) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putBoolean("use_efficiency", true) + .build(); + } + + public static NbtMap getAxeDigger(int speed) { + List speeds = new ArrayList<>(); + speeds.add(createTagBreakSpeed(speed, "wood", "pumpkin", "plant")); + + return createDigger(speeds); + } + + public static NbtMap getPickaxeDigger(int speed, String toolTier) { + List speeds = new ArrayList<>(); + if (toolTier.equals(ToolTier.DIAMOND.toString()) || toolTier.equals(ToolTier.NETHERITE.toString())) { + speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable", "diamond_pick_diggable")); + } else { + speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable")); + } + speeds.add(createTagBreakSpeed(speed, "stone", "metal", "rail", "mob_spawner")); + + return createDigger(speeds); + } + + public static NbtMap getShovelDigger(int speed) { + List speeds = new ArrayList<>(); + speeds.add(createTagBreakSpeed(speed, "dirt", "sand", "gravel", "grass", "snow")); + + return createDigger(speeds); + } + + public static NbtMap getSwordDigger(int speed) { + List speeds = new ArrayList<>(); + speeds.add(createBreakSpeed(speed, "minecraft:web")); + speeds.add(createBreakSpeed(speed, "minecraft:bamboo")); + + return createDigger(speeds); + } + + public static NbtMap getHoeDigger(int speed) { + List speeds = new ArrayList<>(); + speeds.add(createBreakSpeed(speed, "minecraft:leaves")); + speeds.add(createBreakSpeed(speed, "minecraft:leaves2")); + speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves")); + speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered")); + + speeds.add(createBreakSpeed(speed, "minecraft:sculk")); + speeds.add(createBreakSpeed(speed, "minecraft:sculk_catalyst")); + speeds.add(createBreakSpeed(speed, "minecraft:sculk_sensor")); + speeds.add(createBreakSpeed(speed, "minecraft:sculk_shrieker")); + speeds.add(createBreakSpeed(speed, "minecraft:sculk_vein")); + + speeds.add(createBreakSpeed(speed, "minecraft:nether_wart_block")); + speeds.add(createBreakSpeed(speed, "minecraft:warped_wart_block")); + + speeds.add(createBreakSpeed(speed, "minecraft:hay_block")); + speeds.add(createBreakSpeed(speed, "minecraft:moss_block")); + speeds.add(createBreakSpeed(speed, "minecraft:shroomlight")); + speeds.add(createBreakSpeed(speed, "minecraft:sponge")); + speeds.add(createBreakSpeed(speed, "minecraft:target")); + + return createDigger(speeds); + } + + public static NbtMap getShearsDigger(int speed) { + List speeds = new ArrayList<>(); + speeds.add(createBreakSpeed(speed, "minecraft:web")); + + speeds.add(createBreakSpeed(speed, "minecraft:leaves")); + speeds.add(createBreakSpeed(speed, "minecraft:leaves2")); + speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves")); + speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered")); + + speeds.add(createBreakSpeed(speed, "minecraft:wool")); + + speeds.add(createBreakSpeed(speed, "minecraft:glow_lichen")); + speeds.add(createBreakSpeed(speed, "minecraft:vine")); + + return createDigger(speeds); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/components/ToolTier.java b/core/src/main/java/org/geysermc/geyser/item/components/ToolTier.java new file mode 100644 index 00000000000..37e58168240 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/components/ToolTier.java @@ -0,0 +1,66 @@ +/* + * 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.item.components; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Locale; + +public enum ToolTier { + WOODEN(2), + STONE(4), + IRON(6), + GOLDEN(12), + DIAMOND(8), + NETHERITE(9); + + public static final ToolTier[] VALUES = values(); + + private final int speed; + + ToolTier(int speed) { + this.speed = speed; + } + + public int getSpeed() { + return speed; + } + + @Override + public String toString() { + return this.name().toLowerCase(Locale.ROOT); + } + + public static ToolTier getByName(@NonNull String name) { + String upperCase = name.toUpperCase(Locale.ROOT); + for (ToolTier tier : VALUES) { + if (tier.name().equals(upperCase)) { + return tier; + } + } + return null; + } +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java b/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java similarity index 73% rename from bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java rename to core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java index b42c8f76ec1..a4479f871b3 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java @@ -23,19 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.velocity.command; +package org.geysermc.geyser.item.components; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import com.nukkitx.nbt.NbtMap; -public class GeyserVelocityCommandManager extends CommandManager { +import java.util.Locale; - public GeyserVelocityCommandManager(GeyserImpl geyser) { - super(geyser); +public enum WearableSlot { + HEAD, + CHEST, + LEGS, + FEET; + + private final NbtMap slotNbt; + + WearableSlot() { + this.slotNbt = NbtMap.builder().putString("slot", "slot.armor." + this.name().toLowerCase(Locale.ROOT)).build(); } - @Override - public String getDescription(String command) { - return ""; // no support for command descriptions in velocity + public NbtMap getSlotNbt() { + return slotNbt; } } diff --git a/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java new file mode 100644 index 00000000000..5878f5cc78a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.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.item.exception; + +public class InvalidCustomMappingsFileException extends Exception { + public InvalidCustomMappingsFileException(Throwable cause) { + super(cause); + } + + public InvalidCustomMappingsFileException(String message) { + super(message); + } + + public InvalidCustomMappingsFileException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/mappings/MappingsConfigReader.java b/core/src/main/java/org/geysermc/geyser/item/mappings/MappingsConfigReader.java new file mode 100644 index 00000000000..eaf07c382e8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/mappings/MappingsConfigReader.java @@ -0,0 +1,98 @@ +/* + * 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.item.mappings; + +import com.fasterxml.jackson.databind.JsonNode; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.item.mappings.versions.MappingsReader; +import org.geysermc.geyser.item.mappings.versions.MappingsReader_v1; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.BiConsumer; + +public class MappingsConfigReader { + private final Int2ObjectMap mappingReaders = new Int2ObjectOpenHashMap<>(); + private final Path customMappingsDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("custom_mappings"); + + public MappingsConfigReader() { + this.mappingReaders.put(1, new MappingsReader_v1()); + } + + public Path[] getCustomMappingsFiles() { + try { + return Files.walk(this.customMappingsDirectory) + .filter(child -> child.toString().endsWith(".json")) + .toArray(Path[]::new); + } catch (IOException e) { + return new Path[0]; + } + } + + public void loadMappingsFromJson(BiConsumer consumer) { + Path customMappingsDirectory = this.customMappingsDirectory; + if (!Files.exists(customMappingsDirectory)) { + try { + Files.createDirectories(customMappingsDirectory); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Failed to create custom mappings directory", e); + return; + } + } + + Path[] mappingsFiles = this.getCustomMappingsFiles(); + for (Path mappingsFile : mappingsFiles) { + this.readMappingsFromJson(mappingsFile, consumer); + } + } + + public void readMappingsFromJson(Path file, BiConsumer consumer) { + JsonNode mappingsRoot; + try { + mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile()); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e); + return; + } + + if (!mappingsRoot.has("format_version")) { + GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " is missing the format version field!"); + return; + } + + int formatVersion = mappingsRoot.get("format_version").asInt(); + if (!this.mappingReaders.containsKey(formatVersion)) { + GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion); + return; + } + + this.mappingReaders.get(formatVersion).readMappings(file, mappingsRoot, consumer); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader.java new file mode 100644 index 00000000000..ef553f488ba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader.java @@ -0,0 +1,93 @@ +/* + * 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.item.mappings.versions; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; + +import java.nio.file.Path; +import java.util.function.BiConsumer; + +public abstract class MappingsReader { + public abstract void readMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); + + public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; + + protected CustomRenderOffsets fromJsonNode(JsonNode node) { + if (node == null || !node.isObject()) { + return null; + } + + return new CustomRenderOffsets( + getHandOffsets(node, "main_hand"), + getHandOffsets(node, "off_hand") + ); + } + + protected CustomRenderOffsets.Hand getHandOffsets(JsonNode node, String hand) { + JsonNode tmpNode = node.get(hand); + if (tmpNode == null || !tmpNode.isObject()) { + return null; + } + + return new CustomRenderOffsets.Hand( + getPerspectiveOffsets(tmpNode, "first_person"), + getPerspectiveOffsets(tmpNode, "third_person") + ); + } + + protected CustomRenderOffsets.Offset getPerspectiveOffsets(JsonNode node, String perspective) { + JsonNode tmpNode = node.get(perspective); + if (tmpNode == null || !tmpNode.isObject()) { + return null; + } + + return new CustomRenderOffsets.Offset( + getOffsetXYZ(tmpNode, "position"), + getOffsetXYZ(tmpNode, "rotation"), + getOffsetXYZ(tmpNode, "scale") + ); + } + + protected CustomRenderOffsets.OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { + JsonNode tmpNode = node.get(offsetType); + if (tmpNode == null || !tmpNode.isObject()) { + return null; + } + + if (!tmpNode.has("x") || !tmpNode.has("y") || !tmpNode.has("z")) { + return null; + } + + return new CustomRenderOffsets.OffsetXYZ( + tmpNode.get("x").floatValue(), + tmpNode.get("y").floatValue(), + tmpNode.get("z").floatValue() + ); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader_v1.java new file mode 100644 index 00000000000..217ff844e1c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader_v1.java @@ -0,0 +1,123 @@ +/* + * 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.item.mappings.versions; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; + +import java.nio.file.Path; +import java.util.function.BiConsumer; + +public class MappingsReader_v1 extends MappingsReader { + @Override + public void readMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + this.readItemMappings(file, mappingsRoot, consumer); + } + + public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + JsonNode itemsNode = mappingsRoot.get("items"); + + if (itemsNode != null && itemsNode.isObject()) { + itemsNode.fields().forEachRemaining(entry -> { + if (entry.getValue().isArray()) { + entry.getValue().forEach(data -> { + try { + CustomItemData customItemData = this.readItemMappingEntry(data); + consumer.accept(entry.getKey(), customItemData); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error in custom mapping file: " + file.toString(), e); + } + }); + } + }); + } + } + + private CustomItemOptions readItemCustomItemOptions(JsonNode node) { + CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder(); + + JsonNode customModelData = node.get("custom_model_data"); + if (customModelData != null && customModelData.isInt()) { + customItemOptions.customModelData(customModelData.asInt()); + } + + JsonNode damagePredicate = node.get("damage_predicate"); + if (damagePredicate != null && damagePredicate.isInt()) { + customItemOptions.damagePredicate(damagePredicate.asInt()); + } + + JsonNode unbreakable = node.get("unbreakable"); + if (unbreakable != null && unbreakable.isBoolean()) { + customItemOptions.unbreakable(unbreakable.asBoolean()); + } + + return customItemOptions.build(); + } + + @Override + public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { + if (node == null || !node.isObject()) { + throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); + } + + String name = node.get("name").asText(); + if (name == null || name.isEmpty()) { + throw new InvalidCustomMappingsFileException("An item entry has no name"); + } + + CustomItemData.Builder customItemData = CustomItemData.builder() + .name(name) + .customItemOptions(this.readItemCustomItemOptions(node)); + + //The next entries are optional + if (node.has("display_name")) { + customItemData.displayName(node.get("display_name").asText()); + } + + if (node.has("icon")) { + customItemData.icon(node.get("icon").asText()); + } + + if (node.has("allow_offhand")) { + customItemData.allowOffhand(node.get("allow_offhand").asBoolean()); + } + + if (node.has("texture_size")) { + customItemData.textureSize(node.get("texture_size").asInt()); + } + + if (node.has("render_offsets")) { + JsonNode tmpNode = node.get("render_offsets"); + + customItemData.renderOffsets(fromJsonNode(tmpNode)); + } + + return customItemData.build(); + } +} 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 9f12128752c..9bb31799682 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 be647cff6cd..015f9c50c75 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 @@ * 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 10091779305..f19060c65c2 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.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 boolean shouldExpectLecternHandled() { @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 int getGameRuleInt(GeyserSession session, GameRule gameRule) { 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 5f3c96b86d7..a3f6b55e4c2 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 @@ 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 @@ 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 69f5d5beb20..e10981f4bb2 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.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 final int getBlockAt(GeyserSession session, Vector3i vector) { * @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 final int getBlockAt(GeyserSession session, Vector3i vector) { * @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 final int getBlockAt(GeyserSession session, Vector3i vector) { * @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 final int getBlockAt(GeyserSession session, Vector3i vector) { * @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 final int getBlockAt(GeyserSession session, Vector3i vector) { * @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/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index e4772df80bf..58cbce77f9a 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -29,11 +29,11 @@ import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntityTranslator; -import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.level.physics.PistonBehavior; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntityTranslator; import org.geysermc.geyser.util.collection.FixedInt2ByteMap; import org.geysermc.geyser.util.collection.FixedInt2IntMap; import org.geysermc.geyser.util.collection.LecternHasBookMap; 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 d2f3e7c9e35..d95767b976a 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 BitArray createArray(int size, int[] words) { // 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 6e37749df00..ce25ead9559 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 void writeSizeToNetwork(ByteBuf buffer, int size) { @Override public int[] getWords() { - return EMPTY_ARRAY; + return IntArrays.EMPTY_ARRAY; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java index 108982a32fd..d6913d6c00e 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java @@ -26,7 +26,9 @@ package org.geysermc.geyser.level.physics; import com.nukkitx.math.vector.Vector3d; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; @Data @AllArgsConstructor diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index a91c2b083de..2a830cd7065 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -33,15 +33,15 @@ import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.level.block.BlockPositionIterator; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.PistonCache; import org.geysermc.geyser.translator.collision.BlockCollision; import org.geysermc.geyser.translator.collision.ScaffoldingCollision; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.level.block.BlockPositionIterator; import org.geysermc.geyser.util.BlockUtils; import java.text.DecimalFormat; 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 d41871cdbff..49fe6c42ddb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -28,18 +28,19 @@ 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; import io.netty.channel.socket.DatagramPacket; import io.netty.util.concurrent.DefaultThreadFactory; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.text.MessageTranslator; import javax.annotation.Nonnull; import java.net.InetSocketAddress; @@ -52,7 +53,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { /* The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client */ - private static final int MINECRAFT_VERSION_BYTES_LENGTH = MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length; + private static final int MINECRAFT_VERSION_BYTES_LENGTH = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length; private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length; /** * The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length. @@ -84,14 +85,16 @@ public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { } } - 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(); @@ -106,9 +109,9 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { pong.setEdition("MCPE"); pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59 pong.setNintendoLimited(false); - pong.setProtocolVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); - pong.setVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. - pong.setIpv4Port(config.getBedrock().getPort()); + pong.setProtocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); + pong.setVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. + pong.setIpv4Port(config.getBedrock().port()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); @@ -118,17 +121,13 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { pong.setMotd(mainMotd.trim()); pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. } else { - pong.setMotd(config.getBedrock().getMotd1()); - pong.setSubMotd(config.getBedrock().getMotd2()); + pong.setMotd(config.getBedrock().primaryMotd()); + 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 BedrockPong onQuery(InetSocketAddress inetSocketAddress) { } } + 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 BedrockPong onQuery(InetSocketAddress inetSocketAddress) { @Override public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { try { - bedrockServerSession.setPacketCodec(MinecraftProtocol.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/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java similarity index 75% rename from core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java rename to core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 391bff65f41..6b46f805641 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -28,24 +28,26 @@ 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; /** * Contains information about the supported protocols in Geyser. */ -public final class MinecraftProtocol { +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,11 +60,18 @@ public final class MinecraftProtocol { 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.10/1.19.11") + .minecraftVersion("1.19.50/1.19.51") .build()); } @@ -82,8 +91,12 @@ public static BedrockPacketCodec getBedrockCodec(int protocolVersion) { /* 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(); } /** @@ -101,7 +114,7 @@ public static PacketCodec getJavaCodec() { * @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()); } /** @@ -113,6 +126,15 @@ public static int getJavaProtocolVersion() { return DEFAULT_JAVA_CODEC.getProtocolVersion(); } + /** + * Gets the supported Minecraft: Java Edition version. + * + * @return the supported Minecraft: Java Edition version + */ + public static String getJavaMinecraftVersion() { + return DEFAULT_JAVA_CODEC.getMinecraftVersion(); + } + /** * @return a string showing all supported Bedrock versions for this Geyser instance */ @@ -137,6 +159,6 @@ public static String getAllSupportedJavaVersions() { return joiner.toString(); } - private MinecraftProtocol() { + private GameProtocol() { } } 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 b0b707ee055..8d2db081aca 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 boolean handle(ItemComponentPacket packet) { 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/QueryPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java index 0caa6fac7b7..d7daa926047 100644 --- a/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java @@ -153,7 +153,7 @@ private byte[] getGameData() { String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { - motd = geyser.getConfig().getBedrock().getMotd1(); + motd = geyser.getConfig().getBedrock().primaryMotd(); } // If passthrough player counts is enabled lets get players from the server @@ -177,13 +177,13 @@ private byte[] getGameData() { gameData.put("hostname", motd); gameData.put("gametype", "SMP"); gameData.put("game_id", "MINECRAFT"); - gameData.put("version", GeyserImpl.NAME + " (" + GeyserImpl.GIT_VERSION + ") " + MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); + gameData.put("version", GeyserImpl.NAME + " (" + GeyserImpl.GIT_VERSION + ") " + GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); gameData.put("plugins", ""); gameData.put("map", map); gameData.put("numplayers", currentPlayerCount); gameData.put("maxplayers", maxPlayerCount); - gameData.put("hostport", String.valueOf(geyser.getConfig().getBedrock().getPort())); - gameData.put("hostip", geyser.getConfig().getBedrock().getAddress()); + gameData.put("hostport", String.valueOf(geyser.getConfig().getBedrock().port())); + gameData.put("hostip", geyser.getConfig().getBedrock().address()); try { writeString(query, "GeyserMC"); 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 5ae6fbca987..227f0ed5ac3 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -28,9 +28,11 @@ 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; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.pack.ResourcePackManifest; @@ -38,16 +40,19 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoginEncryptionUtils; 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 @@ boolean defaultHandler(BedrockPacket packet) { 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 boolean handle(LoginPacket loginPacket) { return true; } - BedrockPacketCodec packetCodec = MinecraftProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); - if (packetCodec == null) { - String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions(); - if (loginPacket.getProtocolVersion() > MinecraftProtocol.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() < MinecraftProtocol.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,12 +142,12 @@ public boolean handle(LoginPacket loginPacket) { 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()); session.sendUpstreamPacket(resourcePacksInfo); - GeyserLocale.loadGeyserLocale(session.getLocale()); + GeyserLocale.loadGeyserLocale(session.locale()); return true; } @@ -119,7 +155,7 @@ public boolean handle(LoginPacket loginPacket) { public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - if (geyser.getConfig().getRemote().getAuthType() != AuthType.ONLINE) { + if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) { session.authenticate(session.getAuthData().name()); } else if (!couldLoginUserByName(session.getAuthData().name())) { // We must spawn the white world @@ -129,24 +165,8 @@ public boolean handle(ResourcePackClientResponsePacket packet) { 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: @@ -160,7 +180,7 @@ public boolean handle(ResourcePackClientResponsePacket packet) { stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); } - if (session.getItemMappings().getFurnaceMinecartData() != null) { + if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { // Allow custom items to work stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } @@ -216,7 +236,7 @@ public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { SetTitlePacket titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(GeyserLocale.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); + titlePacket.setText(GeyserLocale.getPlayerLocaleString("geyser.auth.login.wait", session.locale())); titlePacket.setFadeInTime(0); titlePacket.setFadeOutTime(1); titlePacket.setStayTime(2); @@ -239,7 +259,8 @@ public boolean handle(ResourcePackChunkRequestPacket packet) { 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 boolean handle(ResourcePackChunkRequestPacket packet) { 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/network/netty/LocalSession.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java index 0781a04b285..370604db960 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java @@ -28,7 +28,9 @@ import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.codec.PacketCodecHelper; import com.github.steveice10.packetlib.packet.PacketProtocol; -import com.github.steveice10.packetlib.tcp.*; +import com.github.steveice10.packetlib.tcp.TcpPacketCodec; +import com.github.steveice10.packetlib.tcp.TcpPacketSizer; +import com.github.steveice10.packetlib.tcp.TcpSession; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.*; 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 d9f1e36f56e..6df1a0c0e44 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -25,13 +25,21 @@ package org.geysermc.geyser.pack; +import lombok.Getter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent; 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.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -55,20 +63,40 @@ 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 */ public static void loadPacks() { - File directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile(); + Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs"); - if (!directory.exists()) { - directory.mkdir(); + if (!Files.exists(directory)) { + try { + Files.createDirectory(directory); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e); + } // As we just created the directory it will be empty return; } - for (File file : directory.listFiles()) { + List resourcePacks; + try { + resourcePacks = Files.walk(directory).collect(Collectors.toList()); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e); + return; + } + + GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks); + GeyserImpl.getInstance().eventBus().fire(event); + + for (Path path : event.resourcePacks()) { + File file = path.toFile(); + if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) { ResourcePack pack = new ResourcePack(); @@ -97,6 +125,11 @@ public static void loadPacks() { } } }); + + // 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/ping/GeyserLegacyPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java index 7db571be068..a69d9bc3ef5 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -32,7 +32,7 @@ import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; import io.netty.util.NetUtil; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -77,14 +77,14 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { @Override public void run() { try (Socket socket = new Socket()) { - String address = geyser.getConfig().getRemote().getAddress(); - int port = geyser.getConfig().getRemote().getPort(); + String address = geyser.getConfig().getRemote().address(); + int port = geyser.getConfig().getRemote().port(); socket.connect(new InetSocketAddress(address, port), 5000); ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); try (DataOutputStream handshake = new DataOutputStream(byteArrayStream)) { handshake.write(0x0); - VarInts.writeUnsignedInt(handshake, MinecraftProtocol.getJavaProtocolVersion()); + VarInts.writeUnsignedInt(handshake, GameProtocol.getJavaProtocolVersion()); VarInts.writeUnsignedInt(handshake, address.length()); handshake.writeBytes(address); handshake.writeShort(port); 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 fc4e3d02241..70a3da88e6d 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 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 V get(K key) { * * @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/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java index 3f7d88031ee..bf412bfaf84 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -31,10 +31,10 @@ import io.netty.channel.EventLoop; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.loader.RegistryLoaders; -import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.protocol.PacketTranslator; import java.util.Collections; import java.util.IdentityHashMap; 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 8f2a9775ad8..866cbd29180 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.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; @@ -47,6 +48,7 @@ import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; +import org.geysermc.geyser.registry.provider.ProviderSupplier; import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ParticleMapping; @@ -57,10 +59,7 @@ import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; import org.geysermc.geyser.translator.sound.SoundTranslator; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Holds all the common registries in Geyser. @@ -138,6 +137,11 @@ public final class Registries { */ public static final SimpleRegistry> POTION_MIXES; + /** + * A registry holding all the + */ + public static final SimpleMappedRegistry, ProviderSupplier> PROVIDERS = SimpleMappedRegistry.create(new IdentityHashMap<>(), ProviderRegistryLoader::new); + /** * A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value. */ @@ -176,5 +180,18 @@ public static void init() { // 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/CollisionRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java index b74573a4ee6..69ad16743e4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java @@ -34,12 +34,12 @@ import lombok.AllArgsConstructor; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.level.physics.BoundingBox; -import org.geysermc.geyser.translator.collision.CollisionRemapper; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.collision.CollisionRemapper; import org.geysermc.geyser.translator.collision.OtherCollision; import org.geysermc.geyser.translator.collision.SolidCollision; -import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.util.FileUtils; import java.io.InputStream; diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java index e566ff37cb5..8ad09bf8884 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java @@ -30,7 +30,7 @@ import it.unimi.dsi.fastutil.ints.IntSet; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.ItemMapping; @@ -77,7 +77,7 @@ public Map load(String input) { IntSet validItems = new IntOpenHashSet(); for (JsonNode itemNode : node.get("valid_items")) { String javaIdentifier = itemNode.textValue(); - ItemMapping itemMapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); + ItemMapping itemMapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); if (itemMapping != null) { validItems.add(itemMapping.getJavaId()); } else { diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java index b0a9c72b2ac..8d40edac3db 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java @@ -26,10 +26,10 @@ package org.geysermc.geyser.registry.loader; import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.inventory.item.Potion; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.inventory.item.Potion; import java.util.ArrayList; import java.util.HashSet; @@ -103,7 +103,7 @@ public Set load(Object input) { } private static ItemMapping getNonNull(String javaIdentifier) { - ItemMapping itemMapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); + ItemMapping itemMapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); if (itemMapping == null) throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier); 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 new file mode 100644 index 00000000000..99a9213feb1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import org.geysermc.geyser.api.command.Command; +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; +import org.geysermc.geyser.registry.provider.ProviderSupplier; + +import java.util.Map; + +/** + * Registers the provider data from the provider. + */ +public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>, Map, ProviderSupplier>> { + + @Override + public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { + providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); + 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/loader/SoundTranslatorRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java index 359cd112e51..558864b3539 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.registry.loader; -import org.geysermc.geyser.translator.sound.SoundTranslator; import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; +import org.geysermc.geyser.translator.sound.SoundTranslator; import java.util.function.Function; 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 53c3e2310b4..cbab0399020 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 @@ -30,6 +30,8 @@ import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; +import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; +import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -56,17 +58,7 @@ /** * 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); - - BLOCK_MAPPERS = stateMapperBuilder.build(); - } - +public final class BlockRegistryPopulator { /** * Stores the raw blocks JSON until it is no longer needed. */ @@ -80,7 +72,13 @@ public static void populate() { } private static void registerBedrockBlocks() { - for (Map.Entry, BiFunction> palette : BLOCK_MAPPERS.entrySet()) { + 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()) { NbtList blocksTag; try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) { @@ -95,7 +93,9 @@ private static void registerBedrockBlocks() { int stateVersion = -1; for (int i = 0; i < blocksTag.size(); i++) { - NbtMap tag = blocksTag.get(i); + NbtMapBuilder builder = blocksTag.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); } @@ -111,7 +111,7 @@ private static void registerBedrockBlocks() { int movingBlockRuntimeId = -1; Iterator> blocksIterator = BLOCKS_JSON.fields(); - BiFunction stateMapper = BLOCK_MAPPERS.getOrDefault(palette.getKey(), EMPTY_MAPPER); + BiFunction stateMapper = blockMappers.getOrDefault(palette.getKey(), emptyMapper); int[] javaToBedrockBlocks = new int[BLOCKS_JSON.size()]; @@ -231,7 +231,7 @@ private static void registerJavaBlocks() { 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 new file mode 100644 index 00000000000..e32030db6a0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.nukkitx.nbt.NbtMap; +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 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; +import org.geysermc.geyser.registry.type.GeyserMappingItem; +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; + +public class CustomItemRegistryPopulator { + public static GeyserCustomMappingData registerCustomItem(String customItemName, GeyserMappingItem javaItem, CustomItemData customItemData, int bedrockId) { + StartGamePacket.ItemEntry startGamePacketItemEntry = new StartGamePacket.ItemEntry(customItemName, (short) bedrockId, true); + + NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, customItemName, bedrockId); + ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); + + return new GeyserCustomMappingData(componentItemData, startGamePacketItemEntry, customItemName, bedrockId); + } + + static boolean initialCheck(String identifier, CustomItemData item, Map mappings) { + if (!mappings.containsKey(identifier)) { + GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name()); + return false; + } + 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; + } + + public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId) { + String customIdentifier = customItemData.identifier(); + + ItemMapping customItemMapping = ItemMapping.builder() + .javaIdentifier(customIdentifier) + .bedrockIdentifier(customIdentifier) + .javaId(customItemData.javaId()) + .bedrockId(customItemId) + .bedrockData(0) + .bedrockBlockId(0) + .stackSize(customItemData.stackSize()) + .toolType(customItemData.toolType()) + .toolTier(customItemData.toolTier()) + .translationString(customItemData.translationString()) + .maxDamage(customItemData.maxDamage()) + .repairMaterials(customItemData.repairMaterials()) + .hasSuspiciousStewEffect(false) + .customItemOptions(Collections.emptyList()) + .build(); + + NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, + customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.isTool()); + ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build()); + + return new NonVanillaItemRegistration(componentItemData, customItemMapping); + } + + private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, GeyserMappingItem mapping, + String customItemName, int customItemId) { + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", customItemName) + .putInt("id", customItemId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + NbtMapBuilder componentBuilder = NbtMap.builder(); + + setupBasicItemInfo(mapping.getMaxDamage(), mapping.getStackSize(), mapping.getToolType() != null, customItemData, itemProperties, componentBuilder); + + boolean canDestroyInCreative = true; + if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. + canDestroyInCreative = computeToolProperties(mapping.getToolTier(), mapping.getToolType(), itemProperties, componentBuilder); + } + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + + if (mapping.getArmorType() != null) { + 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()); + builder.putCompound("components", componentBuilder.build()); + + return builder; + } + + private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, + int customItemId, OptionalInt creativeCategory, + String creativeGroup, boolean isHat, boolean isTool) { + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", customItemName) + .putInt("id", customItemId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + NbtMapBuilder componentBuilder = NbtMap.builder(); + + setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), isTool, customItemData, itemProperties, componentBuilder); + + boolean canDestroyInCreative = true; + if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. + canDestroyInCreative = computeToolProperties(customItemData.toolTier(), customItemData.toolType(), itemProperties, componentBuilder); + } + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + + String armorType = customItemData.armorType(); + if (armorType != null) { + computeArmorProperties(armorType, customItemData.protectionValue(), componentBuilder); + } + + computeRenderOffsets(isHat, customItemData, componentBuilder); + + if (creativeGroup != null) { + itemProperties.putString("creative_group", creativeGroup); + } + if (creativeCategory.isPresent()) { + itemProperties.putInt("creative_category", creativeCategory.getAsInt()); + } + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + + return builder; + } + + private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean isTool, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + itemProperties.putCompound("minecraft:icon", NbtMap.builder() + .putString("texture", customItemData.icon()) + .build()); + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build()); + + itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); + itemProperties.putBoolean("hand_equipped", isTool); + itemProperties.putInt("max_stack_size", stackSize); + // 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) + .putInt("min", 1) + .build()) + .putInt("max_durability", maxDamage) + .build()); + itemProperties.putBoolean("use_duration", true); + } + } + + /** + * @return can destroy in creative + */ + private static boolean computeToolProperties(String toolTier, String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + boolean canDestroyInCreative = true; + float miningSpeed = 1.0f; + + if (toolType.equals("shears")) { + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShearsDigger(15)); + } else { + int toolSpeed = ToolBreakSpeedsUtils.toolTierToSpeed(toolTier); + switch (toolType) { + case "sword" -> { + miningSpeed = 1.5f; + canDestroyInCreative = false; + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getSwordDigger(toolSpeed)); + componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY); + } + case "pickaxe" -> { + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getPickaxeDigger(toolSpeed, toolTier)); + setItemTag(componentBuilder, "pickaxe"); + } + case "axe" -> { + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getAxeDigger(toolSpeed)); + setItemTag(componentBuilder, "axe"); + } + case "shovel" -> { + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShovelDigger(toolSpeed)); + setItemTag(componentBuilder, "shovel"); + } + case "hoe" -> { + componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getHoeDigger(toolSpeed)); + setItemTag(componentBuilder, "hoe"); + } + } + } + + itemProperties.putBoolean("hand_equipped", true); + itemProperties.putFloat("mining_speed", miningSpeed); + + return canDestroyInCreative; + } + + private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder componentBuilder) { + switch (armorType) { + case "boots" -> { + componentBuilder.putString("minecraft:render_offsets", "boots"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + } + case "chestplate" -> { + componentBuilder.putString("minecraft:render_offsets", "chestplates"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + } + case "leggings" -> { + componentBuilder.putString("minecraft:render_offsets", "leggings"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + } + case "helmet" -> { + componentBuilder.putString("minecraft:render_offsets", "helmets"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + } + } + } + + 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"); + componentBuilder.putString("minecraft:render_offsets", "helmets"); + + componentBuilder.remove("minecraft:wearable"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + } + + CustomRenderOffsets renderOffsets = customItemData.renderOffsets(); + if (renderOffsets != null) { + componentBuilder.remove("minecraft:render_offsets"); + componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); + } else if (customItemData.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { + float scale1 = (float) (0.075 / (customItemData.textureSize() / 16f)); + float scale2 = (float) (0.125 / (customItemData.textureSize() / 16f)); + float scale3 = (float) (0.075 / (customItemData.textureSize() / 16f * 2.4f)); + + componentBuilder.putCompound("minecraft:render_offsets", + NbtMap.builder().putCompound("main_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) + .putCompound("off_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); + } + } + + private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { + NbtMapBuilder builder = NbtMap.builder(); + + CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand(); + if (mainHand != null) { + NbtMap nbt = toNbtMap(mainHand); + if (nbt != null) { + builder.putCompound("main_hand", nbt); + } + } + CustomRenderOffsets.Hand offhand = renderOffsets.offhand(); + if (offhand != null) { + NbtMap nbt = toNbtMap(offhand); + if (nbt != null) { + builder.putCompound("off_hand", nbt); + } + } + + return builder.build(); + } + + private static NbtMap toNbtMap(CustomRenderOffsets.Hand hand) { + NbtMap firstPerson = toNbtMap(hand.firstPerson()); + NbtMap thirdPerson = toNbtMap(hand.thirdPerson()); + + if (firstPerson == null && thirdPerson == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (firstPerson != null) { + builder.putCompound("first_person", firstPerson); + } + if (thirdPerson != null) { + builder.putCompound("third_person", thirdPerson); + } + + return builder.build(); + } + + private static NbtMap toNbtMap(@Nullable CustomRenderOffsets.Offset offset) { + if (offset == null) { + return null; + } + + CustomRenderOffsets.OffsetXYZ position = offset.position(); + CustomRenderOffsets.OffsetXYZ rotation = offset.rotation(); + CustomRenderOffsets.OffsetXYZ scale = offset.scale(); + + if (position == null && rotation == null && scale == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (position != null) { + builder.putList("position", NbtType.FLOAT, toList(position)); + } + if (rotation != null) { + builder.putList("rotation", NbtType.FLOAT, toList(rotation)); + } + if (scale != null) { + builder.putList("scale", NbtType.FLOAT, toList(scale)); + } + + return builder.build(); + } + + private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { + return List.of(xyz.x(), xyz.y(), xyz.z()); + } + + private static void setItemTag(NbtMapBuilder builder, String tag) { + builder.putList("item_tags", NbtType.STRING, List.of("minecraft:is_" + tag)); + } + + private static NbtMap xyzToScaleList(float x, float y, float z) { + return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 5dc60b44b48..4b218aa7dd9 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 @@ -27,6 +27,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; @@ -35,15 +37,25 @@ 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; import it.unimi.dsi.fastutil.objects.*; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +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; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.*; @@ -65,9 +77,8 @@ private record PaletteVersion(int protocolVersion, Map additiona public static void populate() { Map paletteVersions = new Object2ObjectOpenHashMap<>(); - paletteVersions.put("1_19_0", new PaletteVersion(Bedrock_v527.V527_CODEC.getProtocolVersion(), - Collections.singletonMap("minecraft:trader_llama_spawn_egg", "minecraft:llama_spawn_egg"))); - paletteVersions.put("1_19_10", new PaletteVersion(Bedrock_v534.V534_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(); @@ -81,6 +92,61 @@ public static void populate() { throw new AssertionError("Unable to load Java runtime item IDs", e); } + boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + + // 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(); + if (customItemsAllowed) { + // Load custom items from mappings files + mappingsConfigReader.loadMappingsFromJson((key, item) -> { + if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { + customItems.get(key).add(item); + } + }); + + nonVanillaCustomItems = new ObjectArrayList<>(); + GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { + @Override + public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { + if (CustomItemRegistryPopulator.initialCheck(identifier, customItemData, items)) { + customItems.get(identifier).add(customItemData); + return true; + } + return false; + } + + @Override + public boolean register(@NonNull NonVanillaCustomItemData customItemData) { + if (customItemData.identifier().startsWith("minecraft:")) { + GeyserImpl.getInstance().getLogger().error("The custom item " + customItemData.identifier() + + " is attempting to masquerade as a vanilla Minecraft item!"); + return false; + } + + if (customItemData.javaId() < items.size()) { + // Attempting to overwrite an item that already exists in the protocol + GeyserImpl.getInstance().getLogger().error("The custom item " + customItemData.identifier() + + " is attempting to overwrite a vanilla Minecraft item!"); + return false; + } + nonVanillaCustomItems.add(customItemData); + return true; + } + }); + } else { + nonVanillaCustomItems = Collections.emptyList(); + } + + int customItemCount = customItems.size() + nonVanillaCustomItems.size(); + if (customItemCount > 0) { + GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); + } + // We can reduce some operations as Java information is the same across all palette versions boolean firstMappingsPass = true; Int2IntMap dyeColors = new FixedInt2IntMap(); @@ -102,11 +168,20 @@ public static void populate() { throw new AssertionError("Unable to load Bedrock runtime item IDs", e); } + // Used for custom items + int nextFreeBedrockId = 0; + List componentItemData = new ObjectArrayList<>(); + Map entries = new Object2ObjectOpenHashMap<>(); for (PaletteItem entry : itemEntries) { - entries.put(entry.getName(), new StartGamePacket.ItemEntry(entry.getName(), (short) entry.getId())); - bedrockIdentifierToId.put(entry.getName(), entry.getId()); + int id = entry.getId(); + if (id >= nextFreeBedrockId) { + nextFreeBedrockId = id + 1; + } + + entries.put(entry.getName(), new StartGamePacket.ItemEntry(entry.getName(), (short) id)); + bedrockIdentifierToId.put(entry.getName(), id); } Object2IntMap bedrockBlockIdOverrides = new Object2IntOpenHashMap<>(); @@ -209,17 +284,19 @@ public static void populate() { int itemIndex = 0; int javaFurnaceMinecartId = 0; - boolean usingFurnaceMinecart = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); Set javaOnlyItems = new ObjectOpenHashSet<>(); Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:bundle"); - if (!usingFurnaceMinecart) { + if (!customItemsAllowed) { javaOnlyItems.add("minecraft:furnace_minecart"); } // Java-only items for this version javaOnlyItems.addAll(palette.getValue().additionalTranslatedItems().keySet()); + Int2ObjectMap customIdMappings = new Int2ObjectOpenHashMap<>(); + Set registeredItemNames = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names + for (Map.Entry entry : items.entrySet()) { String javaIdentifier = entry.getKey().intern(); GeyserMappingItem mappingItem; @@ -231,7 +308,7 @@ public static void populate() { mappingItem = entry.getValue(); } - if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { + if (customItemsAllowed && javaIdentifier.equals("minecraft:furnace_minecart")) { javaFurnaceMinecartId = itemIndex; itemIndex++; // Will be added later @@ -385,12 +462,49 @@ public static void populate() { .toolTier(""); } } + if (javaOnlyItems.contains(javaIdentifier)) { // These items don't exist on Bedrock, so set up a variable that indicates they should have custom names mappingBuilder = mappingBuilder.translationString((bedrockBlockId != -1 ? "block." : "item.") + entry.getKey().replace(":", ".")); GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); } + // Add the custom item properties, if applicable + List> customItemOptions; + Collection customItemsToLoad = customItems.get(javaIdentifier); + if (customItemsAllowed && !customItemsToLoad.isEmpty()) { + customItemOptions = new ObjectArrayList<>(customItemsToLoad.size()); + + for (CustomItemData customItem : customItemsToLoad) { + int customProtocolId = nextFreeBedrockId++; + + String customItemName = "geyser_custom:" + customItem.name(); + if (!registeredItemNames.add(customItemName)) { + if (firstMappingsPass) { + GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItem.name() + "' already exists and was registered again! Skipping..."); + } + continue; + } + + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( + customItemName, mappingItem, customItem, customProtocolId + ); + // StartGamePacket entry - needed for Bedrock to recognize the item through the protocol + entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry()); + // ComponentItemData - used to register some custom properties + componentItemData.add(customMapping.componentItemData()); + 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 = Collections.emptyList(); + } + mappingBuilder.customItemOptions(customItemOptions); + ItemMapping mapping = mappingBuilder.build(); if (javaIdentifier.contains("boat")) { @@ -441,12 +555,12 @@ public static void populate() { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) + .customItemOptions(Collections.emptyList()) .build(); - ComponentItemData furnaceMinecartData = null; - if (usingFurnaceMinecart) { + if (customItemsAllowed) { // Add the furnace minecart as a custom item - int furnaceMinecartId = mappings.size() + 1; + int furnaceMinecartId = nextFreeBedrockId++; entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); @@ -458,10 +572,11 @@ public static void populate() { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) + .customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() - .netId(netId) + .netId(netId++) .id(furnaceMinecartId) .count(1).build()); @@ -497,7 +612,36 @@ public static void populate() { componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); - furnaceMinecartData = new ComponentItemData("geysermc:furnace_minecart", builder.build()); + componentItemData.add(new ComponentItemData("geysermc:furnace_minecart", builder.build())); + + // Register any completely custom items given to us + IntSet registeredJavaIds = new IntOpenHashSet(); // Used to check for duplicate item java ids + for (NonVanillaCustomItemData customItem : nonVanillaCustomItems) { + if (!registeredJavaIds.add(customItem.javaId())) { + if (firstMappingsPass) { + GeyserImpl.getInstance().getLogger().error("Custom item java id " + customItem.javaId() + " already exists and was registered again! Skipping..."); + } + continue; + } + + int customItemId = nextFreeBedrockId++; + NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId); + + componentItemData.add(registration.componentItemData()); + ItemMapping mapping = registration.mapping(); + while (mapping.getJavaId() >= mappings.size()) { + // Fill with empty to get to the correct size + mappings.add(ItemMapping.AIR); + } + mappings.set(mapping.getJavaId(), mapping); + + if (customItem.creativeGroup() != null || customItem.creativeCategory().isPresent()) { + creativeItems.add(ItemData.builder() + .id(customItemId) + .netId(netId++) + .count(1).build()); + } + } } ItemMappings itemMappings = ItemMappings.builder() @@ -511,8 +655,9 @@ public static void populate() { .boatIds(boats) .spawnEggIds(spawnEggs) .carpets(carpets) - .furnaceMinecartData(furnaceMinecartData) + .componentItemData(componentItemData) .lodestoneCompass(lodestoneEntry) + .customIdMappings(customIdMappings) .build(); Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); 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 f0a215f2ab1..6b6bfe9feb4 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.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 static void populate() { 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 @@ private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2Objec /* 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 @@ private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2Objec 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/provider/ProviderSupplier.java b/core/src/main/java/org/geysermc/geyser/registry/provider/ProviderSupplier.java new file mode 100644 index 00000000000..6cb220ce419 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/ProviderSupplier.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.provider; + +public interface ProviderSupplier { + + Object create(Object... args); +} 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 3fadcf5e51f..34cde0acf23 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 @@ -27,8 +27,8 @@ import lombok.Builder; import lombok.Value; -import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.level.physics.PistonBehavior; +import org.geysermc.geyser.util.BlockUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -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 9d06fd3a9bd..480d1095dd1 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 @@ -42,8 +42,12 @@ public class GeyserMappingItem { @JsonProperty("stack_size") int stackSize = 64; @JsonProperty("tool_type") String toolType; @JsonProperty("tool_tier") String toolTier; + @JsonProperty("armor_type") String armorType; + @JsonProperty("protection_value") int protectionValue; @JsonProperty("max_damage") int maxDamage = 0; @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 28d41ba4612..e3d34b0ca72 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,12 +25,15 @@ package org.geysermc.geyser.registry.type; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; -import org.geysermc.geyser.network.MinecraftProtocol; -import org.geysermc.geyser.registry.BlockRegistries; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import java.util.Collections; +import java.util.List; import java.util.Set; @Value @@ -38,8 +41,8 @@ @EqualsAndHashCode public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, - BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, 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; @@ -59,6 +62,9 @@ public class ItemMapping { String translationString; + @NonNull + List> customItemOptions; + int maxDamage; Set repairMaterials; @@ -91,4 +97,4 @@ public boolean hasTranslation() { public boolean isTool() { return this.toolType != null; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java index ef1a8bc7775..ce7ac0b07dc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java @@ -29,6 +29,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Builder; import lombok.Value; @@ -36,7 +37,6 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Set; @@ -67,7 +67,8 @@ public class ItemMappings { IntList spawnEggIds; List carpets; - @Nullable ComponentItemData furnaceMinecartData; + List componentItemData; + Int2ObjectMap customIdMappings; /** * Gets an {@link ItemMapping} from the given {@link ItemStack}. diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/NonVanillaItemRegistration.java b/core/src/main/java/org/geysermc/geyser/registry/type/NonVanillaItemRegistration.java new file mode 100644 index 00000000000..e2063f41a80 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/NonVanillaItemRegistration.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; + +/** + * The return data of a successful registration of a custom item. + */ +public record NonVanillaItemRegistration(ComponentItemData componentItemData, ItemMapping mapping) { +} \ No newline at end of file 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 45ae7eff22d..fed3054b439 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 void run() { 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 ecc2b0c860d..8f9bc394a4a 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.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; @@ -88,8 +87,12 @@ import lombok.Getter; import lombok.NonNull; 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; @@ -98,7 +101,9 @@ import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; @@ -119,7 +124,6 @@ import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.session.cache.*; import org.geysermc.geyser.skin.FloodgateSkinUploader; @@ -133,9 +137,7 @@ 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; @@ -146,15 +148,15 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter -public class GeyserSession implements GeyserConnection, CommandSender { +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; @@ -166,14 +168,9 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private JsonNode certChainData; - /* Setter for GeyserConnect */ + @Accessors(fluent = true) @Setter - private String remoteAddress; - @Setter - private int remotePort; - @Setter - private AuthType remoteAuthType; - /* Setter for GeyserConnect */ + private RemoteServer remoteServer; @Deprecated @Setter @@ -299,6 +296,11 @@ public class GeyserSession implements GeyserConnection, CommandSender { */ @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; @@ -597,9 +599,7 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio disconnect(message); }); - this.remoteAddress = geyser.getConfig().getRemote().getAddress(); - this.remotePort = geyser.getConfig().getRemote().getPort(); - this.remoteAuthType = geyser.getConfig().getRemote().getAuthType(); + this.remoteServer = geyser.defaultRemoteServer(); } /** @@ -612,9 +612,9 @@ public void connect() { // Set the hardcoded shield ID to the ID we just defined in StartGamePacket upstream.getSession().getHardcodedBlockingId().set(this.itemMappings.getStoredItems().shield().getBedrockId()); - if (this.itemMappings.getFurnaceMinecartData() != null) { + if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { ItemComponentPacket componentPacket = new ItemComponentPacket(); - componentPacket.getItems().add(this.itemMappings.getFurnaceMinecartData()); + componentPacket.getItems().addAll(itemMappings.getComponentItemData()); upstream.sendPacket(componentPacket); } @@ -632,6 +632,12 @@ public void connect() { 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); @@ -696,7 +702,7 @@ public void authenticate(String username, String password) { // However, this doesn't affect the final username as Floodgate is still in charge of that. // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear. String validUsername = username; - if (remoteAuthType == AuthType.FLOODGATE) { + if (this.remoteServer.authType() == AuthType.FLOODGATE) { validUsername = username.replace(' ', '_'); } @@ -739,7 +745,7 @@ public void authenticateWithRefreshToken(String refreshToken) { 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; } @@ -751,7 +757,7 @@ public void authenticateWithRefreshToken(String refreshToken) { } 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) { @@ -842,7 +848,7 @@ public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.Authentic connectDownstream(); // Save our refresh token for later use - geyser.saveRefreshToken(name(), service.getRefreshToken()); + geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken()); return true; } } @@ -853,18 +859,18 @@ public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.Authentic * After getting whatever credentials needed, we attempt to join the Java server. */ private void connectDownstream() { - boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE; + boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE; // Start ticking tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); if (geyser.getBootstrap().getSocketAddress() != null) { // We're going to connect through the JVM and not through TCP - downstream = new LocalSession(this.remoteAddress, this.remotePort, + downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(), geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), this.protocol, this.protocol.createHelper()); } else { - downstream = new TcpClientSession(this.remoteAddress, this.remotePort, this.protocol); + downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), this.protocol); disableSrvResolving(); } @@ -944,13 +950,13 @@ public void connected(ConnectedEvent event) { } else { // Connected to an IP address geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect", - authData.name(), protocol.getProfile().getName(), remoteAddress)); + authData.name(), protocol.getProfile().getName(), remoteServer.address())); } UUID uuid = protocol.getProfile().getId(); if (uuid == null) { // Set what our UUID *probably* is going to be - if (remoteAuthType == AuthType.FLOODGATE) { + if (remoteServer.authType() == AuthType.FLOODGATE) { uuid = new UUID(0, Long.parseLong(authData.xuid())); } else { uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + protocol.getProfile().getName()).getBytes(StandardCharsets.UTF_8)); @@ -980,9 +986,9 @@ public void disconnected(DisconnectedEvent event) { String disconnectMessage; Throwable cause = event.getCause(); if (cause instanceof UnexpectedEncryptionException) { - if (remoteAuthType != AuthType.FLOODGATE) { + if (remoteServer.authType() != AuthType.FLOODGATE) { // Server expects online mode - disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", getLocale()); + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale()); // Explain that they may be looking for Floodgate. geyser.getLogger().warning(GeyserLocale.getLocaleStringLog( geyser.getPlatformType() == PlatformType.STANDALONE ? @@ -992,14 +998,14 @@ public void disconnected(DisconnectedEvent event) { )); } else { // Likely that Floodgate is not configured correctly. - disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", getLocale()); + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", locale()); if (geyser.getPlatformType() == PlatformType.STANDALONE) { geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.remote.floodgate_login_error_standalone")); } } } else if (cause instanceof ConnectException) { // Server is offline, probably - disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.server_offline", getLocale()); + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.server_offline", locale()); } else { disconnectMessage = MessageTranslator.convertMessageLenient(event.getReason()); } @@ -1007,7 +1013,7 @@ public void disconnected(DisconnectedEvent event) { if (downstream instanceof LocalSession) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect_internal", authData.name(), disconnectMessage)); } else { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect", authData.name(), remoteAddress, disconnectMessage)); + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect", authData.name(), remoteServer.address(), disconnectMessage)); } if (cause != null) { cause.printStackTrace(); @@ -1045,7 +1051,7 @@ public void disconnect(String reason) { } 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()) { @@ -1075,7 +1081,7 @@ public void executeInEventLoop(Runnable runnable) { 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); } }); } @@ -1088,7 +1094,7 @@ public ScheduledFuture scheduleInEventLoop(Runnable runnable, long duration, 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); } @@ -1314,7 +1320,7 @@ public void activateArmAnimationTicking() { 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; @@ -1331,32 +1337,7 @@ protected void disableSrvResolving() { @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 @@ -1378,7 +1359,7 @@ public boolean isConsole() { } @Override - public String getLocale() { + public String locale() { return clientData.getLanguageCode(); } @@ -1386,14 +1367,14 @@ public String getLocale() { * 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, Collections.emptyList(), null)); + 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.emptyList(), false, Collections.emptyList(), null)); + sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyList(), 0, new BitSet())); } public void setServerRenderDistance(int renderDistance) { @@ -1409,12 +1390,14 @@ public InetSocketAddress getSocketAddress() { 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; } /** @@ -1446,7 +1429,7 @@ private void startGame() { 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); @@ -1475,7 +1458,7 @@ private void startGame() { startGamePacket.setFromWorldTemplate(false); startGamePacket.setWorldTemplateOptionLocked(false); - String serverName = geyser.getConfig().getBedrock().getServerName(); + String serverName = geyser.getConfig().getBedrock().serverName(); startGamePacket.setLevelId(serverName); startGamePacket.setLevelName(serverName); @@ -1483,7 +1466,9 @@ private void startGame() { // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); + startGamePacket.setItemEntries(this.itemMappings.getItemEntries()); + startGamePacket.setVanillaVersion("*"); startGamePacket.setInventoriesServerAuthoritative(true); startGamePacket.setServerEngine(""); // Do we want to fill this in? @@ -1491,6 +1476,8 @@ private void startGame() { startGamePacket.setPlayerPropertyData(NbtMap.EMPTY); startGamePacket.setWorldTemplateId(UUID.randomUUID()); + startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE); + SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); settings.setMovementMode(AuthoritativeMovementMode.CLIENT); settings.setRewindHistorySize(0); @@ -1644,76 +1631,40 @@ public void sendAdventureSettings() { boolean spectator = gameMode == GameMode.SPECTATOR; boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; - if (org.geysermc.geyser.network.MinecraftProtocol.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); - - 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 (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) { - 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); - } + UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); + adventureSettingsPacket.setNoMvP(false); + adventureSettingsPacket.setNoPvM(false); + adventureSettingsPacket.setImmutableWorld(worldImmutable); + adventureSettingsPacket.setShowNameTags(false); + adventureSettingsPacket.setAutoJump(true); + sendUpstreamPacket(adventureSettingsPacket); - 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); + UpdateAbilitiesPacket updateAbilitiesPacket = new UpdateAbilitiesPacket(); + updateAbilitiesPacket.setUniqueEntityId(bedrockId); + updateAbilitiesPacket.setCommandPermission(commandPermission); + updateAbilitiesPacket.setPlayerPermission(playerPermission); - 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) { @@ -1723,20 +1674,21 @@ public void sendAdventureSettings() { 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() { @@ -1754,7 +1706,7 @@ private int getRenderDistance() { * Send a packet to the server to indicate client render distance, locale, skin parts, and hand preference. */ public void sendJavaClientSettings() { - ServerboundClientInformationPacket clientSettingsPacket = new ServerboundClientInformationPacket(getLocale(), + ServerboundClientInformationPacket clientSettingsPacket = new ServerboundClientInformationPacket(locale(), getRenderDistance(), ChatVisibility.FULL, true, SKIN_PARTS, HandPreference.RIGHT_HAND, false, true); sendDownstreamPacket(clientSettingsPacket); @@ -1765,7 +1717,7 @@ public void sendJavaClientSettings() { * * @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()) { @@ -1851,4 +1803,69 @@ public float getEyeHeight() { 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 8cfc73d7e9a..7e5982ab593 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.Getter; import org.geysermc.geyser.text.GeyserLocale; +import javax.annotation.Nonnull; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -61,12 +62,23 @@ public void addSession(UUID uuid, GeyserSession session) { } 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. */ @@ -80,7 +92,7 @@ public List getAllSessions() { public void disconnectAll(String message) { Collection sessions = getAllSessions(); for (GeyserSession session : sessions) { - session.disconnect(GeyserLocale.getPlayerLocaleString(message, session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString(message, session.locale())); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java index f5801ed2b2b..00b18629207 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java @@ -73,13 +73,13 @@ public AdvancementsCache(GeyserSession session) { public void buildAndShowMenuForm() { SimpleForm.Builder builder = SimpleForm.builder() - .translator(MinecraftLocale::getLocaleString, session.getLocale()) + .translator(MinecraftLocale::getLocaleString, session.locale()) .title("gui.advancements"); List rootAdvancementIds = new ArrayList<>(); for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement - builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.locale())); rootAdvancementIds.add(advancement.getKey()); } } @@ -111,7 +111,7 @@ public void buildAndShowMenuForm() { */ public void buildAndShowListForm() { GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); - String language = session.getLocale(); + String language = session.locale(); SimpleForm.Builder builder = SimpleForm.builder() @@ -160,7 +160,7 @@ public void buildAndShowListForm() { */ public void buildAndShowInfoForm(GeyserAdvancement advancement) { // Cache language for easier access - String language = session.getLocale(); + String language = session.locale(); String earned = isEarned(advancement) ? "yes" : "no"; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java b/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java index 7cfeaa165f3..cd1bc4c982d 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java @@ -57,7 +57,7 @@ public void updateBossBar() { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.locale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); bossEventPacket.setOverlay(overlay); @@ -71,7 +71,7 @@ public void updateTitle(Component title) { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.locale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index 91d6b33d617..d2c1415a386 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -30,10 +30,10 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.chunk.GeyserChunk; -import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.MathUtils; public class ChunkCache { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java index 03785de16ea..07ccd628060 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java @@ -34,10 +34,10 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; -import org.geysermc.geyser.level.physics.Axis; import java.util.Map; 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 ac0c93204b6..9cd5b2ef65d 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 void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet 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 void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet 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 239f5c8655b..b3d0518b3ec 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 @@ 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.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 void resetTitleTimes(boolean clientSync) { /* 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 void endPredictionsUpTo(int sequence) { 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 7a800890bea..7b6dacd16c0 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -31,12 +31,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.util.PluginMessageUtils; -import org.geysermc.floodgate.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -114,7 +114,7 @@ public void onMessage(String message) { 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 4eb92c3ac90..730d4690828 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -33,9 +33,9 @@ import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; @@ -258,21 +258,26 @@ private static GameProfileData loadFromJson(String encodedJson) throws IOExcepti 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://"); + if (textures == null) { + return null; + } + + JsonNode skinTexture = textures.get("SKIN"); + if (skinTexture == null) { + return null; + } - boolean isAlex = skinTexture.has("metadata"); + String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); - String capeUrl = null; - JsonNode capeTexture = textures.get("CAPE"); - if (capeTexture != null) { - capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); - } + boolean isAlex = skinTexture.has("metadata"); - return new GameProfileData(skinUrl, capeUrl, isAlex); + String capeUrl = null; + JsonNode capeTexture = textures.get("CAPE"); + if (capeTexture != null) { + capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); } - return null; + + return new GameProfileData(skinUrl, capeUrl, isAlex); } /** @@ -286,7 +291,7 @@ private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) { String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); - if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().getAuthType() != AuthType.ONLINE) { + if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { 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 27b277c727f..69dbb558e05 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 @@ 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 void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) th } 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 d39c0d69682..49178f0333f 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 static String toANSI(String string) { 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 static String toANSI(String string) { 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 c45de8f9fe5..af965ba8af3 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; 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 86e015c0f57..340674119a1 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 @@ private static String loadGeyserLocale(String locale, GeyserBootstrap bootstrap) 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 static String getLocaleStringLog(String key, Object... values) { 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/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java index 93150942ccb..94ad5eeadaf 100644 --- a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java @@ -30,7 +30,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; @@ -71,7 +71,7 @@ private static CompletableFuture generateAssetCache() { // Get the url for the latest version of the games manifest String latestInfoURL = ""; for (Version version : versionManifest.getVersions()) { - if (version.getId().equals(MinecraftProtocol.getJavaCodec().getMinecraftVersion())) { + if (version.getId().equals(GameProtocol.getJavaCodec().getMinecraftVersion())) { latestInfoURL = version.getUrl(); break; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java index 3d2cc563eb2..1dc6cd4e9d7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java @@ -29,10 +29,10 @@ import com.nukkitx.math.vector.Vector3i; import lombok.EqualsAndHashCode; import lombok.Getter; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionManager; -import org.geysermc.geyser.level.physics.Axis; +import org.geysermc.geyser.session.GeyserSession; @EqualsAndHashCode public class BlockCollision { diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java index c101fcdfbe8..b47b187c4da 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.translator.collision; import lombok.EqualsAndHashCode; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.session.GeyserSession; @EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "_door$", usesParams = true, passDefaultBoxes = true) diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java index 2aa74499a74..dfbd1c8b8b6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.translator.collision; import lombok.EqualsAndHashCode; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.session.GeyserSession; /** * In order for scaffolding to work on Bedrock, entity flags need to be sent to the player diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java index 998e15ded31..fb83e357d4b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.translator.collision; import lombok.EqualsAndHashCode; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.session.GeyserSession; @EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "^snow$", passDefaultBoxes = true, usesParams = true) diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java index 0660c3cf666..836c05711d3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java @@ -26,9 +26,9 @@ package org.geysermc.geyser.translator.collision; import lombok.EqualsAndHashCode; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.session.GeyserSession; @EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "_trapdoor$", usesParams = true, passDefaultBoxes = true) 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 bf806bd06d2..a2417816135 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 @@ -27,10 +27,10 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.InventoryHolder; import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; /** * Provided as a base for any inventory that requires a block for opening it @@ -65,8 +65,8 @@ public AbstractBlockInventoryTranslator(int size, InventoryHolder holder, Invent } @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 e56586b14ca..52e542b7b0a 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 @@ -34,11 +34,11 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.geyser.inventory.AnvilContainer; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import java.util.Objects; @@ -59,10 +59,12 @@ protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession 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/BaseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java index 8016ca24f6e..9b6e6df563c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java @@ -28,12 +28,8 @@ import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import org.geysermc.geyser.inventory.Container; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; public abstract class BaseInventoryTranslator extends InventoryTranslator { public BaseInventoryTranslator(int size) { 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 4dac5e86f3d..304b8ef0062 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 @@ protected boolean checkInteractionPosition(GeyserSession session) { @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 0c4fe12e7dc..69ad41f97c0 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 @@ -29,10 +29,10 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator { public BrewingInventoryTranslator() { @@ -43,7 +43,7 @@ public BrewingInventoryTranslator() { 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 void openInventory(GeyserSession session, Inventory inventory) { @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/CartographyInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java index 226abe1577c..c796ab5e302 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java @@ -28,13 +28,9 @@ import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import org.geysermc.geyser.inventory.CartographyContainer; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator { public CartographyInventoryTranslator() { 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 800b3590150..97946b59cc0 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 ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession se // 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 23bab8c0ebf..3ca8f165f66 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 @@ -28,12 +28,12 @@ import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.Generic3X3Container; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; /** * Droppers and dispensers @@ -52,7 +52,7 @@ public Inventory createInventory(String name, int windowId, ContainerType contai @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 6f4ca7ee4c4..e6cc010f538 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 ItemStackResponsePacket.Response translateRequest(GeyserSession session, 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 ItemStackResponsePacket.Response translateRequest(GeyserSession session, 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 boolean checkNetId(GeyserSession session, Inventory inventory, StackReque */ //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 fc4090c73e0..59fe81751f6 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 @@ -39,8 +39,8 @@ import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.LecternContainer; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.InventoryUtils; @@ -55,7 +55,8 @@ public LecternInventoryTranslator() { } @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { + public boolean prepareInventory(GeyserSession session, Inventory inventory) { + return true; } @Override @@ -99,10 +100,10 @@ private void updateBook(GeyserSession session, Inventory inventory, GeyserItemSt 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 @@ private void updateBook(GeyserSession session, Inventory inventory, GeyserItemSt 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 5a237b72ac0..d44ff589a29 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 ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession se // 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 031fb606e29..857b96e55b5 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 @@ -37,16 +37,12 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.MerchantContainer; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.updater.InventoryUpdater; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InventoryUtils; import java.util.concurrent.TimeUnit; @@ -98,7 +94,7 @@ public SlotType getSlotType(int javaSlot) { } @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(); @@ -121,6 +117,8 @@ protected void initializeMetadata() { 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 e2349e5a511..8432b0253fd 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 ItemStackResponsePacket.Response translateRequest(GeyserSession session, } } 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 Inventory createInventory(String name, int windowId, ContainerType contai } @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/ShulkerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java index f77ff2229b4..a055d3b5dd3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java @@ -32,13 +32,13 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator { public ShulkerInventoryTranslator() { 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 e0e2e27bdab..1668e3a9311 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 @@ protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession 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/ChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java index 65d789c0b05..548e9e6e3b1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java @@ -26,12 +26,12 @@ package org.geysermc.geyser.translator.inventory.chest; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; +import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.updater.ChestInventoryUpdater; import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; 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 fc3279de14c..fa20e6dbbc0 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 @@ -35,11 +35,12 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.DoubleChestValue; -import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator; 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 DoubleChestInventoryTranslator(int size) { } @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 void prepareInventory(GeyserSession session, Inventory inventory) { 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 void prepareInventory(GeyserSession session, Inventory inventory) { 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 void closeInventory(GeyserSession session, Inventory inventory) { // 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 4d158c4fec8..ae914ed8c86 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 @@ -27,9 +27,9 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.InventoryHolder; +import org.geysermc.geyser.session.GeyserSession; public class SingleChestInventoryTranslator extends ChestInventoryTranslator { private final InventoryHolder holder; @@ -52,8 +52,8 @@ protected boolean isValidBlock(String[] javaBlockString) { } @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 472f92b4dbf..764ab0a3354 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 @@ -28,12 +28,12 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.SlotType; -import org.geysermc.geyser.translator.inventory.AbstractBlockInventoryTranslator; import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.AbstractBlockInventoryTranslator; public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator { AbstractFurnaceInventoryTranslator(String javaBlockIdentifier, ContainerType containerType) { @@ -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 064793d290f..538133e0e95 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 @@ -26,10 +26,10 @@ package org.geysermc.geyser.translator.inventory.horse; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; import org.geysermc.geyser.inventory.updater.HorseInventoryUpdater; import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -40,7 +40,8 @@ public AbstractHorseInventoryTranslator(int size) { } @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 035f8efa24a..4930c6b604a 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 @@ -30,9 +30,9 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import java.util.Arrays; @@ -105,7 +105,7 @@ public void updateInventory(GeyserSession session, Inventory inventory) { } 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/CompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java index 4c297808231..a0da82648c7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java @@ -30,7 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -79,8 +79,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems()) + return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) .collect(Collectors.toList()); } 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 00000000000..82a8c9de152 --- /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/FilledMapTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java index 3dfa2d82f2b..b5dbefc3ac3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -61,7 +61,7 @@ protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping m @Override public List getAppliedItems() { return Collections.singletonList( - Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getMapping("minecraft:filled_map") ); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/GoatHornTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/GoatHornTranslator.java index 08e8534afc5..2cb9d7ec7d2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/GoatHornTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/GoatHornTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -91,7 +91,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { return Collections.singletonList( - Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getMapping("minecraft:goat_horn") ); } 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 539d20207c8..ab3feae5fe3 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 @@ -122,7 +122,7 @@ public static ItemStack translateToJava(ItemData data, ItemMappings mappings) { } } if (itemStack.getNbt().isEmpty()) { - // Otherwise, seems to causes issues with villagers accepting books, and I don't see how this will break anything else. - Camotoy + // Otherwise, seems to cause issues with villagers accepting books, and I don't see how this will break anything else. - Camotoy itemStack = new ItemStack(itemStack.getId(), itemStack.getAmount(), null); } } @@ -159,7 +159,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack nbt = translateDisplayProperties(session, nbt, bedrockItem); if (session.isAdvancedTooltips()) { - nbt = addAdvancedTooltips(nbt, bedrockItem, session.getLocale()); + nbt = addAdvancedTooltips(nbt, bedrockItem, session.locale()); } ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); @@ -173,13 +173,15 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack 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(); @@ -241,12 +243,11 @@ private static CompoundTag addAdvancedTooltips(CompoundTag nbt, ItemMapping mapp * 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(); @@ -256,21 +257,29 @@ private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBe // 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; } /** - * Given an item stack, determine the item mapping that should be applied to Bedrock players. + * Given an item stack, determine the Bedrock item ID that should be applied to Bedrock players. */ - @Nonnull - public static ItemMapping getBedrockItemMapping(GeyserSession session, @Nonnull GeyserItemStack itemStack) { + public static int getBedrockItemId(GeyserSession session, @Nonnull GeyserItemStack itemStack) { if (itemStack.isEmpty()) { - return ItemMapping.AIR; + return ItemMapping.AIR.getJavaId(); } int javaId = itemStack.getJavaId(); - return ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) + ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) .getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings()); + + int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping); + if (customItemId == -1) { + // No custom item + return mapping.getBedrockId(); + } else { + return customItemId; + } } private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @@ -292,6 +301,10 @@ protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping m if (itemStack.getNbt() != null) { builder.tag(this.translateNbtToBedrock(itemStack.getNbt())); } + + CompoundTag nbt = itemStack.getNbt(); + translateCustomItem(nbt, builder, mapping); + return builder; } @@ -313,66 +326,26 @@ protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings m } 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)); @@ -384,11 +357,14 @@ private Object translateToBedrockNBT(Tag tag) { 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) { @@ -416,7 +392,7 @@ private Tag translateToJavaNBT(String name, Object object) { if (object instanceof byte[]) { return new ByteArrayTag(name, (byte[]) object); } - + if (object instanceof Byte) { return new ByteTag(name, (byte) object); } @@ -490,7 +466,7 @@ public static CompoundTag translateDisplayProperties(GeyserSession session, Comp String name = tagName.getValue(); // Get the translated name and prefix it with a reset char - name = MessageTranslator.convertMessageLenient(name, session.getLocale()); + name = MessageTranslator.convertMessageLenient(name, session.locale()); // Add the new name tag display.put(new StringTag("Name", name)); @@ -518,55 +494,20 @@ public static CompoundTag translateDisplayProperties(GeyserSession session, Comp String translationKey = mapping.getTranslationString(); // Reset formatting since Bedrock defaults to italics - display.put(new StringTag("Name", "§r§" + translationColor + MinecraftLocale.getLocaleString(translationKey, session.getLocale()))); + display.put(new StringTag("Name", "§r§" + translationColor + MinecraftLocale.getLocaleString(translationKey, session.locale()))); } return tag; } /** - * 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 + * Translates the custom model data of an item */ - 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; - } - } - } - - if (!checkNbt) { - return true; - } - 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; + private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) { + int bedrockId = CustomItemTranslator.getCustomItem(nbt, mapping); + if (bedrockId != -1) { + builder.id(bedrockId); } - - 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/NbtItemStackTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java index bfa7ebc2eda..5f22668dff1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; public abstract class NbtItemStackTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java index bf16af38fb9..3e814a098f5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java @@ -30,8 +30,8 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.inventory.item.Potion; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -74,8 +74,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems()) + return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) .collect(Collectors.toList()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java index d831ce58603..bbf598ecd4c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java @@ -30,8 +30,8 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -42,7 +42,7 @@ @ItemRemapper public class TippedArrowTranslator extends ItemTranslator { - private static final int TIPPED_ARROW_JAVA_ID = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + private static final int TIPPED_ARROW_JAVA_ID = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getMapping("minecraft:tipped_arrow") .getJavaId(); @@ -81,8 +81,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems()) + return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems()) .filter(entry -> entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")) .collect(Collectors.toList()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java index c3abf249596..19809c12f0b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java @@ -28,11 +28,11 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.text.MinecraftLocale; @ItemRemapper public class AxolotlBucketTranslator extends NbtItemStackTranslator { @@ -42,7 +42,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM // Bedrock Edition displays the properties of the axolotl. Java does not. // To work around this, set the custom name to the Axolotl translation and it's displayed correctly itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); - itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.axolotl", session.getLocale()))); + itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.axolotl", session.locale()))); // Boilerplate required so the nametag does not appear as "Bucket of " itemTag.put(new StringTag("ColorID", "")); itemTag.put(new StringTag("BodyID", "")); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index ed4865411f6..95dd07f22fb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java @@ -29,7 +29,7 @@ import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; @@ -76,8 +76,7 @@ private static CompoundTag getPatternTag(String pattern, int color) { } public BannerTranslator() { - appliedItems = Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems()) + appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) .collect(Collectors.toList()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java index a507d02cca4..5dcc76b49b4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java @@ -26,11 +26,11 @@ package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; -import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ItemUtils; import java.util.ArrayList; @@ -59,7 +59,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM List lore = new ArrayList<>(); for (Tag tag : listTag.getValue()) { if (!(tag instanceof StringTag)) continue; - lore.add(new StringTag("", MessageTranslator.convertMessageLenient(((StringTag) tag).getValue(), session.getLocale()))); + lore.add(new StringTag("", MessageTranslator.convertMessageLenient(((StringTag) tag).getValue(), session.locale()))); } displayTag.put(new ListTag("Lore", lore)); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java index ec741f26195..652d804fe48 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java @@ -29,11 +29,11 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; import java.util.ArrayList; import java.util.List; 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 723798c8977..642a43123de 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 @@ -28,19 +28,19 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper 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/EnchantedBookTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java index 9b1d423c131..ad6c2e9f13c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java @@ -28,10 +28,10 @@ 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 org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper(priority = 1) public class EnchantedBookTranslator extends NbtItemStackTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index cd6d5d6ff01..204981965c6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -27,11 +27,11 @@ import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.inventory.item.Enchantment; -import org.geysermc.geyser.registry.type.ItemMapping; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java index 6a4438358cf..b74a4f61e72 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java @@ -29,8 +29,8 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; -import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import org.geysermc.geyser.level.FireworkColor; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import org.geysermc.geyser.util.MathUtils; /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java index 566b0ac2ba1..fdf898273b6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java @@ -29,9 +29,9 @@ 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 org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.util.MathUtils; @ItemRemapper diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java index c907375b90d..eca3272d171 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java @@ -29,9 +29,9 @@ import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; -import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper public class FireworkStarTranslator extends FireworkBaseTranslator { 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 9c74e712305..5e5920b4a56 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 @@ -27,18 +27,17 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; -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 void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM 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/inventory/item/nbt/LodestoneCompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java index f4b91165d99..8025817f7ce 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java @@ -25,11 +25,14 @@ package org.geysermc.geyser.translator.inventory.item.nbt; -import com.github.steveice10.opennbt.tag.builtin.*; +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 com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper public class LodestoneCompassTranslator extends NbtItemStackTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java index 80b22dafb03..8fd44ef65a2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java @@ -26,10 +26,10 @@ package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper public class MapItemTranslator extends NbtItemStackTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java index 680be00fdaa..d4975f81a67 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java @@ -28,11 +28,11 @@ 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 org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.text.MinecraftLocale; @ItemRemapper public class PlayerHeadTranslator extends NbtItemStackTranslator { @@ -56,7 +56,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM } // Add correct name of player skull // TODO: It's always yellow, even with a custom name. Handle? - String displayName = "\u00a7r\u00a7e" + MinecraftLocale.getLocaleString("block.minecraft.player_head.named", session.getLocale()).replace("%s", name.getValue()); + String displayName = "\u00a7r\u00a7e" + MinecraftLocale.getLocaleString("block.minecraft.player_head.named", session.locale()).replace("%s", name.getValue()); if (!itemTag.contains("display")) { itemTag.put(new CompoundTag("display")); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java index 1b9acdb96a6..f95c54e1855 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java @@ -27,9 +27,9 @@ import com.github.steveice10.mc.protocol.data.game.Identifier; import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import org.geysermc.geyser.util.MathUtils; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java index dbacc75fe48..6313dc36209 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java @@ -31,12 +31,12 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import org.geysermc.geyser.entity.type.living.animal.TropicalFishEntity; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemRemapper; -import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.text.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -50,7 +50,7 @@ public class TropicalFishBucketTranslator extends NbtItemStackTranslator { public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { // Prevent name from appearing as "Bucket of" itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); - itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.tropical_fish", session.getLocale()))); + itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.tropical_fish", session.locale()))); // Add Java's client side lore tag Tag bucketVariantTag = itemTag.get("BucketVariantTag"); if (bucketVariantTag instanceof IntTag) { @@ -66,10 +66,10 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM int predefinedVariantId = TropicalFishEntity.getPredefinedId(varNumber); if (predefinedVariantId != -1) { Component tooltip = Component.translatable("entity.minecraft.tropical_fish.predefined." + predefinedVariantId, LORE_STYLE); - lore.add(0, new StringTag("", MessageTranslator.convertMessage(tooltip, session.getLocale()))); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(tooltip, session.locale()))); } else { Component typeTooltip = Component.translatable("entity.minecraft.tropical_fish.type." + TropicalFishEntity.getVariantName(varNumber), LORE_STYLE); - lore.add(0, new StringTag("", MessageTranslator.convertMessage(typeTooltip, session.getLocale()))); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(typeTooltip, session.locale()))); byte baseColor = TropicalFishEntity.getBaseColor(varNumber); byte patternColor = TropicalFishEntity.getPatternColor(varNumber); @@ -78,7 +78,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemM colorTooltip = colorTooltip.append(Component.text(", ", LORE_STYLE)) .append(Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(patternColor), LORE_STYLE)); } - lore.add(1, new StringTag("", MessageTranslator.convertMessage(colorTooltip, session.getLocale()))); + lore.add(1, new StringTag("", MessageTranslator.convertMessage(colorTooltip, session.locale()))); } ListTag loreTag = displayTag.get("Lore"); 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 537c93a413c..04b39deeb61 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 @@ -30,19 +30,22 @@ import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.SingletonPalette; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.chunk.BlockStorage; import org.geysermc.geyser.level.chunk.GeyserChunkSection; import org.geysermc.geyser.level.chunk.bitarray.BitArray; 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.util.JavaCodecEntry; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.JavaCodecUtil; import org.geysermc.geyser.util.MathUtils; // Array index formula by https://wiki.vg/Chunk_Format @@ -56,7 +59,7 @@ public static void loadServerBiomes(GeyserSession session, CompoundTag codec) { 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 53e1af8a5d4..e36ad2d227b 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,9 +28,10 @@ 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.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; @@ -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++; } @@ -48,7 +49,7 @@ public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) protected NbtMap getItem(CompoundTag tag) { // TODO: Version independent mappings - ItemMapping mapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping((String) tag.get("id").getValue()); + ItemMapping mapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping((String) tag.get("id").getValue()); NbtMapBuilder tagBuilder = NbtMap.builder() .putString("Name", mapping.getBedrockIdentifier()) .putByte("Count", (byte) tag.get("Count").getValue()) diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java index f5ec3607c0a..0836b1e59ea 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java @@ -29,9 +29,9 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.DoubleChestValue; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockEntityUtils; /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java index 845e2e42943..ed1a9e82bc7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java @@ -29,8 +29,8 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java index f6561ccbe00..c5268901492 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java @@ -37,16 +37,19 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; import org.geysermc.common.PlatformType; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.physics.Axis; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.PistonCache; -import org.geysermc.geyser.level.physics.BoundingBox; -import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.translator.collision.BlockCollision; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.BlockEntityUtils; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.ChunkUtils; import java.util.LinkedList; import java.util.Map; @@ -619,8 +622,10 @@ private void placeFinalBlocks() { Vector3i movement = getMovement(); attachedBlocks.forEach((blockPos, javaId) -> { blockPos = blockPos.add(movement); - // Send a final block entity packet to detach blocks - BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos); + if (!GameProtocol.supports1_19_50(session)) { + // Send a final block entity packet to detach blocks for clients older than 1.19.50 + BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos); + } // Don't place blocks that collide with the player if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { ChunkUtils.updateBlock(session, javaId, blockPos); @@ -737,8 +742,8 @@ private NbtMap buildPistonTag() { .putFloat("LastProgress", lastProgress) .putByte("NewState", getState()) .putByte("State", getState()) - .putByte("Sticky", (byte) (sticky ? 1 : 0)) - .putByte("isMovable", (byte) 0) + .putBoolean("Sticky", sticky) + .putBoolean("isMovable", false) .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()); @@ -760,8 +765,8 @@ public static NbtMap buildStaticPistonTag(Vector3i position, boolean extended, b .putFloat("LastProgress", extended ? 1.0f : 0.0f) .putByte("NewState", (byte) (extended ? 2 : 0)) .putByte("State", (byte) (extended ? 2 : 0)) - .putByte("Sticky", (byte) (sticky ? 1 : 0)) - .putByte("isMovable", (byte) 0) + .putBoolean("Sticky", sticky) + .putBoolean("isMovable", false) .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()); @@ -781,8 +786,9 @@ private NbtMap buildMovingBlockTag(Vector3i position, int javaId, Vector3i pisto NbtMap movingBlock = session.getBlockMappings().getBedrockBlockStates().get(session.getBlockMappings().getBedrockBlockId(javaId)); NbtMapBuilder builder = NbtMap.builder() .putString("id", "MovingBlock") + .putBoolean("expanding", action == PistonValueType.PUSHING) .putCompound("movingBlock", movingBlock) - .putByte("isMovable", (byte) 1) + .putBoolean("isMovable", true) .putInt("pistonPosX", pistonPosition.getX()) .putInt("pistonPosY", pistonPosition.getY()) .putInt("pistonPosZ", pistonPosition.getZ()) 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 bd3f9683642..1b4fd6a10c2 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 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 void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) 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 3d11d5ceddc..2a4711e2678 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 @@ 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 void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) 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 641161127f8..aabc39e12d2 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/BedrockBlockPickRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java index 63ccdf729ab..8d7cbe22b52 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java @@ -29,11 +29,11 @@ import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.ItemFrameEntity; +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; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.util.InventoryUtils; @Translator(packet = BlockPickRequestPacket.class) 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 0aa57b077f0..24fc8396f33 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -28,8 +28,8 @@ import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; 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; @@ -39,18 +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/BedrockMoveEntityAbsoluteTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java index 0bb3df07167..e6390bdbab2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java @@ -28,9 +28,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java index 0cbaa9e9941..87639511433 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -29,11 +29,11 @@ import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.floodgate.util.DeviceOs; import java.util.Collections; import java.util.concurrent.TimeUnit; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java index fb2435ceacd..88cfcdff789 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java @@ -29,9 +29,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundPlayerInputPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.PlayerInputPacket; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity; import org.geysermc.geyser.session.GeyserSession; 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 00000000000..fe8150d406b --- /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 8641a35ff2a..121bfd065f9 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 @@ -26,7 +26,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -41,10 +41,10 @@ public void translate(GeyserSession session, SetLocalPlayerAsInitializedPacket p if (!session.getUpstream().isInitialized()) { session.getUpstream().setInitialized(true); - if (session.getRemoteAuthType() == AuthType.ONLINE) { + 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/BedrockShowCreditsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java index ca6ac09dd92..6fa2710108c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java @@ -25,13 +25,12 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.nukkitx.protocol.bedrock.packet.ShowCreditsPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = ShowCreditsPacket.class) public class BedrockShowCreditsTranslator extends PacketTranslator { 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 e52fac3719b..00eb95455bb 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 5001fc2d26a..c728390d6d9 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 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 void translate(GeyserSession session, PlayerActionPacket packet) { attributesPacket.setRuntimeEntityId(entity.getGeyserId()); attributesPacket.getAttributes().addAll(entity.getAttributes().values()); session.sendUpstreamPacket(attributesPacket); + + // Bounding box must be sent after a player dies and respawns since 1.19.40 + entity.updateBoundingBox(); break; case START_SWIMMING: if (!entity.getFlag(EntityFlag.SWIMMING)) { @@ -128,7 +132,7 @@ public void translate(GeyserSession session, PlayerActionPacket packet) { break; case DROP_ITEM: ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, - vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); + vector, Direction.VALUES[packet.getFace()], 0); session.sendDownstreamPacket(dropItemPacket); break; case STOP_SLEEP: @@ -171,7 +175,7 @@ public void translate(GeyserSession session, PlayerActionPacket packet) { } int breakingBlock = session.getBreakingBlock(); if (breakingBlock == -1) { - break; + breakingBlock = BlockStateValues.JAVA_AIR_ID; } Vector3f vectorFloat = vector.toFloat(); @@ -202,7 +206,7 @@ public void translate(GeyserSession session, PlayerActionPacket packet) { } } - ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, session.getWorldCache().nextPredictionSequence()); + ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, 0); session.sendDownstreamPacket(abortBreakingPacket); LevelEventPacket stopBreak = new LevelEventPacket(); stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java index 5d15761bdce..43d2d314ce9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java @@ -44,7 +44,7 @@ public void translate(GeyserSession session, EmotePacket packet) { 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 ba4652726c4..6078b7ebd29 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,13 +31,12 @@ 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; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -49,17 +48,6 @@ public void translate(GeyserSession session, MovePlayerPacket packet) { SessionPlayerEntity entity = session.getPlayerEntity(); if (!session.isSpawned()) return; - if (!session.getUpstream().isInitialized()) { - MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket(); - moveEntityBack.setRuntimeEntityId(entity.getGeyserId()); - moveEntityBack.setPosition(entity.getPosition()); - moveEntityBack.setRotation(entity.getBedrockRotation()); - moveEntityBack.setTeleported(true); - moveEntityBack.setOnGround(true); - session.sendUpstreamPacketImmediately(moveEntityBack); - return; - } - session.setLastMovementTimestamp(System.currentTimeMillis()); // Send book update before the player moves @@ -175,7 +163,7 @@ private boolean isValidMove(GeyserSession session, Vector3f currentPosition, Vec return false; } if (currentPosition.distanceSquared(newPosition) > 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/JavaBossEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java index 1011eb739cb..30d6aa017d2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java @@ -25,13 +25,12 @@ package org.geysermc.geyser.translator.protocol.java; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundBossEventPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.BossBar; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundBossEventPacket; - @Translator(packet = ClientboundBossEventPacket.class) public class JavaBossEventTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java index fdc2fa2fb28..6504959dc0e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java @@ -26,10 +26,10 @@ package org.geysermc.geyser.translator.protocol.java; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket; +import com.nukkitx.protocol.bedrock.packet.SetDifficultyPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import com.nukkitx.protocol.bedrock.packet.SetDifficultyPacket; @Translator(packet = ClientboundChangeDifficultyPacket.class) public class JavaChangeDifficultyTranslator extends PacketTranslator { 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 c9f192d3fe2..11311b63cea 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,14 +44,16 @@ 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.command.CommandManager; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; +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.EntityUtils; import java.util.*; @@ -115,7 +117,7 @@ public void translate(GeyserSession session, ClientboundCommandsPacket packet) { return; } - CommandManager manager = session.getGeyser().getCommandManager(); + GeyserCommandManager manager = session.getGeyser().commandManager(); CommandNode[] nodes = packet.getNodes(); List commandData = new ArrayList<>(); IntSet commandNodes = new IntOpenHashSet(); @@ -136,7 +138,7 @@ public void translate(GeyserSession session, ClientboundCommandsPacket packet) { // Get and update the commandArgs list with the found arguments if (node.getChildIndices().length >= 1) { for (int childIndex : node.getChildIndices()) { - commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]); + commandArgs.computeIfAbsent(nodeIndex, ($) -> new ArrayList<>()).add(nodes[childIndex]); } } @@ -144,15 +146,20 @@ public void translate(GeyserSession session, ClientboundCommandsPacket packet) { CommandParamData[][] params = getParams(session, nodes[nodeIndex], nodes); // Insert the alias name into the command list - commands.computeIfAbsent(new BedrockCommandInfo(manager.getDescription(node.getName().toLowerCase(Locale.ROOT)), params), + commands.computeIfAbsent(new BedrockCommandInfo(node.getName().toLowerCase(Locale.ROOT), manager.description(node.getName().toLowerCase(Locale.ROOT)), params), index -> new HashSet<>()).add(node.getName().toLowerCase()); } + ServerDefineCommandsEvent event = new ServerDefineCommandsEvent(session, commands.keySet()); + session.getGeyser().eventBus().fire(event); + if (event.isCancelled()) { + return; + } + // The command flags, not sure what these do apart from break things List flags = Collections.emptyList(); // Loop through all the found commands - for (Map.Entry> entry : commands.entrySet()) { String commandName = entry.getValue().iterator().next(); // We know this has a value @@ -192,7 +199,7 @@ private static CommandParamData[][] getParams(GeyserSession session, CommandNode if (commandNode.getChildIndices().length >= 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(); @@ -205,11 +212,11 @@ private static CommandParamData[][] getParams(GeyserSession session, CommandNode /** * Convert Java edition command types to Bedrock edition * - * @param session the session + * @param context the session's command context * @param node Command type to convert * @return Bedrock parameter data type */ - private static Object mapCommandType(GeyserSession session, CommandNode node) { + private static Object mapCommandType(CommandBuilderContext context, CommandNode node) { CommandParser parser = node.getParser(); if (parser == null) { return CommandParam.STRING; @@ -226,21 +233,24 @@ private static Object mapCommandType(GeyserSession session, CommandNode node) { case RESOURCE_LOCATION, FUNCTION -> 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; }; } @@ -248,7 +258,55 @@ private static Object mapCommandType(GeyserSession session, CommandNode node) { /** * Stores the command description and parameter data for best optimizing the Bedrock commands packet. */ - private record BedrockCommandInfo(String description, CommandParamData[][] paramData) { + private record BedrockCommandInfo(String name, String description, CommandParamData[][] paramData) implements ServerDefineCommandsEvent.CommandInfo { + } + + /** + * Stores command completions so we don't have to rebuild the same values multiple times. + */ + @MonotonicNonNull + private static class CommandBuilderContext { + private final GeyserSession session; + private Object biomesWithTags; + private Object biomesNoTags; + private String[] blockStates; + private String[] entityTypes; + + CommandBuilderContext(GeyserSession session) { + this.session = session; + } + + private Object getBiomes() { + if (biomesNoTags != null) { + return biomesNoTags; + } + + String[] identifiers = session.getGeyser().getWorldManager().getBiomeIdentifiers(false); + return (biomesNoTags = identifiers != null ? identifiers : CommandParam.STRING); + } + + private Object getBiomesWithTags() { + if (biomesWithTags != null) { + return biomesWithTags; + } + + String[] identifiers = session.getGeyser().getWorldManager().getBiomeIdentifiers(true); + return (biomesWithTags = identifiers != null ? identifiers : CommandParam.STRING); + } + + private String[] getBlockStates() { + if (blockStates != null) { + return blockStates; + } + return (blockStates = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.get().keySet().toArray(new String[0])); + } + + private String[] getEntityTypes() { + if (entityTypes != null) { + return entityTypes; + } + return (entityTypes = Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0])); + } } @Getter @@ -273,10 +331,10 @@ public ParamInfo(CommandNode paramNode, CommandParamData paramData) { /** * Build the array of all the child parameters (recursive) * - * @param session the session + * @param context the session's command builder context * @param allNodes Every command node */ - public void buildChildren(GeyserSession session, CommandNode[] allNodes) { + public void buildChildren(CommandBuilderContext context, CommandNode[] allNodes) { for (int paramID : paramNode.getChildIndices()) { CommandNode paramNode = allNodes[paramID]; @@ -314,12 +372,12 @@ public void buildChildren(GeyserSession session, CommandNode[] allNodes) { } } else { // Put the non-enum param into the list - Object mappedType = mapCommandType(session, paramNode); + Object mappedType = mapCommandType(context, paramNode); CommandEnumData enumData = null; CommandParam type = null; boolean optional = this.paramNode.isExecutable(); if (mappedType instanceof String[]) { - enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(Locale.ROOT), (String[]) mappedType, false); + enumData = new CommandEnumData(getEnumDataName(paramNode).toLowerCase(Locale.ROOT), (String[]) mappedType, false); } else { type = (CommandParam) mappedType; // Bedrock throws a fit if an optional message comes after a string or target @@ -337,8 +395,23 @@ public void buildChildren(GeyserSession session, CommandNode[] allNodes) { // Recursively build all child options for (ParamInfo child : children) { - child.buildChildren(session, allNodes); + child.buildChildren(context, allNodes); + } + } + + /** + * Mitigates https://github.com/GeyserMC/Geyser/issues/3411. Not a perfect solution. + */ + private static String getEnumDataName(CommandNode node) { + if (node.getProperties() instanceof ResourceProperties properties) { + String registryKey = properties.getRegistryKey(); + int identifierSplit = registryKey.indexOf(':'); + if (identifierSplit != -1) { + return registryKey.substring(identifierSplit); + } + return registryKey; } + return node.getParser().name(); } /** 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 13ace5e233a..aaedfa4434d 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 @@ -38,8 +38,8 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -52,7 +52,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator { - byte[] raw = response.getBytes(StandardCharsets.UTF_8); - byte[] finalData = new byte[raw.length + 2]; - - finalData[0] = data[1]; - finalData[1] = data[2]; - System.arraycopy(raw, 0, finalData, 2, raw.length); + 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); + } session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData)); }); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java index 65fbd9381cc..96b0e3dbd23 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java @@ -36,7 +36,7 @@ public class JavaDisconnectTranslator 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/JavaGameProfileTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java index 199d29e30ef..7f8500ce4c3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java @@ -27,12 +27,12 @@ import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundGameProfilePacket; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; 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; -import org.geysermc.geyser.skin.SkinManager; @Translator(packet = ClientboundGameProfilePacket.class) public class JavaGameProfileTranslator extends PacketTranslator { @@ -40,7 +40,7 @@ public class JavaGameProfileTranslator 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.getLocale()); - String disconnectMessage; - if (isOutdatedMessage) { - String locale = session.getLocale(); - 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, MinecraftProtocol.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 ddbd66e054d..09d117087fa 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 @@ -36,17 +36,17 @@ import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; 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.session.auth.AuthType; 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; @@ -66,7 +66,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { 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"); @@ -87,6 +87,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { session.getWorldCache().removeScoreboard(); } session.setWorldName(packet.getWorldName()); + session.setLevels(packet.getWorldNames()); BiomeTranslator.loadServerBiomes(session, packet.getRegistry()); session.getTagCache().clear(); @@ -99,6 +100,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { if (needsSpawnPacket) { // The player has yet to spawn so let's do that using some of the information in this Java packet session.setDimension(newDimension); + DimensionUtils.setBedrockDimension(session, newDimension); session.connect(); // It is now safe to send these packets @@ -135,7 +137,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); // register the plugin messaging channels used in Floodgate - if (session.getRemoteAuthType() == AuthType.FLOODGATE) { + if (session.remoteServer().authType() == AuthType.FLOODGATE) { session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageChannels.getFloodgateRegisterData())); } @@ -146,7 +148,6 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { session.sendFog("minecraft:fog_hell"); } - session.setDimensionType(dimensions.get(newDimension)); ChunkUtils.loadDimension(session); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java index 6dcfda89a38..faff85fecbf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundPongPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPingPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundPongPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java index 74b27d417bc..e06182b8d86 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java @@ -26,57 +26,18 @@ package org.geysermc.geyser.translator.protocol.java; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket; -import com.nukkitx.protocol.bedrock.packet.TextPacket; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TranslatableComponent; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - @Translator(packet = ClientboundPlayerChatPacket.class) public class JavaPlayerChatTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) { - TextPacket textPacket = new TextPacket(); - textPacket.setPlatformChatId(""); - textPacket.setSourceName(""); - textPacket.setXuid(session.getAuthData().xuid()); - textPacket.setType(TextPacket.Type.CHAT); - - textPacket.setNeedsTranslation(false); - Component message = packet.getUnsignedContent() == null ? packet.getMessageDecorated() : packet.getUnsignedContent(); - - TextDecoration decoration = session.getChatTypes().get(packet.getChatType()); - 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.TARGET)) { - args.add(packet.getTargetName()); - } - if (parameters.contains(TextDecoration.Parameter.SENDER)) { - args.add(packet.getName()); - } - if (parameters.contains(TextDecoration.Parameter.CONTENT)) { - args.add(message); - } - withDecoration.args(args); - textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.getLocale())); - } else { - textPacket.setMessage(MessageTranslator.convertMessage(message, session.getLocale())); - } - - 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 3471ce57628..45db499f1d5 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 void translate(GeyserSession session, ClientboundRespawnPacket packet) { String newDimension = packet.getDimension(); if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) { - // Switching to a new world (based off the world name change); send a fake dimension change - if (!packet.getWorldName().equals(session.getWorldName()) && (session.getDimension().equals(newDimension) - // Ensure that the player never ever dimension switches to the same dimension - BAD - // Can likely be removed if the Above Bedrock Nether Building option can be removed - || DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension))) { + // Switching to a new world (based off the world name change or new dimension); send a fake dimension change + if (DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension)) { String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension); DimensionUtils.switchDimension(session, fakeDim); } session.setWorldName(packet.getWorldName()); DimensionUtils.switchDimension(session, newDimension); - session.setDimensionType(session.getDimensions().get(newDimension)); ChunkUtils.loadDimension(session); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java index b605dbbbcad..d71055a877b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSystemChatTranslator.java @@ -44,7 +44,7 @@ public void translate(GeyserSession session, ClientboundSystemChatPacket packet) textPacket.setType(packet.isOverlay() ? TextPacket.Type.TIP : TextPacket.Type.SYSTEM); textPacket.setNeedsTranslation(false); - textPacket.setMessage(MessageTranslator.convertMessage(packet.getContent(), session.getLocale())); + textPacket.setMessage(MessageTranslator.convertMessage(packet.getContent(), session.locale())); if (session.isSentSpawnPacket()) { session.sendUpstreamPacket(textPacket); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java index a1d8da90c1c..94bac62bfb8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java @@ -28,13 +28,13 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateAdvancementsPacket; import com.nukkitx.protocol.bedrock.packet.ToastRequestPacket; +import org.geysermc.geyser.level.GeyserAdvancement; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.session.cache.AdvancementsCache; -import org.geysermc.geyser.level.GeyserAdvancement; -import org.geysermc.geyser.text.MinecraftLocale; import java.util.Locale; @@ -82,8 +82,8 @@ public void sendAdvancementToasts(GeyserSession session, ClientboundUpdateAdvanc if (advancement != null && advancement.getDisplayData() != null) { if (advancement.getDisplayData().isShowToast() && session.getAdvancementsCache().isEarned(advancement)) { String frameType = advancement.getDisplayData().getFrameType().toString().toLowerCase(Locale.ROOT); - String frameTitle = advancement.getDisplayColor() + MinecraftLocale.getLocaleString("advancements.toast." + frameType, session.getLocale()); - String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.getLocale()); + String frameTitle = advancement.getDisplayColor() + MinecraftLocale.getLocaleString("advancements.toast." + frameType, session.locale()); + String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.locale()); ToastRequestPacket toastRequestPacket = new ToastRequestPacket(); toastRequestPacket.setTitle(frameTitle); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index f1de8391409..90468a9cb14 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -34,9 +34,10 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.SmithingRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket; -import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor; +import com.nukkitx.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; @@ -45,12 +46,12 @@ import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.util.InventoryUtils; import java.util.*; @@ -96,8 +97,8 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack } // Strip NBT - tools won't appear in the recipe book otherwise output = output.toBuilder().tag(null).build(); - ItemData[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); - for (ItemData[] inputs : inputCombinations) { + ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); + for (ItemDescriptorWithCount[] inputs : inputCombinations) { UUID uuid = UUID.randomUUID(); craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId)); @@ -113,8 +114,8 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack } // See above output = output.toBuilder().tag(null).build(); - ItemData[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); - for (ItemData[] inputs : inputCombinations) { + ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); + for (ItemDescriptorWithCount[] inputs : inputCombinations) { UUID uuid = UUID.randomUUID(); craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs), @@ -138,14 +139,14 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack SmithingRecipeData recipeData = (SmithingRecipeData) recipe.getData(); ItemData output = ItemTranslator.translateToBedrock(session, recipeData.getResult()); for (ItemStack base : recipeData.getBase().getOptions()) { - ItemData bedrockBase = ItemTranslator.translateToBedrock(session, base); + ItemDescriptorWithCount bedrockBase = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, base)); for (ItemStack addition : recipeData.getAddition().getOptions()) { - ItemData bedrockAddition = ItemTranslator.translateToBedrock(session, addition); + ItemDescriptorWithCount bedrockAddition = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, addition)); UUID uuid = UUID.randomUUID(); craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), - Arrays.asList(bedrockBase, bedrockAddition), + List.of(bedrockBase, bedrockAddition), Collections.singletonList(output), uuid, "smithing_table", 2, netId++)); } } @@ -175,6 +176,7 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack // As of 1.16.4, all stonecutter recipes have one ingredient option ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; ItemData input = ItemTranslator.translateToBedrock(session, ingredient); + ItemDescriptorWithCount descriptor = ItemDescriptorWithCount.fromItem(input); ItemStack javaOutput = stoneCuttingData.getResult(); ItemData output = ItemTranslator.translateToBedrock(session, javaOutput); if (input.equals(ItemData.AIR) || output.equals(ItemData.AIR)) { @@ -185,7 +187,7 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack // We need to register stonecutting recipes so they show up on Bedrock craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), - Collections.singletonList(input), Collections.singletonList(output), uuid, "stonecutter", 0, netId)); + Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, netId)); // Save the recipe list for reference when crafting // Add the net ID as the key and the button required + output for the value @@ -206,19 +208,19 @@ public void translate(GeyserSession session, ClientboundUpdateRecipesPacket pack * * @return the Java ingredient list as an array that Bedrock can understand */ - private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredients) { - Map, 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; @@ -231,46 +233,42 @@ private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredient if (entry.getValue().size() < idCount) { optionSet.addAll(entry.getValue()); } else { - optionSet.add(ItemData.builder() - .id(groupedItem.id) - .damage(Short.MAX_VALUE) - .count(groupedItem.count) - .tag(groupedItem.tag).build()); + optionSet.add(groupedItem.id == 0 ? ItemDescriptorWithCount.EMPTY : new ItemDescriptorWithCount(new DefaultDescriptor(groupedItem.id, Short.MAX_VALUE), groupedItem.count)); } } else { - ItemData item = entry.getValue().get(0); + ItemDescriptorWithCount item = entry.getValue().get(0); optionSet.add(item); } } squashedOptions.computeIfAbsent(optionSet, k -> 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++; @@ -285,6 +283,5 @@ private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredient private static class GroupedItem { int id; int count; - NbtMap tag; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityMotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityMotionTranslator.java index bf947382e4f..c627d5e22d0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityMotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityMotionTranslator.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.translator.protocol.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityMotionPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemEntity; import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; @@ -32,10 +35,6 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityMotionPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; - @Translator(packet = ClientboundSetEntityMotionPacket.class) public class JavaSetEntityMotionTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java index 06f141aa66d..68f310db4fa 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java @@ -40,6 +40,6 @@ public void translate(GeyserSession session, ClientboundSoundEntityPacket packet if (entity == null) { return; } - SoundUtils.playBuiltinSound(session, packet.getSound(), entity.getPosition(), packet.getVolume(), packet.getPitch()); + SoundUtils.playSound(session, packet.getSound(), entity.getPosition(), packet.getVolume(), packet.getPitch()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java index 0f1fd4d1b34..656e177891e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java @@ -28,7 +28,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket; import com.nukkitx.protocol.bedrock.packet.DeathInfoPacket; import net.kyori.adventure.text.Component; -import org.geysermc.geyser.network.MinecraftProtocol; +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; @@ -39,11 +39,11 @@ public class JavaPlayerCombatKillTranslator 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 993da7746f9..00000000000 --- 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.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.skin.SkinManager; - -@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 00000000000..a69c031699d --- /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/entity/spawn/JavaAddPlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java index c54b75f4f6a..c4b5aae2dc2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java @@ -30,10 +30,10 @@ 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.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.skin.SkinManager; @Translator(packet = ClientboundAddPlayerPacket.class) public class JavaAddPlayerTranslator extends PacketTranslator { 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 934ee882d50..9f687f046d7 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 void translate(GeyserSession session, ClientboundContainerSetContentPacke inventory.setItem(i, newItem, session); } - InventoryTranslator translator = session.getInventoryTranslator(); - if (translator != null) { - translator.updateInventory(session, inventory); - } + updateInventory(session, inventory, packet.getContainerId()); int stateId = packet.getStateId(); session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId()); @@ -80,4 +75,14 @@ public void translate(GeyserSession session, ClientboundContainerSetContentPacke session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); InventoryUtils.updateCursor(session); } + + private void updateInventory(GeyserSession session, Inventory inventory, int containerId) { + InventoryTranslator translator = session.getInventoryTranslator(); + if (containerId == 0 && !(translator instanceof PlayerInventoryTranslator)) { + // In rare cases, the window ID can still be 0 but Java treats it as valid + InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateInventory(session, inventory); + } else if (translator != null) { + translator.updateInventory(session, inventory); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java index 30c2abe2570..923b10a2683 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java @@ -28,9 +28,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetDataPacket; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.util.InventoryUtils; @Translator(packet = ClientboundContainerSetDataPacket.class) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java index aef8cf8b2e7..869f0cedee6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; 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 com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.GeyserImpl; @@ -76,7 +77,7 @@ public void translate(GeyserSession session, ClientboundContainerSetSlotPacket p int slot = packet.getSlot(); if (slot >= 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 @@ private static void updateCraftingGrid(GeyserSession session, int slot, ItemStac uuid.toString(), width, height, - Arrays.asList(ingredients), + Arrays.stream(ingredients).map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(ItemTranslator.translateToBedrock(session, item)), uuid, "crafting_table", diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java index 727a1778565..46b138076e3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java @@ -36,12 +36,12 @@ import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity; import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.horse.DonkeyInventoryTranslator; import org.geysermc.geyser.translator.inventory.horse.HorseInventoryTranslator; import org.geysermc.geyser.translator.inventory.horse.LlamaInventoryTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.InventoryUtils; import java.util.ArrayList; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index 69f00b01050..2ebe48a8378 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java @@ -54,7 +54,7 @@ public class JavaMerchantOffersTranslator extends PacketTranslator { @@ -45,7 +44,7 @@ public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet Vector3i pos = packet.getEntry().getPosition(); boolean updatePlacement = session.getGeyser().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event session.getGeyser().getWorldManager().getBlockAt(session, pos) != packet.getEntry().getBlock(); - ChunkUtils.updateBlock(session, packet.getEntry().getBlock(), pos); + session.getWorldCache().updateServerCorrectBlockState(pos, packet.getEntry().getBlock()); if (updatePlacement) { this.checkPlace(session, packet); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java index d5ac1abba42..0e90831ffe3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java @@ -28,9 +28,10 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundExplodePacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelEventGenericPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.geyser.level.block.BlockStateValues; @@ -44,19 +45,27 @@ public class JavaExplodeTranslator 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/JavaGameEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaGameEventTranslator.java index a4bdf162ecf..05e14c41b82 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaGameEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaGameEventTranslator.java @@ -31,8 +31,8 @@ import com.github.steveice10.mc.protocol.data.game.level.notify.RainStrengthValue; import com.github.steveice10.mc.protocol.data.game.level.notify.RespawnScreenValue; import com.github.steveice10.mc.protocol.data.game.level.notify.ThunderStrengthValue; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundGameEventPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -40,10 +40,10 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; -import org.geysermc.geyser.text.MinecraftLocale; @Translator(packet = ClientboundGameEventPacket.class) public class JavaGameEventTranslator extends PacketTranslator { @@ -153,7 +153,7 @@ public void translate(GeyserSession session, ClientboundGameEventPacket packet) case INVALID_BED: // Not sent as a proper message? Odd. session.sendMessage(MinecraftLocale.getLocaleString("block.minecraft.spawn.not_valid", - session.getLocale())); + session.locale())); break; case ARROW_HIT_PLAYER: PlaySoundPacket arrowSoundPacket = new PlaySoundPacket(); 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 c4fabbd3d41..08125144772 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.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; @@ -284,6 +283,9 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke } sectionCount++; + // As of 1.18.30, the amount of biomes read is dependent on how high Bedrock thinks the dimension is + int biomeCount = bedrockDimension.height() >> 4; + // Estimate chunk size int size = 0; for (int i = 0; i < sectionCount; i++) { @@ -294,9 +296,8 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke size += SERIALIZED_CHUNK_DATA.length; } } - size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data + size += ChunkUtils.EMPTY_BIOME_DATA.length * biomeCount; size += 1; // Border blocks - size += 1; // Extra data length (always 0) size += bedrockBlockEntities.size() * 64; // Conservative estimate of 64 bytes per tile entity // Allocate output buffer @@ -310,8 +311,6 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke } } - // As of 1.18.30, the amount of biomes read is dependent on how high Bedrock thinks the dimension is - int biomeCount = bedrockDimension.height() >> 4; int dimensionOffset = bedrockDimension.minY() >> 4; for (int i = 0; i < biomeCount; i++) { int biomeYOffset = dimensionOffset + i; @@ -331,7 +330,6 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke } byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now // Encode tile entities into buffer NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index 8fcfa381f28..797699e2be0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java @@ -77,7 +77,7 @@ public void translate(GeyserSession session, ClientboundLevelEventPacket packet) textPacket.setSourceName(null); textPacket.setMessage("record.nowPlaying"); String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc"; - textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(recordString, session.getLocale()))); + textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(recordString, session.locale()))); session.sendUpstreamPacket(textPacket); } return; @@ -263,7 +263,7 @@ public void translate(GeyserSession session, ClientboundLevelEventPacket packet) LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket(); // TODO add SCULK_BLOCK_CHARGE sound if (eventData.getCharge() > 0) { - levelEventPacket.setEventId(2037); + levelEventPacket.setEventId(2037/*LevelEventType.SCULK_CHARGE*/); levelEventPacket.setTag( NbtMap.builder() .putInt("x", packet.getPosition().getX()) @@ -274,7 +274,7 @@ public void translate(GeyserSession session, ClientboundLevelEventPacket packet) .build() ); } else { - levelEventPacket.setEventId(2038); + levelEventPacket.setEventId(2038/*LevelEventType.SCULK_CHARGE_POP*/); levelEventPacket.setTag( NbtMap.builder() .putInt("x", packet.getPosition().getX()) @@ -288,7 +288,7 @@ public void translate(GeyserSession session, ClientboundLevelEventPacket packet) } case SCULK_SHRIEKER_SHRIEK -> { 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 a413421a3a8..94466a1ab8a 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 @@ -39,12 +39,12 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.util.DimensionUtils; import java.util.Optional; @@ -157,7 +157,7 @@ private Function createParticle(GeyserSession session, return (position) -> { 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 eb658aa5400..3a2d1a05042 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,14 +28,15 @@ 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; +import org.geysermc.geyser.level.MapColor; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.level.BedrockMapIcon; import org.geysermc.geyser.util.DimensionUtils; -import org.geysermc.geyser.level.MapColor; @Translator(packet = ClientboundMapItemDataPacket.class) public class JavaMapItemDataTranslator extends PacketTranslator { @@ -48,7 +49,10 @@ public void translate(GeyserSession session, ClientboundMapItemDataPacket packet mapItemDataPacket.setUniqueMapId(packet.getMapId()); mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); mapItemDataPacket.setLocked(packet.isLocked()); + mapItemDataPacket.setOrigin(Vector3i.ZERO); // Required since 1.19.20 mapItemDataPacket.setScale(packet.getScale()); + // Required as of 1.19.50 + mapItemDataPacket.getTrackedEntityIds().add(packet.getMapId()); MapData data = packet.getData(); if (data != null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index 18796fcef2a..abc9a6c2136 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -30,7 +30,6 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.util.ChunkUtils; @Translator(packet = ClientboundSectionBlocksUpdatePacket.class) public class JavaSectionBlocksUpdateTranslator extends PacketTranslator { @@ -38,7 +37,7 @@ public class JavaSectionBlocksUpdateTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java index 5b83ff5516e..8bd1d94d486 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java @@ -38,6 +38,6 @@ public class JavaSoundTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java index 33da27a88d7..3b009a2a526 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java @@ -31,15 +31,15 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.Scoreboard; +import org.geysermc.geyser.scoreboard.ScoreboardUpdater; +import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.scoreboard.Objective; -import org.geysermc.geyser.scoreboard.Scoreboard; -import org.geysermc.geyser.scoreboard.ScoreboardUpdater; -import org.geysermc.geyser.scoreboard.UpdateType; @Translator(packet = ClientboundSetObjectivePacket.class) public class JavaSetObjectiveTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java index 40129701d03..f942b6f0991 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java @@ -31,14 +31,14 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; import java.util.Arrays; import java.util.Set; @@ -67,8 +67,8 @@ public void translate(GeyserSession session, ClientboundSetPlayerTeamPacket pack .setName(MessageTranslator.convertMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale())); + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.locale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.locale())); if (packet.getPlayers().length != 0) { if ((team.getNameTagVisibility() != NameTagVisibility.ALWAYS && !team.isVisibleFor(session.getPlayerEntity().getUsername())) @@ -98,8 +98,8 @@ public void translate(GeyserSession session, ClientboundSetPlayerTeamPacket pack team.setName(MessageTranslator.convertMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale())) + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.locale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.locale())) .setUpdateType(UpdateType.UPDATE); if (oldVisibility != team.getNameTagVisibility() diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java index 41e9a38a2d4..41b978a8691 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java @@ -33,14 +33,14 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.entity.type.player.PlayerEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.cache.WorldCache; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.scoreboard.Objective; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = ClientboundSetScorePacket.class) public class JavaSetScoreTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetActionBarTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetActionBarTextTranslator.java index c6473e375d4..a534ec372e5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetActionBarTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetActionBarTextTranslator.java @@ -41,7 +41,7 @@ public void translate(GeyserSession session, ClientboundSetActionBarTextPacket p if (packet.getText() == null) { //TODO 1.17 can this happen? text = " "; } else { - text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + text = MessageTranslator.convertMessage(packet.getText(), session.locale()); } SetTitlePacket titlePacket = new SetTitlePacket(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetSubtitleTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetSubtitleTextTranslator.java index 0689a62cdb4..1f4dec59539 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetSubtitleTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetSubtitleTextTranslator.java @@ -41,7 +41,7 @@ public void translate(GeyserSession session, ClientboundSetSubtitleTextPacket pa if (packet.getText() == null) { //TODO 1.17 can this happen? text = " "; } else { - text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + text = MessageTranslator.convertMessage(packet.getText(), session.locale()); } SetTitlePacket titlePacket = new SetTitlePacket(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetTitleTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetTitleTextTranslator.java index 43c69072754..22e5a51b514 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetTitleTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaSetTitleTextTranslator.java @@ -44,7 +44,7 @@ public void translate(GeyserSession session, ClientboundSetTitleTextPacket packe if (packet.getText() == null || Component.empty().equals(packet.getText())) { // This can happen, see https://github.com/KyoriPowered/adventure/issues/447 text = " "; } else { - text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + text = MessageTranslator.convertMessage(packet.getText(), session.locale()); } SetTitlePacket titlePacket = new SetTitlePacket(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/BlockSoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/BlockSoundInteractionTranslator.java index 14f708153ca..ff21be3e885 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/BlockSoundInteractionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/BlockSoundInteractionTranslator.java @@ -32,8 +32,8 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3f; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockUtils; import java.util.Map; diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundInteractionTranslator.java index 536d5e18837..9cb6e40d2e0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundInteractionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundInteractionTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.translator.sound; import com.nukkitx.math.vector.Vector3f; - import org.geysermc.geyser.session.GeyserSession; /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/block/GrassPathInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/block/GrassPathInteractionTranslator.java index 98f460dd764..aff4b5562d4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/block/GrassPathInteractionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/block/GrassPathInteractionTranslator.java @@ -28,10 +28,10 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.sound.BlockSoundInteractionTranslator; import org.geysermc.geyser.translator.sound.SoundTranslator; -import org.geysermc.geyser.registry.BlockRegistries; @SoundTranslator(blocks = "grass_path", items = "shovel", ignoreSneakingWhileHolding = true) public class GrassPathInteractionTranslator implements BlockSoundInteractionTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/block/HoeInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/block/HoeInteractionTranslator.java index 0e1aae95c5a..7d81e5fb02d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/block/HoeInteractionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/block/HoeInteractionTranslator.java @@ -28,10 +28,10 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.sound.BlockSoundInteractionTranslator; import org.geysermc.geyser.translator.sound.SoundTranslator; -import org.geysermc.geyser.registry.BlockRegistries; @SoundTranslator(blocks = "farmland", items = "hoe", ignoreSneakingWhileHolding = true) public class HoeInteractionTranslator implements BlockSoundInteractionTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java index 6f446f1366e..3d5f5fe0f42 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java @@ -27,7 +27,9 @@ import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; +import com.nukkitx.protocol.bedrock.packet.TextPacket; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -36,8 +38,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.*; -import java.util.EnumMap; -import java.util.Map; +import java.util.*; public class MessageTranslator { // These are used for handling the translations of the messages @@ -201,6 +202,28 @@ public static String convertToJavaMessage(String message) { return GSON_SERIALIZER.serialize(component); } + + /** + * Convert legacy format message to plain text + * + * @param message Message to convert + * @return The plain text of the message + */ + public static String convertToPlainText(String message) { + char[] input = message.toCharArray(); + char[] output = new char[input.length]; + int outputSize = 0; + for (int i = 0, inputLength = input.length; i < inputLength; i++) { + char c = input[i]; + if (c == ChatColor.ESCAPE) { + i++; + } else { + output[outputSize++] = c; + } + } + return new String(output, 0, outputSize); + } + /** * Convert JSON and legacy format message to plain text * @@ -228,6 +251,46 @@ public static String convertToPlainText(String message, String locale) { return PlainTextComponentSerializer.plainText().serialize(messageComponent); } + public static void handleChatPacket(GeyserSession session, Component message, int chatType, Component targetName, Component sender) { + TextPacket textPacket = new TextPacket(); + textPacket.setPlatformChatId(""); + textPacket.setSourceName(""); + textPacket.setXuid(session.getAuthData().xuid()); + textPacket.setType(TextPacket.Type.CHAT); + + textPacket.setNeedsTranslation(false); + + TextDecoration decoration = session.getChatTypes().get(chatType); + 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.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 * @@ -247,7 +310,7 @@ public static String toChatColor(TeamColor teamColor) { */ public static boolean isTooLong(String message, GeyserSession session) { if (message.length() > 256) { - session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.too_long", session.locale(), message.length())); return true; } 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 b87f82f9082..501087f7881 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.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; @@ -54,10 +55,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 { @@ -81,20 +78,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) { @@ -125,7 +108,6 @@ public static void updateChunkPosition(GeyserSession session, Vector3i position) 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); } /** @@ -186,11 +168,32 @@ public static void updateBlockClientSide(GeyserSession session, int blockState, } 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); @@ -219,7 +222,8 @@ public static void sendEmptyChunks(GeyserSession session, Vector3i position, int * 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(); @@ -230,13 +234,7 @@ public static void loadDimension(GeyserSession session) { 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 7e5d65a9783..5963da703a5 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 @@ 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 static void switchDimension(GeyserSession session, String javaDimension) 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 static void switchDimension(GeyserSession session, String javaDimension) 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 static void switchDimension(GeyserSession session, String javaDimension) } } + 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 static String getTemporaryDimension(String currentDimension, String newDi // 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/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 5c2905d939b..1f31d917be1 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.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.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.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 static void openInventory(GeyserSession session, Inventory inventory) { 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 static void displayInventory(GeyserSession session, Inventory inventory) 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 static void closeInventory(GeyserSession session, int windowId, boolean c 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 static boolean canStack(GeyserItemStack item1, GeyserItemStack item2) { 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 static IntFunction createUnusableSpaceBlock(String description) 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/ItemUtils.java b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java index 37c4609fed9..92967a10b86 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java @@ -25,7 +25,10 @@ package org.geysermc.geyser.util; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import it.unimi.dsi.fastutil.ints.Int2IntMap; import org.geysermc.geyser.session.GeyserSession; 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 45be5bf9976..f0b8ee6bc2a 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 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 CompoundTag next() { } }; } + + 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 372d4025820..8d832f8fad0 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -230,7 +230,7 @@ public static void buildAndShowLoginWindow(GeyserSession session) { session.sendForm( SimpleForm.builder() - .translator(GeyserLocale::getPlayerLocaleString, session.getLocale()) + .translator(GeyserLocale::getPlayerLocaleString, session.locale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") .optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled) @@ -245,17 +245,11 @@ public static void buildAndShowLoginWindow(GeyserSession session) { } if (response.clickedButtonId() == 1) { - if (isPasswordAuthEnabled) { - session.setMicrosoftAccount(true); - buildAndShowMicrosoftAuthenticationWindow(session); - } else { - // Just show the OAuth code - session.authenticateWithMicrosoftCode(); - } + session.authenticateWithMicrosoftCode(); return; } - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); })); } @@ -263,9 +257,11 @@ public static void buildAndShowLoginWindow(GeyserSession session) { * Build a window that explains the user's credentials will be saved to the system. */ public static void buildAndShowConsentWindow(GeyserSession session) { + String locale = session.locale(); + session.sendForm( SimpleForm.builder() - .translator(LoginEncryptionUtils::translate, session.getLocale()) + .translator(LoginEncryptionUtils::translate, locale) .title("%gui.signIn") .content(""" geyser.auth.login.save_token.warning @@ -278,9 +274,11 @@ public static void buildAndShowConsentWindow(GeyserSession session) { } public static void buildAndShowTokenExpiredWindow(GeyserSession session) { + String locale = session.locale(); + session.sendForm( SimpleForm.builder() - .translator(LoginEncryptionUtils::translate, session.getLocale()) + .translator(LoginEncryptionUtils::translate, locale) .title("geyser.auth.login.form.expired") .content(""" geyser.auth.login.save_token.expired @@ -305,49 +303,22 @@ private static BiConsumer> au public static void buildAndShowLoginDetailsWindow(GeyserSession session) { session.sendForm( CustomForm.builder() - .translator(GeyserLocale::getPlayerLocaleString, session.getLocale()) + .translator(GeyserLocale::getPlayerLocaleString, session.locale()) .title("geyser.auth.login.form.details.title") .label("geyser.auth.login.form.details.desc") .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.getLocale()) - .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.getLocale())); - } - })); - } - /** * Shows the code that a user must input into their browser */ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) { + String locale = session.locale(); + StringBuilder message = new StringBuilder("%xbox.signin.website\n") .append(ChatColor.AQUA) .append("%xbox.signin.url") @@ -359,7 +330,7 @@ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAut if (timeout != 0) { message.append("\n\n") .append(ChatColor.RESET) - .append(GeyserLocale.getPlayerLocaleString("geyser.auth.login.timeout", session.getLocale(), String.valueOf(timeout))); + .append(GeyserLocale.getPlayerLocaleString("geyser.auth.login.timeout", session.locale(), String.valueOf(timeout))); } session.sendForm( @@ -368,10 +339,10 @@ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAut .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", session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", locale)); } }) ); diff --git a/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java b/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java index b543e4a48df..bb92e6cd163 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoopbackUtil.java @@ -35,7 +35,7 @@ public final class LoopbackUtil { private static final String checkExemption = "CheckNetIsolation LoopbackExempt -s"; - private static final String loopbackCommand = "CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'"; + 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. */ @@ -50,12 +50,12 @@ public static boolean needsLoopback(GeyserLogger logger) { if (os.equalsIgnoreCase("Windows 10") || os.equalsIgnoreCase("Windows 11")) { try { Process process = Runtime.getRuntime().exec(checkExemption); - process.waitFor(); InputStream is = process.getInputStream(); + int data; StringBuilder sb = new StringBuilder(); - while (is.available() != 0) { - sb.append((char) is.read()); + while ((data = is.read()) != -1) { + sb.append((char) data); } return !sb.toString().contains(minecraftApplication); diff --git a/core/src/main/java/org/geysermc/geyser/util/NewsHandler.java b/core/src/main/java/org/geysermc/geyser/util/NewsHandler.java index 31e3116d528..71e7c99c12a 100644 --- a/core/src/main/java/org/geysermc/geyser/util/NewsHandler.java +++ b/core/src/main/java/org/geysermc/geyser/util/NewsHandler.java @@ -29,15 +29,15 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; -import org.geysermc.geyser.Constants; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.floodgate.news.NewsItem; import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.floodgate.news.data.AnnouncementData; import org.geysermc.floodgate.news.data.BuildSpecificData; import org.geysermc.floodgate.news.data.CheckAfterData; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import java.util.*; 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 10c4b863cde..5957fb9d98f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -44,7 +44,7 @@ public class SettingsUtils { */ public static CustomForm buildForm(GeyserSession session) { // Cache the language for cleaner access - String language = session.getLocale(); + String language = session.locale(); CustomForm.Builder builder = CustomForm.builder() .translator(SettingsUtils::translateEntry, language) @@ -100,11 +100,7 @@ public static CustomForm buildForm(GeyserSession session) { .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 static CustomForm buildForm(GeyserSession session) { 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 15348939c6a..fd694f53ee1 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -26,7 +26,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,34 +62,38 @@ private static SoundEvent toSoundEvent(String sound) { /** * 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 ""; - } - - // Drop the Minecraft namespace if applicable - if (packetSound.startsWith("minecraft:")) { - packetSound = packetSound.substring("minecraft:".length()); - } + public static String translatePlaySound(String javaIdentifier) { + javaIdentifier = trim(javaIdentifier); - 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(); } + private static String trim(String identifier) { + // Drop the Minecraft namespace if applicable + if (identifier.startsWith("minecraft:")) { + return identifier.substring("minecraft:".length()); + } + return identifier; + } + + private static void playSound(GeyserSession session, String bedrockName, Vector3f position, float volume, float pitch) { + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound(bedrockName); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(volume); + playSoundPacket.setPitch(pitch); + session.sendUpstreamPacket(playSoundPacket); + } + /** * Translates and plays a Java Builtin Sound for a Bedrock client * @@ -99,23 +102,25 @@ public static String translatePlaySound(Sound sound) { * @param position the position * @param pitch the pitch */ - public static void playBuiltinSound(GeyserSession session, BuiltinSound javaSound, Vector3f position, float volume, float pitch) { - String packetSound = javaSound.getName(); + public static void playSound(GeyserSession session, Sound javaSound, Vector3f position, float volume, float pitch) { + String packetSound; + if (!(javaSound instanceof BuiltinSound)) { + // Identifier needs trimmed probably. + packetSound = trim(javaSound.getName()); + } else { + packetSound = javaSound.getName(); + } SoundMapping soundMapping = Registries.SOUNDS.get(packetSound); if (soundMapping == null) { - session.getGeyser().getLogger().debug("[Builtin] Sound mapping for " + packetSound + " not found"); + session.getGeyser().getLogger().debug("[Builtin] Sound mapping for " + packetSound + " not found; assuming custom."); + playSound(session, packetSound, position, volume, pitch); return; } if (soundMapping.getPlaysound() != null) { // We always prefer the PlaySound mapping because we can control volume and pitch - PlaySoundPacket playSoundPacket = new PlaySoundPacket(); - playSoundPacket.setSound(soundMapping.getPlaysound()); - playSoundPacket.setPosition(position); - playSoundPacket.setVolume(volume); - playSoundPacket.setPitch(pitch); - session.sendUpstreamPacket(playSoundPacket); + playSound(session, soundMapping.getPlaysound(), position, volume, pitch); return; } 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 7e1f6e7b784..d46a759fe42 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 149656fd93e..58e0b131a30 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 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; @@ -52,7 +52,7 @@ public class StatisticsUtils { */ public static void buildAndSendStatisticsMenu(GeyserSession session) { // Cache the language for cleaner access - String language = session.getLocale(); + String language = session.locale(); session.sendForm( SimpleForm.builder() @@ -79,23 +79,23 @@ public static void buildAndSendStatisticsMenu(GeyserSession session) { 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 static void buildAndSendStatisticsMenu(GeyserSession session) { 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 934680ce1ba..049d7861948 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 static void checkForOutdatedFloodgate(GeyserLogger logger) { } } + 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 f9574f08b61..c0889f1c510 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -73,6 +73,8 @@ public static String getBody(String reqURL) { 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()); } diff --git a/core/src/main/java/org/geysermc/geyser/util/collection/LecternHasBookMap.java b/core/src/main/java/org/geysermc/geyser/util/collection/LecternHasBookMap.java index 73cb68df1ac..913ea44d5a3 100644 --- a/core/src/main/java/org/geysermc/geyser/util/collection/LecternHasBookMap.java +++ b/core/src/main/java/org/geysermc/geyser/util/collection/LecternHasBookMap.java @@ -27,9 +27,9 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; +import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; -import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.util.BlockEntityUtils; /** diff --git a/core/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/core/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 463d1efad14..00000000000 --- a/core/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1,5 +0,0 @@ -org.geysermc.processor.BlockEntityProcessor -org.geysermc.processor.CollisionRemapperProcessor -org.geysermc.processor.ItemRemapperProcessor -org.geysermc.processor.PacketTranslatorProcessor -org.geysermc.processor.SoundHandlerProcessor \ No newline at end of file diff --git a/core/src/main/resources/bedrock/biome_definitions.dat b/core/src/main/resources/bedrock/biome_definitions.dat index 0520c61e28f..47b19ab44fb 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 8c095ad027b..00000000000 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 00000000000..75d84b6a767 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 00000000000..8b53566c5c9 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_10.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_10.json rename to core/src/main/resources/bedrock/creative_items.1_19_20.json index 50e09d830d9..98d9e007a2e 100644 --- a/core/src/main/resources/bedrock/creative_items.1_19_10.json +++ b/core/src/main/resources/bedrock/creative_items.1_19_20.json @@ -2,167 +2,167 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 6071 + "blockRuntimeId" : 6073 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6072 + "blockRuntimeId" : 6074 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6073 + "blockRuntimeId" : 6075 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6074 + "blockRuntimeId" : 6076 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6075 + "blockRuntimeId" : 6077 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6076 + "blockRuntimeId" : 6078 }, { "id" : "minecraft:mangrove_planks", - "blockRuntimeId" : 947 + "blockRuntimeId" : 949 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 4850 + "blockRuntimeId" : 4852 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 920 + "blockRuntimeId" : 922 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1182 + "blockRuntimeId" : 1184 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1183 + "blockRuntimeId" : 1185 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1184 + "blockRuntimeId" : 1186 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1185 + "blockRuntimeId" : 1187 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1186 + "blockRuntimeId" : 1188 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1187 + "blockRuntimeId" : 1189 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1194 + "blockRuntimeId" : 1196 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1189 + "blockRuntimeId" : 1191 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1190 + "blockRuntimeId" : 1192 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1188 + "blockRuntimeId" : 1190 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1191 + "blockRuntimeId" : 1193 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1195 + "blockRuntimeId" : 1197 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1192 + "blockRuntimeId" : 1194 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1193 + "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 + "blockRuntimeId" : 732 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7364 + "blockRuntimeId" : 7366 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7365 + "blockRuntimeId" : 7367 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7366 + "blockRuntimeId" : 7368 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7367 + "blockRuntimeId" : 7369 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7368 + "blockRuntimeId" : 7370 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7369 + "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,343 +464,343 @@ }, { "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" : 1140 + "blockRuntimeId" : 1142 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1148 + "blockRuntimeId" : 1150 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1145 + "blockRuntimeId" : 1147 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1147 + "blockRuntimeId" : 1149 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1134 + "blockRuntimeId" : 1136 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1137 + "blockRuntimeId" : 1139 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1138 + "blockRuntimeId" : 1140 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1146 + "blockRuntimeId" : 1148 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1142 + "blockRuntimeId" : 1144 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1136 + "blockRuntimeId" : 1138 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1144 + "blockRuntimeId" : 1146 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1143 + "blockRuntimeId" : 1145 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1135 + "blockRuntimeId" : 1137 }, { "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" : 4859 + "blockRuntimeId" : 4861 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4867 + "blockRuntimeId" : 4869 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4864 + "blockRuntimeId" : 4866 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4866 + "blockRuntimeId" : 4868 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4853 + "blockRuntimeId" : 4855 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4856 + "blockRuntimeId" : 4858 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4857 + "blockRuntimeId" : 4859 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4865 + "blockRuntimeId" : 4867 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4861 + "blockRuntimeId" : 4863 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4855 + "blockRuntimeId" : 4857 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4863 + "blockRuntimeId" : 4865 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4862 + "blockRuntimeId" : 4864 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4854 + "blockRuntimeId" : 4856 }, { "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 + "blockRuntimeId" : 5795 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5270 + "blockRuntimeId" : 5272 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5271 + "blockRuntimeId" : 5273 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5272 + "blockRuntimeId" : 5274 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5273 + "blockRuntimeId" : 5275 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5274 + "blockRuntimeId" : 5276 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5275 + "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" : 5810 + "blockRuntimeId" : 5812 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5811 + "blockRuntimeId" : 5813 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5808 + "blockRuntimeId" : 5810 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5809 + "blockRuntimeId" : 5811 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5807 + "blockRuntimeId" : 5809 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5806 + "blockRuntimeId" : 5808 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4274 + "blockRuntimeId" : 4276 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4277 + "blockRuntimeId" : 4279 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5795 + "blockRuntimeId" : 5797 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5804 + "blockRuntimeId" : 5806 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4276 + "blockRuntimeId" : 4278 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5821 + "blockRuntimeId" : 5823 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5789 + "blockRuntimeId" : 5791 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5790 + "blockRuntimeId" : 5792 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5791 + "blockRuntimeId" : 5793 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5792 + "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,47 +824,47 @@ }, { "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 + "blockRuntimeId" : 6353 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6547 + "blockRuntimeId" : 6549 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6548 + "blockRuntimeId" : 6550 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6549 + "blockRuntimeId" : 6551 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6550 + "blockRuntimeId" : 6552 }, { "id" : "minecraft:end_bricks", @@ -872,47 +872,47 @@ }, { "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,51 +920,51 @@ }, { "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 6670 + "blockRuntimeId" : 6672 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 4582 + "blockRuntimeId" : 4584 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3653 + "blockRuntimeId" : 3655 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3654 + "blockRuntimeId" : 3656 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3655 + "blockRuntimeId" : 3657 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3656 + "blockRuntimeId" : 3658 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6580 + "blockRuntimeId" : 6582 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6581 + "blockRuntimeId" : 6583 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6582 + "blockRuntimeId" : 6584 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6583 + "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,67 +1048,67 @@ }, { "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 + "blockRuntimeId" : 363 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3696 + "blockRuntimeId" : 3698 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3698 + "blockRuntimeId" : 3700 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3697 + "blockRuntimeId" : 3699 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3699 + "blockRuntimeId" : 3701 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6085 + "blockRuntimeId" : 6087 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6086 + "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,371 +1116,371 @@ }, { "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" : 3465 + "blockRuntimeId" : 3467 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3473 + "blockRuntimeId" : 3475 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3470 + "blockRuntimeId" : 3472 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3472 + "blockRuntimeId" : 3474 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3459 + "blockRuntimeId" : 3461 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3462 + "blockRuntimeId" : 3464 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3463 + "blockRuntimeId" : 3465 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3471 + "blockRuntimeId" : 3473 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3467 + "blockRuntimeId" : 3469 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3461 + "blockRuntimeId" : 3463 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3469 + "blockRuntimeId" : 3471 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3468 + "blockRuntimeId" : 3470 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3460 + "blockRuntimeId" : 3462 }, { "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" : 956 + "blockRuntimeId" : 958 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 964 + "blockRuntimeId" : 966 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 961 + "blockRuntimeId" : 963 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 963 + "blockRuntimeId" : 965 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 950 + "blockRuntimeId" : 952 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 953 + "blockRuntimeId" : 955 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 954 + "blockRuntimeId" : 956 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 962 + "blockRuntimeId" : 964 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 958 + "blockRuntimeId" : 960 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 952 + "blockRuntimeId" : 954 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 960 + "blockRuntimeId" : 962 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 959 + "blockRuntimeId" : 961 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 951 + "blockRuntimeId" : 953 }, { "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" : 6271 + "blockRuntimeId" : 6273 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6279 + "blockRuntimeId" : 6281 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6276 + "blockRuntimeId" : 6278 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6278 + "blockRuntimeId" : 6280 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6265 + "blockRuntimeId" : 6267 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6268 + "blockRuntimeId" : 6270 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6269 + "blockRuntimeId" : 6271 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6277 + "blockRuntimeId" : 6279 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6273 + "blockRuntimeId" : 6275 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6267 + "blockRuntimeId" : 6269 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6275 + "blockRuntimeId" : 6277 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6274 + "blockRuntimeId" : 6276 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6266 + "blockRuntimeId" : 6268 }, { "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" : 667 + "blockRuntimeId" : 669 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 675 + "blockRuntimeId" : 677 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 672 + "blockRuntimeId" : 674 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 674 + "blockRuntimeId" : 676 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 661 + "blockRuntimeId" : 663 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 664 + "blockRuntimeId" : 666 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 665 + "blockRuntimeId" : 667 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 673 + "blockRuntimeId" : 675 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 669 + "blockRuntimeId" : 671 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 663 + "blockRuntimeId" : 665 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 671 + "blockRuntimeId" : 673 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 670 + "blockRuntimeId" : 672 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 662 + "blockRuntimeId" : 664 }, { "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" : 6183 + "blockRuntimeId" : 6185 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6191 + "blockRuntimeId" : 6193 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6188 + "blockRuntimeId" : 6190 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6190 + "blockRuntimeId" : 6192 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6177 + "blockRuntimeId" : 6179 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6180 + "blockRuntimeId" : 6182 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6181 + "blockRuntimeId" : 6183 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6189 + "blockRuntimeId" : 6191 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6185 + "blockRuntimeId" : 6187 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6179 + "blockRuntimeId" : 6181 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6187 + "blockRuntimeId" : 6189 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6186 + "blockRuntimeId" : 6188 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6178 + "blockRuntimeId" : 6180 }, { "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,39 +1488,39 @@ }, { "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 + "blockRuntimeId" : 6541 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7714 + "blockRuntimeId" : 7716 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7716 + "blockRuntimeId" : 7718 }, { "id" : "minecraft:packed_mud", @@ -1528,31 +1528,31 @@ }, { "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,23 +1680,23 @@ }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 8287 + "blockRuntimeId" : 8289 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 654 + "blockRuntimeId" : 656 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 656 + "blockRuntimeId" : 658 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 658 + "blockRuntimeId" : 660 }, { "id" : "minecraft:blackstone", - "blockRuntimeId" : 7585 + "blockRuntimeId" : 7587 }, { "id" : "minecraft:deepslate", @@ -1704,79 +1704,79 @@ }, { "id" : "minecraft:stone", - "blockRuntimeId" : 655 + "blockRuntimeId" : 657 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 657 + "blockRuntimeId" : 659 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 659 + "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 + "blockRuntimeId" : 6988 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6672 + "blockRuntimeId" : 6674 }, { "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7543 + "blockRuntimeId" : 7545 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6673 + "blockRuntimeId" : 6675 }, { "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6288 + "blockRuntimeId" : 6290 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6674 + "blockRuntimeId" : 6676 }, { "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 5972 + "blockRuntimeId" : 5974 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6675 + "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,167 +1784,167 @@ }, { "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 + "blockRuntimeId" : 7402 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3474 + "blockRuntimeId" : 3476 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3480 + "blockRuntimeId" : 3482 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3475 + "blockRuntimeId" : 3477 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3481 + "blockRuntimeId" : 3483 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3476 + "blockRuntimeId" : 3478 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3482 + "blockRuntimeId" : 3484 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3477 + "blockRuntimeId" : 3479 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3483 + "blockRuntimeId" : 3485 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3478 + "blockRuntimeId" : 3480 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3484 + "blockRuntimeId" : 3486 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3479 + "blockRuntimeId" : 3481 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3485 + "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 + "blockRuntimeId" : 5581 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6090 + "blockRuntimeId" : 6092 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6091 + "blockRuntimeId" : 6093 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6092 + "blockRuntimeId" : 6094 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6093 + "blockRuntimeId" : 6095 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4353 + "blockRuntimeId" : 4355 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4354 + "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 + "blockRuntimeId" : 6341 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 712 + "blockRuntimeId" : 714 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 713 + "blockRuntimeId" : 715 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 714 + "blockRuntimeId" : 716 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 715 + "blockRuntimeId" : 717 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 716 + "blockRuntimeId" : 718 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 717 + "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,45 +2003,49 @@ }, { "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" : 6494 + }, { "id" : "minecraft:coral", "blockRuntimeId" : 6492 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6490 + "blockRuntimeId" : 6493 }, { "id" : "minecraft:coral", @@ -2049,11 +2053,11 @@ }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6489 + "blockRuntimeId" : 6495 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6493 + "blockRuntimeId" : 6499 }, { "id" : "minecraft:coral", @@ -2061,7 +2065,7 @@ }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6495 + "blockRuntimeId" : 6498 }, { "id" : "minecraft:coral", @@ -2069,11 +2073,11 @@ }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6494 + "blockRuntimeId" : 6500 }, { - "id" : "minecraft:coral", - "blockRuntimeId" : 6498 + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4618 }, { "id" : "minecraft:coral_fan", @@ -2081,7 +2085,7 @@ }, { "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 @@ -2178,25 +2170,33 @@ "id" : "minecraft:red_flower", "blockRuntimeId" : 3626 }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3627 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3628 + }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5452 + "blockRuntimeId" : 5454 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5453 + "blockRuntimeId" : 5455 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5456 + "blockRuntimeId" : 5458 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5457 + "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,66 +2469,66 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 6713 + "blockRuntimeId" : 6715 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 401 + "blockRuntimeId" : 403 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4144 + "blockRuntimeId" : 4146 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4145 + "blockRuntimeId" : 4147 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4146 + "blockRuntimeId" : 4148 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4147 + "blockRuntimeId" : 4149 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4148 + "blockRuntimeId" : 4150 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4149 + "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" @@ -2745,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" @@ -2790,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", @@ -2836,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", @@ -2854,11 +2854,11 @@ }, { "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 4389 + "blockRuntimeId" : 4391 }, { "id" : "minecraft:reinforced_deepslate", - "blockRuntimeId" : 5832 + "blockRuntimeId" : 5834 }, { "id" : "minecraft:leather_helmet" @@ -3919,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", @@ -4006,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" @@ -4032,152 +4032,152 @@ }, { "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" : 5323 + "blockRuntimeId" : 5325 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5331 + "blockRuntimeId" : 5333 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5328 + "blockRuntimeId" : 5330 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5330 + "blockRuntimeId" : 5332 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5317 + "blockRuntimeId" : 5319 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5320 + "blockRuntimeId" : 5322 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5321 + "blockRuntimeId" : 5323 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5329 + "blockRuntimeId" : 5331 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5325 + "blockRuntimeId" : 5327 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5319 + "blockRuntimeId" : 5321 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5327 + "blockRuntimeId" : 5329 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5326 + "blockRuntimeId" : 5328 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5318 + "blockRuntimeId" : 5320 }, { "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" @@ -4232,7 +4232,7 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 3885 + "blockRuntimeId" : 3887 }, { "id" : "minecraft:redstone_lamp", @@ -4240,7 +4240,7 @@ }, { "id" : "minecraft:sea_lantern", - "blockRuntimeId" : 7546 + "blockRuntimeId" : 7548 }, { "id" : "minecraft:oak_sign" @@ -4349,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" @@ -4500,11 +4500,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 5891 + "blockRuntimeId" : 5893 }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 1176 + "blockRuntimeId" : 1178 }, { "id" : "minecraft:end_crystal" @@ -5002,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", @@ -5033,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", @@ -5061,7 +5061,7 @@ }, { "id" : "minecraft:acacia_button", - "blockRuntimeId" : 7231 + "blockRuntimeId" : 7233 }, { "id" : "minecraft:dark_oak_button", @@ -5069,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", @@ -5129,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" @@ -5162,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" @@ -5431,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_0.json b/core/src/main/resources/bedrock/creative_items.1_19_50.json similarity index 81% rename from core/src/main/resources/bedrock/creative_items.1_19_0.json rename to core/src/main/resources/bedrock/creative_items.1_19_50.json index 3a356a802f5..4ed5f819462 100644 --- a/core/src/main/resources/bedrock/creative_items.1_19_0.json +++ b/core/src/main/resources/bedrock/creative_items.1_19_50.json @@ -2,167 +2,179 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 6071 + "blockRuntimeId" : 9805 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6072 + "blockRuntimeId" : 9806 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6073 + "blockRuntimeId" : 9807 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6074 + "blockRuntimeId" : 9808 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6075 + "blockRuntimeId" : 9809 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 6076 + "blockRuntimeId" : 9810 }, { "id" : "minecraft:mangrove_planks", - "blockRuntimeId" : 947 + "blockRuntimeId" : 1570 + }, + { + "id" : "minecraft:bamboo_planks", + "blockRuntimeId" : 8202 + }, + { + "id" : "minecraft:bamboo_mosaic", + "blockRuntimeId" : 12438 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 4850 + "blockRuntimeId" : 7399 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 920 + "blockRuntimeId" : 1543 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1182 + "blockRuntimeId" : 1805 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1183 + "blockRuntimeId" : 1806 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1184 + "blockRuntimeId" : 1807 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1185 + "blockRuntimeId" : 1808 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1186 + "blockRuntimeId" : 1809 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1187 + "blockRuntimeId" : 1810 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1194 + "blockRuntimeId" : 1817 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1189 + "blockRuntimeId" : 1812 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1190 + "blockRuntimeId" : 1813 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1188 + "blockRuntimeId" : 1811 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1191 + "blockRuntimeId" : 1814 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1195 + "blockRuntimeId" : 1818 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1192 + "blockRuntimeId" : 1815 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1193 + "blockRuntimeId" : 1816 }, { "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 3930 + "blockRuntimeId" : 5707 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6724 + "blockRuntimeId" : 10496 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 971 + "blockRuntimeId" : 1594 }, { "id" : "minecraft:cobbled_deepslate_wall", - "blockRuntimeId" : 8082 + "blockRuntimeId" : 12260 }, { "id" : "minecraft:deepslate_tile_wall", - "blockRuntimeId" : 5071 + "blockRuntimeId" : 7636 }, { "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 7817 + "blockRuntimeId" : 11995 }, { "id" : "minecraft:deepslate_brick_wall", - "blockRuntimeId" : 429 + "blockRuntimeId" : 659 }, { "id" : "minecraft:mud_brick_wall", - "blockRuntimeId" : 730 + "blockRuntimeId" : 1353 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7364 + "blockRuntimeId" : 11542 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7365 + "blockRuntimeId" : 11543 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7366 + "blockRuntimeId" : 11544 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7367 + "blockRuntimeId" : 11545 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7368 + "blockRuntimeId" : 11546 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 7369 + "blockRuntimeId" : 11547 }, { "id" : "minecraft:mangrove_fence", - "blockRuntimeId" : 6633 + "blockRuntimeId" : 10405 + }, + { + "id" : "minecraft:bamboo_fence", + "blockRuntimeId" : 863 }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 4290 + "blockRuntimeId" : 6071 }, { "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 7996 + "blockRuntimeId" : 12174 }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 5853 + "blockRuntimeId" : 8819 }, { "id" : "minecraft:fence_gate", @@ -170,47 +182,51 @@ }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 6584 + "blockRuntimeId" : 10356 }, { "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 3777 + "blockRuntimeId" : 5170 }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5365 + "blockRuntimeId" : 7946 }, { "id" : "minecraft:acacia_fence_gate", - "blockRuntimeId" : 7586 + "blockRuntimeId" : 11764 }, { "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 4173 + "blockRuntimeId" : 5950 }, { "id" : "minecraft:mangrove_fence_gate", - "blockRuntimeId" : 4625 + "blockRuntimeId" : 6406 + }, + { + "id" : "minecraft:bamboo_fence_gate", + "blockRuntimeId" : 7611 }, { "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 4661 + "blockRuntimeId" : 6826 }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 5399 + "blockRuntimeId" : 7980 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 633 + "blockRuntimeId" : 864 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 3708 + "blockRuntimeId" : 5101 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 4092 + "blockRuntimeId" : 5869 }, { "id" : "minecraft:oak_stairs", @@ -222,75 +238,83 @@ }, { "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 7003 + "blockRuntimeId" : 10781 }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 6967 + "blockRuntimeId" : 10745 }, { "id" : "minecraft:acacia_stairs", - "blockRuntimeId" : 6200 + "blockRuntimeId" : 9950 }, { "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 5063 + "blockRuntimeId" : 7628 }, { "id" : "minecraft:mangrove_stairs", - "blockRuntimeId" : 4595 + "blockRuntimeId" : 6376 + }, + { + "id" : "minecraft:bamboo_stairs", + "blockRuntimeId" : 1339 + }, + { + "id" : "minecraft:bamboo_mosaic_stairs", + "blockRuntimeId" : 9958 }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 931 + "blockRuntimeId" : 1554 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5883 + "blockRuntimeId" : 9233 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 3587 + "blockRuntimeId" : 4980 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 3627 + "blockRuntimeId" : 5020 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 5350 + "blockRuntimeId" : 7931 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 5546 + "blockRuntimeId" : 8127 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 3537 + "blockRuntimeId" : 4546 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 4150 + "blockRuntimeId" : 5927 }, { "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 4391 + "blockRuntimeId" : 6172 }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6714 + "blockRuntimeId" : 10486 }, { "id" : "minecraft:andesite_stairs", - "blockRuntimeId" : 5308 + "blockRuntimeId" : 7889 }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 7028 + "blockRuntimeId" : 10806 }, { "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 6530 + "blockRuntimeId" : 10302 }, { "id" : "minecraft:nether_brick_stairs", @@ -298,31 +322,31 @@ }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6602 + "blockRuntimeId" : 10374 }, { "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 6382 + "blockRuntimeId" : 10140 }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 4767 + "blockRuntimeId" : 6932 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 7700 + "blockRuntimeId" : 11878 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 7755 + "blockRuntimeId" : 11933 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 7263 + "blockRuntimeId" : 11441 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 7430 + "blockRuntimeId" : 11608 }, { "id" : "minecraft:prismarine_bricks_stairs", @@ -330,55 +354,55 @@ }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 6280 + "blockRuntimeId" : 10038 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 3718 + "blockRuntimeId" : 5111 }, { "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 7019 + "blockRuntimeId" : 10797 }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 4297 + "blockRuntimeId" : 6078 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 4477 + "blockRuntimeId" : 6258 }, { "id" : "minecraft:cut_copper_stairs", - "blockRuntimeId" : 4604 + "blockRuntimeId" : 6385 }, { "id" : "minecraft:exposed_cut_copper_stairs", - "blockRuntimeId" : 4587 + "blockRuntimeId" : 6368 }, { "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 4305 + "blockRuntimeId" : 6086 }, { "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 351 + "blockRuntimeId" : 581 }, { "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 393 + "blockRuntimeId" : 623 }, { "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 3902 + "blockRuntimeId" : 5679 }, { "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 6167 + "blockRuntimeId" : 9917 }, { "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 5840 + "blockRuntimeId" : 8806 }, { "id" : "minecraft:cobbled_deepslate_stairs", @@ -386,19 +410,19 @@ }, { "id" : "minecraft:deepslate_tile_stairs", - "blockRuntimeId" : 4653 + "blockRuntimeId" : 6818 }, { "id" : "minecraft:polished_deepslate_stairs", - "blockRuntimeId" : 294 + "blockRuntimeId" : 522 }, { "id" : "minecraft:deepslate_brick_stairs", - "blockRuntimeId" : 7422 + "blockRuntimeId" : 11600 }, { "id" : "minecraft:mud_brick_stairs", - "blockRuntimeId" : 5522 + "blockRuntimeId" : 8103 }, { "id" : "minecraft:wooden_door" @@ -421,6 +445,9 @@ { "id" : "minecraft:mangrove_door" }, + { + "id" : "minecraft:bamboo_door" + }, { "id" : "minecraft:iron_door" }, @@ -436,371 +463,383 @@ }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 6552 + "blockRuntimeId" : 10324 }, { "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 6650 + "blockRuntimeId" : 10422 }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5381 + "blockRuntimeId" : 7962 }, { "id" : "minecraft:acacia_trapdoor", - "blockRuntimeId" : 5589 + "blockRuntimeId" : 8170 }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 7502 + "blockRuntimeId" : 11680 }, { "id" : "minecraft:mangrove_trapdoor", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 6266 + }, + { + "id" : "minecraft:bamboo_trapdoor", + "blockRuntimeId" : 7828 }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 321 + "blockRuntimeId" : 549 }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 4333 + "blockRuntimeId" : 6114 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 4733 + "blockRuntimeId" : 6898 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 4801 + "blockRuntimeId" : 6966 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 6164 + "blockRuntimeId" : 9914 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1133 + "blockRuntimeId" : 1756 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1141 + "blockRuntimeId" : 1764 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1140 + "blockRuntimeId" : 1763 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1148 + "blockRuntimeId" : 1771 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1145 + "blockRuntimeId" : 1768 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1147 + "blockRuntimeId" : 1770 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1134 + "blockRuntimeId" : 1757 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1137 + "blockRuntimeId" : 1760 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1138 + "blockRuntimeId" : 1761 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1146 + "blockRuntimeId" : 1769 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1142 + "blockRuntimeId" : 1765 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1136 + "blockRuntimeId" : 1759 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1144 + "blockRuntimeId" : 1767 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1143 + "blockRuntimeId" : 1766 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1135 + "blockRuntimeId" : 1758 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1139 + "blockRuntimeId" : 1762 }, { "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 5975 + "blockRuntimeId" : 9325 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 5233 + "blockRuntimeId" : 7798 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4852 + "blockRuntimeId" : 7401 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4860 + "blockRuntimeId" : 7409 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4859 + "blockRuntimeId" : 7408 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4867 + "blockRuntimeId" : 7416 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4864 + "blockRuntimeId" : 7413 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4866 + "blockRuntimeId" : 7415 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4853 + "blockRuntimeId" : 7402 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4856 + "blockRuntimeId" : 7405 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4857 + "blockRuntimeId" : 7406 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4865 + "blockRuntimeId" : 7414 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4861 + "blockRuntimeId" : 7410 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4855 + "blockRuntimeId" : 7404 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4863 + "blockRuntimeId" : 7412 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4862 + "blockRuntimeId" : 7411 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4854 + "blockRuntimeId" : 7403 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4858 + "blockRuntimeId" : 7407 }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 8262 + "blockRuntimeId" : 12441 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 3571 + "blockRuntimeId" : 4964 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4270 + "blockRuntimeId" : 6049 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 8404 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4273 + "blockRuntimeId" : 6052 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5793 + "blockRuntimeId" : 8375 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5270 + "blockRuntimeId" : 7851 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5271 + "blockRuntimeId" : 7852 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5272 + "blockRuntimeId" : 7853 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5273 + "blockRuntimeId" : 7854 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5274 + "blockRuntimeId" : 7855 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5275 + "blockRuntimeId" : 7856 }, { "id" : "minecraft:mangrove_slab", - "blockRuntimeId" : 1149 + "blockRuntimeId" : 1772 + }, + { + "id" : "minecraft:bamboo_slab", + "blockRuntimeId" : 10300 + }, + { + "id" : "minecraft:bamboo_mosaic_slab", + "blockRuntimeId" : 4081 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4275 + "blockRuntimeId" : 6054 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5820 + "blockRuntimeId" : 8402 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4271 + "blockRuntimeId" : 6050 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5823 + "blockRuntimeId" : 8405 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5794 + "blockRuntimeId" : 8376 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5788 + "blockRuntimeId" : 8370 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5824 + "blockRuntimeId" : 8406 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5805 + "blockRuntimeId" : 8387 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5810 + "blockRuntimeId" : 8392 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5811 + "blockRuntimeId" : 8393 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5808 + "blockRuntimeId" : 8390 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5809 + "blockRuntimeId" : 8391 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5807 + "blockRuntimeId" : 8389 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5806 + "blockRuntimeId" : 8388 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4274 + "blockRuntimeId" : 6053 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4277 + "blockRuntimeId" : 6056 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5795 + "blockRuntimeId" : 8377 }, { "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5804 + "blockRuntimeId" : 8386 }, { "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4276 + "blockRuntimeId" : 6055 }, { "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5821 + "blockRuntimeId" : 8403 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5789 + "blockRuntimeId" : 8371 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5790 + "blockRuntimeId" : 8372 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5791 + "blockRuntimeId" : 8373 }, { "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5792 + "blockRuntimeId" : 8374 }, { "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 5900 + "blockRuntimeId" : 9250 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 6484 + "blockRuntimeId" : 10254 }, { "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 910 + "blockRuntimeId" : 1533 }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6018 + "blockRuntimeId" : 9752 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 4192 + "blockRuntimeId" : 5971 }, { "id" : "minecraft:cut_copper_slab", - "blockRuntimeId" : 5235 + "blockRuntimeId" : 7800 }, { "id" : "minecraft:exposed_cut_copper_slab", - "blockRuntimeId" : 6600 + "blockRuntimeId" : 10372 }, { "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 6053 + "blockRuntimeId" : 9787 }, { "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5282 + "blockRuntimeId" : 7863 }, { "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7815 + "blockRuntimeId" : 11993 }, { "id" : "minecraft:waxed_exposed_cut_copper_slab", @@ -808,15 +847,15 @@ }, { "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 6545 + "blockRuntimeId" : 10317 }, { "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 708 + "blockRuntimeId" : 1323 }, { "id" : "minecraft:cobbled_deepslate_slab", - "blockRuntimeId" : 7310 + "blockRuntimeId" : 11488 }, { "id" : "minecraft:polished_deepslate_slab", @@ -824,47 +863,47 @@ }, { "id" : "minecraft:deepslate_tile_slab", - "blockRuntimeId" : 4291 + "blockRuntimeId" : 6072 }, { "id" : "minecraft:deepslate_brick_slab", - "blockRuntimeId" : 3716 + "blockRuntimeId" : 5109 }, { "id" : "minecraft:mud_brick_slab", - "blockRuntimeId" : 3910 + "blockRuntimeId" : 5687 }, { "id" : "minecraft:brick_block", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 6930 }, { "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 7249 + "blockRuntimeId" : 11427 }, { "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 4552 + "blockRuntimeId" : 6333 }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6351 + "blockRuntimeId" : 10109 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6547 + "blockRuntimeId" : 10319 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6548 + "blockRuntimeId" : 10320 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6549 + "blockRuntimeId" : 10321 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6550 + "blockRuntimeId" : 10322 }, { "id" : "minecraft:end_bricks", @@ -872,47 +911,47 @@ }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6087 + "blockRuntimeId" : 9837 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 4680 + "blockRuntimeId" : 6845 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 7214 + "blockRuntimeId" : 11376 }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4586 + "blockRuntimeId" : 6367 }, { "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 5062 + "blockRuntimeId" : 7627 }, { "id" : "minecraft:deepslate_tiles", - "blockRuntimeId" : 4581 + "blockRuntimeId" : 6362 }, { "id" : "minecraft:cracked_deepslate_tiles", - "blockRuntimeId" : 4160 + "blockRuntimeId" : 5937 }, { "id" : "minecraft:deepslate_bricks", - "blockRuntimeId" : 5464 + "blockRuntimeId" : 8045 }, { "id" : "minecraft:cracked_deepslate_bricks", - "blockRuntimeId" : 5364 + "blockRuntimeId" : 7945 }, { "id" : "minecraft:chiseled_deepslate", - "blockRuntimeId" : 5234 + "blockRuntimeId" : 7799 }, { "id" : "minecraft:cobblestone", - "blockRuntimeId" : 3615 + "blockRuntimeId" : 5008 }, { "id" : "minecraft:mossy_cobblestone", @@ -920,119 +959,119 @@ }, { "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 6670 + "blockRuntimeId" : 10442 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 4582 + "blockRuntimeId" : 6363 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3653 + "blockRuntimeId" : 5046 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3654 + "blockRuntimeId" : 5047 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3655 + "blockRuntimeId" : 5048 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 3656 + "blockRuntimeId" : 5049 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6580 + "blockRuntimeId" : 10352 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6581 + "blockRuntimeId" : 10353 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6582 + "blockRuntimeId" : 10354 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6583 + "blockRuntimeId" : 10355 }, { "id" : "minecraft:coal_block", - "blockRuntimeId" : 5398 + "blockRuntimeId" : 7979 }, { "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 7979 + "blockRuntimeId" : 12157 }, { "id" : "minecraft:gold_block", - "blockRuntimeId" : 291 + "blockRuntimeId" : 323 }, { "id" : "minecraft:iron_block", - "blockRuntimeId" : 8261 + "blockRuntimeId" : 12440 }, { "id" : "minecraft:copper_block", - "blockRuntimeId" : 4651 + "blockRuntimeId" : 6816 }, { "id" : "minecraft:exposed_copper", - "blockRuntimeId" : 593 + "blockRuntimeId" : 823 }, { "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 8246 + "blockRuntimeId" : 12424 }, { "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 3553 + "blockRuntimeId" : 4946 }, { "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7734 + "blockRuntimeId" : 11912 }, { "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 694 + "blockRuntimeId" : 1309 }, { "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 707 + "blockRuntimeId" : 1322 }, { "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7542 + "blockRuntimeId" : 11720 }, { "id" : "minecraft:cut_copper", - "blockRuntimeId" : 4689 + "blockRuntimeId" : 6854 }, { "id" : "minecraft:exposed_cut_copper", - "blockRuntimeId" : 6166 + "blockRuntimeId" : 9916 }, { "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7197 + "blockRuntimeId" : 11359 }, { "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5478 + "blockRuntimeId" : 8059 }, { "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7293 + "blockRuntimeId" : 11471 }, { "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 3809 + "blockRuntimeId" : 5202 }, { "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 4851 + "blockRuntimeId" : 7400 }, { "id" : "minecraft:waxed_oxidized_cut_copper", @@ -1040,7 +1079,7 @@ }, { "id" : "minecraft:emerald_block", - "blockRuntimeId" : 1159 + "blockRuntimeId" : 1782 }, { "id" : "minecraft:diamond_block", @@ -1048,67 +1087,67 @@ }, { "id" : "minecraft:lapis_block", - "blockRuntimeId" : 4286 + "blockRuntimeId" : 6065 }, { "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 8260 + "blockRuntimeId" : 12439 }, { "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 5269 + "blockRuntimeId" : 7850 }, { "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 361 + "blockRuntimeId" : 591 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3696 + "blockRuntimeId" : 5089 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3698 + "blockRuntimeId" : 5091 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3697 + "blockRuntimeId" : 5090 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3699 + "blockRuntimeId" : 5092 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6085 + "blockRuntimeId" : 9835 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6086 + "blockRuntimeId" : 9836 }, { "id" : "minecraft:slime", - "blockRuntimeId" : 4233 + "blockRuntimeId" : 6012 }, { "id" : "minecraft:honey_block", - "blockRuntimeId" : 892 + "blockRuntimeId" : 1515 }, { "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 4476 + "blockRuntimeId" : 6257 }, { "id" : "minecraft:hay_block", - "blockRuntimeId" : 695 + "blockRuntimeId" : 1310 }, { "id" : "minecraft:bone_block", - "blockRuntimeId" : 4234 + "blockRuntimeId" : 6013 }, { "id" : "minecraft:nether_brick", - "blockRuntimeId" : 7272 + "blockRuntimeId" : 11450 }, { "id" : "minecraft:red_nether_brick", @@ -1116,371 +1155,371 @@ }, { "id" : "minecraft:netherite_block", - "blockRuntimeId" : 3775 + "blockRuntimeId" : 5168 }, { "id" : "minecraft:lodestone", - "blockRuntimeId" : 8259 + "blockRuntimeId" : 12437 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3458 + "blockRuntimeId" : 4083 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3466 + "blockRuntimeId" : 4091 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3465 + "blockRuntimeId" : 4090 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3473 + "blockRuntimeId" : 4098 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3470 + "blockRuntimeId" : 4095 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3472 + "blockRuntimeId" : 4097 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3459 + "blockRuntimeId" : 4084 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3462 + "blockRuntimeId" : 4087 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3463 + "blockRuntimeId" : 4088 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3471 + "blockRuntimeId" : 4096 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3467 + "blockRuntimeId" : 4092 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3461 + "blockRuntimeId" : 4086 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3469 + "blockRuntimeId" : 4094 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3468 + "blockRuntimeId" : 4093 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3460 + "blockRuntimeId" : 4085 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 3464 + "blockRuntimeId" : 4089 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 949 + "blockRuntimeId" : 1572 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 957 + "blockRuntimeId" : 1580 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 956 + "blockRuntimeId" : 1579 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 964 + "blockRuntimeId" : 1587 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 961 + "blockRuntimeId" : 1584 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 963 + "blockRuntimeId" : 1586 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 950 + "blockRuntimeId" : 1573 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 953 + "blockRuntimeId" : 1576 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 954 + "blockRuntimeId" : 1577 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 962 + "blockRuntimeId" : 1585 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 958 + "blockRuntimeId" : 1581 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 952 + "blockRuntimeId" : 1575 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 960 + "blockRuntimeId" : 1583 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 959 + "blockRuntimeId" : 1582 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 951 + "blockRuntimeId" : 1574 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 955 + "blockRuntimeId" : 1578 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6264 + "blockRuntimeId" : 10022 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6272 + "blockRuntimeId" : 10030 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6271 + "blockRuntimeId" : 10029 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6279 + "blockRuntimeId" : 10037 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6276 + "blockRuntimeId" : 10034 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6278 + "blockRuntimeId" : 10036 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6265 + "blockRuntimeId" : 10023 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6268 + "blockRuntimeId" : 10026 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6269 + "blockRuntimeId" : 10027 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6277 + "blockRuntimeId" : 10035 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6273 + "blockRuntimeId" : 10031 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6267 + "blockRuntimeId" : 10025 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6275 + "blockRuntimeId" : 10033 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6274 + "blockRuntimeId" : 10032 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6266 + "blockRuntimeId" : 10024 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6270 + "blockRuntimeId" : 10028 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 660 + "blockRuntimeId" : 1275 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 668 + "blockRuntimeId" : 1283 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 667 + "blockRuntimeId" : 1282 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 675 + "blockRuntimeId" : 1290 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 672 + "blockRuntimeId" : 1287 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 674 + "blockRuntimeId" : 1289 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 661 + "blockRuntimeId" : 1276 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 664 + "blockRuntimeId" : 1279 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 665 + "blockRuntimeId" : 1280 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 673 + "blockRuntimeId" : 1288 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 669 + "blockRuntimeId" : 1284 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 663 + "blockRuntimeId" : 1278 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 671 + "blockRuntimeId" : 1286 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 670 + "blockRuntimeId" : 1285 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 662 + "blockRuntimeId" : 1277 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 666 + "blockRuntimeId" : 1281 }, { "id" : "minecraft:clay", - "blockRuntimeId" : 7124 + "blockRuntimeId" : 10902 }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 641 + "blockRuntimeId" : 872 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6176 + "blockRuntimeId" : 9926 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6184 + "blockRuntimeId" : 9934 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6183 + "blockRuntimeId" : 9933 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6191 + "blockRuntimeId" : 9941 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6188 + "blockRuntimeId" : 9938 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6190 + "blockRuntimeId" : 9940 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6177 + "blockRuntimeId" : 9927 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6180 + "blockRuntimeId" : 9930 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6181 + "blockRuntimeId" : 9931 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6189 + "blockRuntimeId" : 9939 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6185 + "blockRuntimeId" : 9935 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6179 + "blockRuntimeId" : 9929 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6187 + "blockRuntimeId" : 9937 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6186 + "blockRuntimeId" : 9936 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6178 + "blockRuntimeId" : 9928 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6182 + "blockRuntimeId" : 9932 }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 5573 + "blockRuntimeId" : 8154 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 3531 + "blockRuntimeId" : 4540 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 8253 + "blockRuntimeId" : 12431 }, { "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 5834 + "blockRuntimeId" : 8800 }, { "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 3547 + "blockRuntimeId" : 4940 }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 4167 + "blockRuntimeId" : 5944 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 1151 + "blockRuntimeId" : 1774 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 913 + "blockRuntimeId" : 1536 }, { "id" : "minecraft:lime_glazed_terracotta", @@ -1488,39 +1527,39 @@ }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 6610 + "blockRuntimeId" : 10382 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 5358 + "blockRuntimeId" : 7939 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5471 + "blockRuntimeId" : 8052 }, { "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 5465 + "blockRuntimeId" : 8046 }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 7011 + "blockRuntimeId" : 10789 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 965 + "blockRuntimeId" : 1588 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 6539 + "blockRuntimeId" : 10311 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7714 + "blockRuntimeId" : 11892 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7716 + "blockRuntimeId" : 11894 }, { "id" : "minecraft:packed_mud", @@ -1528,31 +1567,31 @@ }, { "id" : "minecraft:mud_bricks", - "blockRuntimeId" : 6889 + "blockRuntimeId" : 10661 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 4293 + "blockRuntimeId" : 6074 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 5905 + "blockRuntimeId" : 9255 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 5061 + "blockRuntimeId" : 7610 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 4189 + "blockRuntimeId" : 5968 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 6349 + "blockRuntimeId" : 10107 }, { "id" : "minecraft:basalt", - "blockRuntimeId" : 4349 + "blockRuntimeId" : 6130 }, { "id" : "minecraft:polished_basalt", @@ -1560,83 +1599,83 @@ }, { "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 1157 + "blockRuntimeId" : 1780 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 5830 + "blockRuntimeId" : 8412 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 5751 + "blockRuntimeId" : 8333 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 5752 + "blockRuntimeId" : 8334 }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 3912 + "blockRuntimeId" : 5689 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 6975 + "blockRuntimeId" : 10753 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 8081 + "blockRuntimeId" : 12259 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 4650 + "blockRuntimeId" : 6815 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 3683 + "blockRuntimeId" : 5076 }, { "id" : "minecraft:mud", - "blockRuntimeId" : 6684 + "blockRuntimeId" : 10456 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 653 + "blockRuntimeId" : 1268 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 4690 + "blockRuntimeId" : 6855 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 912 + "blockRuntimeId" : 1535 }, { "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 4361 + "blockRuntimeId" : 6142 }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 7699 + "blockRuntimeId" : 11877 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 4289 + "blockRuntimeId" : 6068 }, { "id" : "minecraft:coal_ore", - "blockRuntimeId" : 4287 + "blockRuntimeId" : 6066 }, { "id" : "minecraft:copper_ore", - "blockRuntimeId" : 3554 + "blockRuntimeId" : 4947 }, { "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 7347 + "blockRuntimeId" : 11525 }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 4501 + "blockRuntimeId" : 6282 }, { "id" : "minecraft:nether_gold_ore", @@ -1644,35 +1683,35 @@ }, { "id" : "minecraft:ancient_debris", - "blockRuntimeId" : 6107 + "blockRuntimeId" : 9857 }, { "id" : "minecraft:deepslate_iron_ore", - "blockRuntimeId" : 7273 + "blockRuntimeId" : 11451 }, { "id" : "minecraft:deepslate_gold_ore", - "blockRuntimeId" : 6106 + "blockRuntimeId" : 9856 }, { "id" : "minecraft:deepslate_diamond_ore", - "blockRuntimeId" : 8038 + "blockRuntimeId" : 12216 }, { "id" : "minecraft:deepslate_lapis_ore", - "blockRuntimeId" : 7262 + "blockRuntimeId" : 11440 }, { "id" : "minecraft:deepslate_redstone_ore", - "blockRuntimeId" : 6616 + "blockRuntimeId" : 10388 }, { "id" : "minecraft:deepslate_emerald_ore", - "blockRuntimeId" : 6350 + "blockRuntimeId" : 10108 }, { "id" : "minecraft:deepslate_coal_ore", - "blockRuntimeId" : 7196 + "blockRuntimeId" : 11358 }, { "id" : "minecraft:deepslate_copper_ore", @@ -1680,23 +1719,23 @@ }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 8287 + "blockRuntimeId" : 12466 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 654 + "blockRuntimeId" : 1269 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 656 + "blockRuntimeId" : 1271 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 658 + "blockRuntimeId" : 1273 }, { "id" : "minecraft:blackstone", - "blockRuntimeId" : 7585 + "blockRuntimeId" : 11763 }, { "id" : "minecraft:deepslate", @@ -1704,79 +1743,79 @@ }, { "id" : "minecraft:stone", - "blockRuntimeId" : 655 + "blockRuntimeId" : 1270 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 657 + "blockRuntimeId" : 1272 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 659 + "blockRuntimeId" : 1274 }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 3682 + "blockRuntimeId" : 5075 }, { "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 7754 + "blockRuntimeId" : 11932 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 4195 + "blockRuntimeId" : 5974 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 4196 + "blockRuntimeId" : 5975 }, { "id" : "minecraft:cactus", - "blockRuntimeId" : 6986 + "blockRuntimeId" : 10764 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6672 + "blockRuntimeId" : 10444 }, { "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7543 + "blockRuntimeId" : 11721 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6673 + "blockRuntimeId" : 10445 }, { "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6288 + "blockRuntimeId" : 10046 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6674 + "blockRuntimeId" : 10446 }, { "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 5972 + "blockRuntimeId" : 9322 }, { "id" : "minecraft:log", - "blockRuntimeId" : 6675 + "blockRuntimeId" : 10447 }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 642 + "blockRuntimeId" : 1257 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 3830 + "blockRuntimeId" : 5607 }, { "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 5848 + "blockRuntimeId" : 8814 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 3831 + "blockRuntimeId" : 5608 }, { "id" : "minecraft:stripped_dark_oak_log", @@ -1784,167 +1823,167 @@ }, { "id" : "minecraft:mangrove_log", - "blockRuntimeId" : 348 + "blockRuntimeId" : 578 }, { "id" : "minecraft:stripped_mangrove_log", - "blockRuntimeId" : 8284 + "blockRuntimeId" : 12463 }, { "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 5897 + "blockRuntimeId" : 9247 }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 6948 + "blockRuntimeId" : 10726 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 6486 + "blockRuntimeId" : 10256 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7400 + "blockRuntimeId" : 11578 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3474 + "blockRuntimeId" : 4099 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3480 + "blockRuntimeId" : 4105 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3475 + "blockRuntimeId" : 4100 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3481 + "blockRuntimeId" : 4106 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3476 + "blockRuntimeId" : 4101 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3482 + "blockRuntimeId" : 4107 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3477 + "blockRuntimeId" : 4102 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3483 + "blockRuntimeId" : 4108 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3478 + "blockRuntimeId" : 4103 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3484 + "blockRuntimeId" : 4109 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3479 + "blockRuntimeId" : 4104 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 3485 + "blockRuntimeId" : 4110 }, { "id" : "minecraft:mangrove_wood", - "blockRuntimeId" : 4161 + "blockRuntimeId" : 5938 }, { "id" : "minecraft:stripped_mangrove_wood", - "blockRuntimeId" : 4229 + "blockRuntimeId" : 6008 }, { "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 4294 + "blockRuntimeId" : 6075 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 6499 + "blockRuntimeId" : 10269 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 5902 + "blockRuntimeId" : 9252 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 5579 + "blockRuntimeId" : 8160 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6090 + "blockRuntimeId" : 9840 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6091 + "blockRuntimeId" : 9841 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6092 + "blockRuntimeId" : 9842 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 6093 + "blockRuntimeId" : 9843 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4353 + "blockRuntimeId" : 6134 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4354 + "blockRuntimeId" : 6135 }, { "id" : "minecraft:mangrove_leaves", - "blockRuntimeId" : 6666 + "blockRuntimeId" : 10438 }, { "id" : "minecraft:azalea_leaves", - "blockRuntimeId" : 7710 + "blockRuntimeId" : 11888 }, { "id" : "minecraft:azalea_leaves_flowered", - "blockRuntimeId" : 6339 + "blockRuntimeId" : 10097 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 712 + "blockRuntimeId" : 1327 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 713 + "blockRuntimeId" : 1328 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 714 + "blockRuntimeId" : 1329 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 715 + "blockRuntimeId" : 1330 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 716 + "blockRuntimeId" : 1331 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 717 + "blockRuntimeId" : 1332 }, { "id" : "minecraft:mangrove_propagule", - "blockRuntimeId" : 6976 + "blockRuntimeId" : 10754 }, { "id" : "minecraft:bee_nest", - "blockRuntimeId" : 5754 + "blockRuntimeId" : 8336 }, { "id" : "minecraft:wheat_seeds" @@ -1987,7 +2026,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 392 + "blockRuntimeId" : 622 }, { "id" : "minecraft:melon_slice" @@ -2003,97 +2042,97 @@ }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 4577 + "blockRuntimeId" : 6358 }, { "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 7378 + "blockRuntimeId" : 11556 }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 6685 + "blockRuntimeId" : 10457 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 929 + "blockRuntimeId" : 1552 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5455 + "blockRuntimeId" : 8036 }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 928 + "blockRuntimeId" : 1551 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5454 + "blockRuntimeId" : 8035 }, { "id" : "minecraft:nether_sprouts" }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6492 + "blockRuntimeId" : 10262 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6490 + "blockRuntimeId" : 10260 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6491 + "blockRuntimeId" : 10261 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6489 + "blockRuntimeId" : 10259 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6493 + "blockRuntimeId" : 10263 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6497 + "blockRuntimeId" : 10267 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6495 + "blockRuntimeId" : 10265 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6496 + "blockRuntimeId" : 10266 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6494 + "blockRuntimeId" : 10264 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 6498 + "blockRuntimeId" : 10268 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4616 + "blockRuntimeId" : 6397 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4614 + "blockRuntimeId" : 6395 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4615 + "blockRuntimeId" : 6396 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4613 + "blockRuntimeId" : 6394 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4617 + "blockRuntimeId" : 6398 }, { "id" : "minecraft:coral_fan_dead", @@ -2115,88 +2154,81 @@ "id" : "minecraft:coral_fan_dead", "blockRuntimeId" : 70 }, - { - "id" : "minecraft:kelp" - }, - { - "id" : "minecraft:seagrass", - "blockRuntimeId" : 246 - }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 7573 + "blockRuntimeId" : 11751 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 4362 + "blockRuntimeId" : 6143 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 302 + "blockRuntimeId" : 530 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3616 + "blockRuntimeId" : 5009 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3617 + "blockRuntimeId" : 5010 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3618 + "blockRuntimeId" : 5011 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3619 + "blockRuntimeId" : 5012 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3620 + "blockRuntimeId" : 5013 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3621 + "blockRuntimeId" : 5014 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3622 + "blockRuntimeId" : 5015 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3623 + "blockRuntimeId" : 5016 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3624 + "blockRuntimeId" : 5017 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3625 + "blockRuntimeId" : 5018 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 3626 + "blockRuntimeId" : 5019 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5452 + "blockRuntimeId" : 8033 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5453 + "blockRuntimeId" : 8034 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5456 + "blockRuntimeId" : 8037 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 5457 + "blockRuntimeId" : 8038 }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 6165 + "blockRuntimeId" : 9915 }, { "id" : "minecraft:white_dye" @@ -2263,35 +2295,42 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 894 + "blockRuntimeId" : 1517 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 5479 + "blockRuntimeId" : 8060 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 5691 + "blockRuntimeId" : 8273 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 1158 + "blockRuntimeId" : 1781 + }, + { + "id" : "minecraft:seagrass", + "blockRuntimeId" : 246 + }, + { + "id" : "minecraft:kelp" }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 4677 + "blockRuntimeId" : 6842 }, { "id" : "minecraft:bamboo", - "blockRuntimeId" : 3684 + "blockRuntimeId" : 5077 }, { "id" : "minecraft:snow", - "blockRuntimeId" : 4194 + "blockRuntimeId" : 5973 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 6689 + "blockRuntimeId" : 10461 }, { "id" : "minecraft:packed_ice", @@ -2299,7 +2338,7 @@ }, { "id" : "minecraft:blue_ice", - "blockRuntimeId" : 7027 + "blockRuntimeId" : 10805 }, { "id" : "minecraft:snow_layer", @@ -2307,11 +2346,11 @@ }, { "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 7416 + "blockRuntimeId" : 11594 }, { "id" : "minecraft:dripstone_block", - "blockRuntimeId" : 893 + "blockRuntimeId" : 1516 }, { "id" : "minecraft:moss_carpet", @@ -2319,11 +2358,11 @@ }, { "id" : "minecraft:moss_block", - "blockRuntimeId" : 6538 + "blockRuntimeId" : 10310 }, { "id" : "minecraft:dirt_with_roots", - "blockRuntimeId" : 5397 + "blockRuntimeId" : 7978 }, { "id" : "minecraft:hanging_roots", @@ -2331,63 +2370,63 @@ }, { "id" : "minecraft:mangrove_roots", - "blockRuntimeId" : 6175 + "blockRuntimeId" : 9925 }, { "id" : "minecraft:muddy_mangrove_roots", - "blockRuntimeId" : 345 + "blockRuntimeId" : 573 }, { "id" : "minecraft:big_dripleaf", - "blockRuntimeId" : 5980 + "blockRuntimeId" : 9330 }, { "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 4320 + "blockRuntimeId" : 6101 }, { "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 7312 + "blockRuntimeId" : 11490 }, { "id" : "minecraft:azalea", - "blockRuntimeId" : 6888 + "blockRuntimeId" : 10660 }, { "id" : "minecraft:flowering_azalea", - "blockRuntimeId" : 5477 + "blockRuntimeId" : 8058 }, { "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 8266 }, { "id" : "minecraft:amethyst_block", - "blockRuntimeId" : 290 + "blockRuntimeId" : 322 }, { "id" : "minecraft:budding_amethyst", - "blockRuntimeId" : 7002 + "blockRuntimeId" : 10780 }, { "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 7810 + "blockRuntimeId" : 11988 }, { "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 4728 + "blockRuntimeId" : 6893 }, { "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 4376 + "blockRuntimeId" : 6157 }, { "id" : "minecraft:small_amethyst_bud", - "blockRuntimeId" : 304 + "blockRuntimeId" : 532 }, { "id" : "minecraft:tuff", - "blockRuntimeId" : 347 + "blockRuntimeId" : 577 }, { "id" : "minecraft:calcite", @@ -2422,15 +2461,15 @@ }, { "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 3546 + "blockRuntimeId" : 4939 }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 4585 + "blockRuntimeId" : 6366 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 7753 + "blockRuntimeId" : 11931 }, { "id" : "minecraft:warped_fungus", @@ -2438,19 +2477,19 @@ }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7362 + "blockRuntimeId" : 11540 }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 3611 + "blockRuntimeId" : 5004 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7363 + "blockRuntimeId" : 11541 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7348 + "blockRuntimeId" : 11526 }, { "id" : "minecraft:egg" @@ -2469,66 +2508,66 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 6713 + "blockRuntimeId" : 10485 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 401 + "blockRuntimeId" : 631 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4144 + "blockRuntimeId" : 5921 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4145 + "blockRuntimeId" : 5922 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4146 + "blockRuntimeId" : 5923 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4147 + "blockRuntimeId" : 5924 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4148 + "blockRuntimeId" : 5925 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4149 + "blockRuntimeId" : 5926 }, { "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 4641 + "blockRuntimeId" : 6806 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 7271 + "blockRuntimeId" : 11449 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7997 + "blockRuntimeId" : 12175 }, { "id" : "minecraft:frog_spawn", - "blockRuntimeId" : 4399 + "blockRuntimeId" : 6180 }, { "id" : "minecraft:pearlescent_froglight", - "blockRuntimeId" : 6435 + "blockRuntimeId" : 10193 }, { "id" : "minecraft:verdant_froglight", - "blockRuntimeId" : 6481 + "blockRuntimeId" : 10251 }, { "id" : "minecraft:ochre_froglight", - "blockRuntimeId" : 3510 + "blockRuntimeId" : 4519 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2704,6 +2743,12 @@ { "id" : "minecraft:tadpole_spawn_egg" }, + { + "id" : "minecraft:trader_llama_spawn_egg" + }, + { + "id" : "minecraft:camel_spawn_egg" + }, { "id" : "minecraft:ghast_spawn_egg" }, @@ -2742,42 +2787,42 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 428 + "blockRuntimeId" : 658 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 6722 + "blockRuntimeId" : 10494 }, { "id" : "minecraft:bedrock", - "blockRuntimeId" : 7017 + "blockRuntimeId" : 10795 }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 5831 + "blockRuntimeId" : 8413 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 7037 + "blockRuntimeId" : 10815 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 8009 + "blockRuntimeId" : 12187 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 3836 + "blockRuntimeId" : 5613 }, { "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 4530 + "blockRuntimeId" : 6311 }, { "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 5505 + "blockRuntimeId" : 8086 }, { "id" : "minecraft:chorus_fruit" @@ -2787,63 +2832,63 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 629 + "blockRuntimeId" : 859 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 630 + "blockRuntimeId" : 860 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5237 + "blockRuntimeId" : 7802 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5238 + "blockRuntimeId" : 7803 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5239 + "blockRuntimeId" : 7804 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5240 + "blockRuntimeId" : 7805 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5241 + "blockRuntimeId" : 7806 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5242 + "blockRuntimeId" : 7807 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5243 + "blockRuntimeId" : 7808 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5244 + "blockRuntimeId" : 7809 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5245 + "blockRuntimeId" : 7810 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 5246 + "blockRuntimeId" : 7811 }, { "id" : "minecraft:sculk", - "blockRuntimeId" : 7036 + "blockRuntimeId" : 10814 }, { "id" : "minecraft:sculk_vein", - "blockRuntimeId" : 7132 + "blockRuntimeId" : 11294 }, { "id" : "minecraft:sculk_catalyst", - "blockRuntimeId" : 3613 + "blockRuntimeId" : 5006 }, { "id" : "minecraft:sculk_shrieker", @@ -2851,11 +2896,11 @@ }, { "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 4389 + "blockRuntimeId" : 6170 }, { "id" : "minecraft:reinforced_deepslate", - "blockRuntimeId" : 5832 + "blockRuntimeId" : 8798 }, { "id" : "minecraft:leather_helmet" @@ -3916,86 +3961,86 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 724 + "blockRuntimeId" : 1347 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 4644 + "blockRuntimeId" : 6809 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 5855 + "blockRuntimeId" : 8821 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 7074 + "blockRuntimeId" : 10852 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 5749 + "blockRuntimeId" : 8331 }, { "id" : "minecraft:candle", - "blockRuntimeId" : 7403 + "blockRuntimeId" : 11581 }, { "id" : "minecraft:white_candle", - "blockRuntimeId" : 5300 + "blockRuntimeId" : 7881 }, { "id" : "minecraft:orange_candle", - "blockRuntimeId" : 362 + "blockRuntimeId" : 592 }, { "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 418 + "blockRuntimeId" : 648 }, { "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 4569 + "blockRuntimeId" : 6350 }, { "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 6192 + "blockRuntimeId" : 9942 }, { "id" : "minecraft:lime_candle", - "blockRuntimeId" : 6368 + "blockRuntimeId" : 10126 }, { "id" : "minecraft:pink_candle", - "blockRuntimeId" : 7370 + "blockRuntimeId" : 11548 }, { "id" : "minecraft:gray_candle", - "blockRuntimeId" : 939 + "blockRuntimeId" : 1562 }, { "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 6224 + "blockRuntimeId" : 9982 }, { "id" : "minecraft:cyan_candle", - "blockRuntimeId" : 7726 + "blockRuntimeId" : 11904 }, { "id" : "minecraft:purple_candle", - "blockRuntimeId" : 7038 + "blockRuntimeId" : 10816 }, { "id" : "minecraft:blue_candle" }, { "id" : "minecraft:brown_candle", - "blockRuntimeId" : 5875 + "blockRuntimeId" : 9225 }, { "id" : "minecraft:green_candle", - "blockRuntimeId" : 686 + "blockRuntimeId" : 1301 }, { "id" : "minecraft:red_candle", - "blockRuntimeId" : 4681 + "blockRuntimeId" : 6846 }, { "id" : "minecraft:black_candle", @@ -4003,23 +4048,23 @@ }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 5854 + "blockRuntimeId" : 8820 }, { "id" : "minecraft:cartography_table", - "blockRuntimeId" : 8288 + "blockRuntimeId" : 12467 }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 5833 + "blockRuntimeId" : 8799 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 3726 + "blockRuntimeId" : 5119 }, { "id" : "minecraft:beehive", - "blockRuntimeId" : 6108 + "blockRuntimeId" : 9858 }, { "id" : "minecraft:campfire" @@ -4029,152 +4074,156 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 7802 + "blockRuntimeId" : 11980 }, { "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 7567 + "blockRuntimeId" : 11745 }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 647 + "blockRuntimeId" : 1262 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 681 + "blockRuntimeId" : 1296 }, { "id" : "minecraft:brewing_stand" }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6634 + "blockRuntimeId" : 10406 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6638 + "blockRuntimeId" : 10410 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 6642 + "blockRuntimeId" : 10414 }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 8039 + "blockRuntimeId" : 12217 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 6723 + "blockRuntimeId" : 10495 }, { "id" : "minecraft:bookshelf", - "blockRuntimeId" : 6671 + "blockRuntimeId" : 10443 + }, + { + "id" : "minecraft:chiseled_bookshelf", + "blockRuntimeId" : 326 }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 6940 + "blockRuntimeId" : 10718 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 5415 + "blockRuntimeId" : 7996 }, { "id" : "minecraft:chest", - "blockRuntimeId" : 7115 + "blockRuntimeId" : 10893 }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 5583 + "blockRuntimeId" : 8164 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4369 + "blockRuntimeId" : 6150 }, { "id" : "minecraft:barrel", - "blockRuntimeId" : 4518 + "blockRuntimeId" : 6299 }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 3681 + "blockRuntimeId" : 5074 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5316 + "blockRuntimeId" : 7897 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5324 + "blockRuntimeId" : 7905 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5323 + "blockRuntimeId" : 7904 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5331 + "blockRuntimeId" : 7912 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5328 + "blockRuntimeId" : 7909 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5330 + "blockRuntimeId" : 7911 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5317 + "blockRuntimeId" : 7898 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5320 + "blockRuntimeId" : 7901 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5321 + "blockRuntimeId" : 7902 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5329 + "blockRuntimeId" : 7910 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5325 + "blockRuntimeId" : 7906 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5319 + "blockRuntimeId" : 7900 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5327 + "blockRuntimeId" : 7908 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5326 + "blockRuntimeId" : 7907 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5318 + "blockRuntimeId" : 7899 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5322 + "blockRuntimeId" : 7903 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 346 + "blockRuntimeId" : 576 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 4874 + "blockRuntimeId" : 7423 }, { "id" : "minecraft:music_disc_13" @@ -4229,7 +4278,7 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 3885 + "blockRuntimeId" : 5662 }, { "id" : "minecraft:redstone_lamp", @@ -4237,7 +4286,7 @@ }, { "id" : "minecraft:sea_lantern", - "blockRuntimeId" : 7546 + "blockRuntimeId" : 11724 }, { "id" : "minecraft:oak_sign" @@ -4260,12 +4309,45 @@ { "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" }, @@ -4346,19 +4428,19 @@ }, { "id" : "minecraft:bell", - "blockRuntimeId" : 6908 + "blockRuntimeId" : 10686 }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 4232 + "blockRuntimeId" : 6011 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7574 + "blockRuntimeId" : 11752 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 6077 + "blockRuntimeId" : 9811 }, { "id" : "minecraft:coal" @@ -4497,11 +4579,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 5891 + "blockRuntimeId" : 9241 }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 1176 + "blockRuntimeId" : 1799 }, { "id" : "minecraft:end_crystal" @@ -4976,6 +5058,9 @@ { "id" : "minecraft:mangrove_boat" }, + { + "id" : "minecraft:bamboo_raft" + }, { "id" : "minecraft:oak_chest_boat" }, @@ -4997,21 +5082,24 @@ { "id" : "minecraft:mangrove_chest_boat" }, + { + "id" : "minecraft:bamboo_chest_raft" + }, { "id" : "minecraft:rail", - "blockRuntimeId" : 3920 + "blockRuntimeId" : 5697 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 5332 + "blockRuntimeId" : 7913 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 4132 + "blockRuntimeId" : 5909 }, { "id" : "minecraft:activator_rail", - "blockRuntimeId" : 309 + "blockRuntimeId" : 537 }, { "id" : "minecraft:minecart" @@ -5030,27 +5118,27 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 3776 + "blockRuntimeId" : 5169 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 3525 + "blockRuntimeId" : 4534 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 6514 + "blockRuntimeId" : 10284 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 6391 + "blockRuntimeId" : 10149 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 4321 + "blockRuntimeId" : 6102 }, { "id" : "minecraft:birch_button", - "blockRuntimeId" : 7766 + "blockRuntimeId" : 11944 }, { "id" : "minecraft:jungle_button", @@ -5058,7 +5146,7 @@ }, { "id" : "minecraft:acacia_button", - "blockRuntimeId" : 7231 + "blockRuntimeId" : 11409 }, { "id" : "minecraft:dark_oak_button", @@ -5066,59 +5154,67 @@ }, { "id" : "minecraft:mangrove_button", - "blockRuntimeId" : 7062 + "blockRuntimeId" : 10840 + }, + { + "id" : "minecraft:bamboo_button", + "blockRuntimeId" : 10238 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 596 + "blockRuntimeId" : 826 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 4432 + "blockRuntimeId" : 6213 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 7250 + "blockRuntimeId" : 11428 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 7790 + "blockRuntimeId" : 11968 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 5914 + "blockRuntimeId" : 9264 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 8063 + "blockRuntimeId" : 12241 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 3759 + "blockRuntimeId" : 5152 }, { "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 3555 + "blockRuntimeId" : 4948 }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 3635 + "blockRuntimeId" : 5028 }, { "id" : "minecraft:acacia_pressure_plate", - "blockRuntimeId" : 5247 + "blockRuntimeId" : 7812 }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 5956 + "blockRuntimeId" : 9306 }, { "id" : "minecraft:mangrove_pressure_plate", - "blockRuntimeId" : 3869 + "blockRuntimeId" : 5646 + }, + { + "id" : "minecraft:bamboo_pressure_plate", + "blockRuntimeId" : 9819 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 8268 + "blockRuntimeId" : 12447 }, { "id" : "minecraft:warped_pressure_plate", @@ -5126,27 +5222,27 @@ }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 3886 + "blockRuntimeId" : 5663 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 3665 + "blockRuntimeId" : 5058 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 1160 + "blockRuntimeId" : 1783 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6232 + "blockRuntimeId" : 9990 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 3513 + "blockRuntimeId" : 4522 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 4197 + "blockRuntimeId" : 5976 }, { "id" : "minecraft:repeater" @@ -5159,30 +5255,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 7385 + "blockRuntimeId" : 11563 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 8013 + "blockRuntimeId" : 12191 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 922 + "blockRuntimeId" : 1545 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 4364 + "blockRuntimeId" : 6145 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 6707 + "blockRuntimeId" : 10479 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 3826 + "blockRuntimeId" : 5603 }, { "id" : "minecraft:banner" @@ -5428,7 +5524,7 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 6390 + "blockRuntimeId" : 10148 }, { "id" : "minecraft:lodestone_compass" diff --git a/core/src/main/resources/bedrock/entity_identifiers.dat b/core/src/main/resources/bedrock/entity_identifiers.dat index b3e6fdb7710..8f0c9ce8ef8 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_10.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json similarity index 100% rename from core/src/main/resources/bedrock/runtime_item_states.1_19_10.json rename to core/src/main/resources/bedrock/runtime_item_states.1_19_20.json 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_50.json similarity index 97% 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_50.json index b1ffe6353b3..d8a12c79486 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_50.json @@ -9,7 +9,7 @@ }, { "name" : "minecraft:acacia_chest_boat", - "id" : 643 + "id" : 645 }, { "name" : "minecraft:acacia_door", @@ -19,6 +19,10 @@ "name" : "minecraft:acacia_fence_gate", "id" : 187 }, + { + "name" : "minecraft:acacia_hanging_sign", + "id" : -504 + }, { "name" : "minecraft:acacia_pressure_plate", "id" : -150 @@ -131,17 +135,97 @@ "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" : 651 + "id" : 655 }, { "name" : "minecraft:barrel", @@ -217,7 +301,7 @@ }, { "name" : "minecraft:birch_chest_boat", - "id" : 640 + "id" : 642 }, { "name" : "minecraft:birch_door", @@ -227,6 +311,10 @@ "name" : "minecraft:birch_fence_gate", "id" : 184 }, + { + "name" : "minecraft:birch_hanging_sign", + "id" : -502 + }, { "name" : "minecraft:birch_pressure_plate", "id" : -151 @@ -329,7 +417,7 @@ }, { "name" : "minecraft:boat", - "id" : 649 + "id" : 653 }, { "name" : "minecraft:bone", @@ -435,6 +523,10 @@ "name" : "minecraft:calcite", "id" : -326 }, + { + "name" : "minecraft:camel_spawn_egg", + "id" : 633 + }, { "name" : "minecraft:camera", "id" : 593 @@ -541,7 +633,7 @@ }, { "name" : "minecraft:chest_boat", - "id" : 646 + "id" : 649 }, { "name" : "minecraft:chest_minecart", @@ -555,6 +647,10 @@ "name" : "minecraft:chicken_spawn_egg", "id" : 435 }, + { + "name" : "minecraft:chiseled_bookshelf", + "id" : -526 + }, { "name" : "minecraft:chiseled_deepslate", "id" : -395 @@ -827,6 +923,10 @@ "name" : "minecraft:crimson_fungus", "id" : -228 }, + { + "name" : "minecraft:crimson_hanging_sign", + "id" : -506 + }, { "name" : "minecraft:crimson_hyphae", "id" : -299 @@ -921,7 +1021,7 @@ }, { "name" : "minecraft:dark_oak_chest_boat", - "id" : 644 + "id" : 646 }, { "name" : "minecraft:dark_oak_door", @@ -931,6 +1031,10 @@ "name" : "minecraft:dark_oak_fence_gate", "id" : 186 }, + { + "name" : "minecraft:dark_oak_hanging_sign", + "id" : -505 + }, { "name" : "minecraft:dark_oak_pressure_plate", "id" : -152 @@ -1121,7 +1225,7 @@ }, { "name" : "minecraft:disc_fragment_5", - "id" : 638 + "id" : 640 }, { "name" : "minecraft:dispenser", @@ -1193,11 +1297,11 @@ }, { "name" : "minecraft:dye", - "id" : 650 + "id" : 654 }, { "name" : "minecraft:echo_shard", - "id" : 648 + "id" : 651 }, { "name" : "minecraft:egg", @@ -1725,7 +1829,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 653 + "id" : 657 }, { "name" : "minecraft:end_gateway", @@ -1831,10 +1935,6 @@ "name" : "minecraft:fire_charge", "id" : 509 }, - { - "name" : "minecraft:firefly_spawn_egg", - "id" : 633 - }, { "name" : "minecraft:firework_rocket", "id" : 519 @@ -1937,7 +2037,7 @@ }, { "name" : "minecraft:glow_berries", - "id" : 654 + "id" : 658 }, { "name" : "minecraft:glow_frame", @@ -2409,7 +2509,7 @@ }, { "name" : "minecraft:jungle_chest_boat", - "id" : 641 + "id" : 643 }, { "name" : "minecraft:jungle_door", @@ -2419,6 +2519,10 @@ "name" : "minecraft:jungle_fence_gate", "id" : 185 }, + { + "name" : "minecraft:jungle_hanging_sign", + "id" : -503 + }, { "name" : "minecraft:jungle_pressure_plate", "id" : -153 @@ -2677,7 +2781,7 @@ }, { "name" : "minecraft:mangrove_chest_boat", - "id" : 645 + "id" : 647 }, { "name" : "minecraft:mangrove_door", @@ -2695,6 +2799,10 @@ "name" : "minecraft:mangrove_fence_gate", "id" : -492 }, + { + "name" : "minecraft:mangrove_hanging_sign", + "id" : -508 + }, { "name" : "minecraft:mangrove_leaves", "id" : -472 @@ -2865,7 +2973,7 @@ }, { "name" : "minecraft:music_disc_5", - "id" : 637 + "id" : 639 }, { "name" : "minecraft:music_disc_blocks", @@ -3041,7 +3149,11 @@ }, { "name" : "minecraft:oak_chest_boat", - "id" : 639 + "id" : 641 + }, + { + "name" : "minecraft:oak_hanging_sign", + "id" : -500 }, { "name" : "minecraft:oak_sign", @@ -3477,7 +3589,7 @@ }, { "name" : "minecraft:recovery_compass", - "id" : 647 + "id" : 650 }, { "name" : "minecraft:red_candle", @@ -3785,7 +3897,7 @@ }, { "name" : "minecraft:spawn_egg", - "id" : 652 + "id" : 656 }, { "name" : "minecraft:spider_eye", @@ -3817,7 +3929,7 @@ }, { "name" : "minecraft:spruce_chest_boat", - "id" : 642 + "id" : 644 }, { "name" : "minecraft:spruce_door", @@ -3827,6 +3939,10 @@ "name" : "minecraft:spruce_fence_gate", "id" : 183 }, + { + "name" : "minecraft:spruce_hanging_sign", + "id" : -501 + }, { "name" : "minecraft:spruce_pressure_plate", "id" : -154 @@ -4083,6 +4199,10 @@ "name" : "minecraft:totem_of_undying", "id" : 568 }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 652 + }, { "name" : "minecraft:trapdoor", "id" : 96 @@ -4223,6 +4343,10 @@ "name" : "minecraft:warped_fungus_on_a_stick", "id" : 618 }, + { + "name" : "minecraft:warped_hanging_sign", + "id" : -507 + }, { "name" : "minecraft:warped_hyphae", "id" : -298 diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index c331a7e62ab..502441560bd 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -175,6 +175,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 00000000000..f14e55623f3 --- /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 4351fc11d5f..a9cf5999af6 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 4351fc11d5fbd9fecc8334910234cdf8a4bc730b +Subproject commit a9cf5999af605902b18dd5c77d3562481f8d7f3d diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 01278912327..677c5b0872d 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 0127891232742209b8470298dfd997249c506320 +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 6a280ea572b..e83c6f73dc8 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 void convertMessageLenient() { @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 00000000000..145f4636910 --- /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 new file mode 100644 index 00000000000..4fb72e2fc2c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# 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 + +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.vfs.watch=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000000..9b595671e46 --- /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.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..41d9927a4d4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..8049c684f04 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000000..1b6c787337f --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000000..107acd32c4e --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 1d99d93e093..00000000000 --- a/pom.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - 4.0.0 - org.geysermc - geyser-parent - 2.0.6-SNAPSHOT - pom - Geyser - Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. - https://geysermc.org - - - Geyser - UTF-8 - UTF-8 - 16 - 16 - geysermc - https://sonarcloud.io - - - - GeyserMC - https://github.com/GeyserMC/Geyser/blob/master/pom.xml - - - - scm:git:https://github.com/GeyserMC/Geyser.git - scm:git:git@github.com:GeyserMC/Geyser.git - https://github.com/GeyserMC/Geyser - - - - ap - api - bootstrap - common - core - - - - - jitpack.io - https://jitpack.io - - - opencollab-release-repo - https://repo.opencollab.dev/maven-releases/ - - true - - - false - - - - opencollab-snapshot-repo - https://repo.opencollab.dev/maven-snapshots/ - - false - - - true - - - - sonatype - https://oss.sonatype.org/content/repositories/snapshots/ - - - - - - org.projectlombok - lombok - 1.18.20 - provided - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000000..6f212d3acc7 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,83 @@ +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +dependencyResolutionManagement { +// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + // Floodgate, Cumulus etc. + maven("https://repo.opencollab.dev/main") + + // Paper, Velocity + maven("https://repo.papermc.io/repository/maven-public") + // Spigot + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { + mavenContent { snapshotsOnly() } + } + + // BungeeCord + maven("https://oss.sonatype.org/content/repositories/snapshots") { + mavenContent { snapshotsOnly() } + } + + // Minecraft + maven("https://libraries.minecraft.net") { + name = "minecraft" + mavenContent { releasesOnly() } + } + + mavenLocal() + mavenCentral() + + // ViaVersion + maven("https://repo.viaversion.com") { + name = "viaversion" + } + + // Sponge + maven("https://repo.spongepowered.org/repository/maven-public/") + + 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" + id("net.kyori.indra") + id("net.kyori.indra.git") + } + includeBuild("build-logic") +} + +rootProject.name = "geyser-parent" + +include(":ap") +include(":api") +include(":geyser-api") +include(":bungeecord") +include(":fabric") +include(":spigot") +include(":sponge") +include(":standalone") +include(":velocity") +include(":common") +include(":core") + +// Specify project dirs +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") +project(":velocity").projectDir = file("bootstrap/velocity") \ No newline at end of file