From 754c2f3d783ad74082cfd7942492d7e33d7b170f Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 12:15:07 -0400 Subject: [PATCH 001/358] First commit. --- bootstrap/fabric/.idea/copyright/Geyser.xml | 6 + .../.idea/copyright/profiles_settings.xml | 7 + bootstrap/fabric/LICENSE | 21 ++ bootstrap/fabric/build.gradle | 97 +++++++++ bootstrap/fabric/gradle.properties | 14 ++ .../gradle/wrapper/gradle-wrapper.properties | 5 + bootstrap/fabric/gradlew | 185 ++++++++++++++++++ bootstrap/fabric/gradlew.bat | 104 ++++++++++ bootstrap/fabric/settings.gradle | 10 + .../fabric/GeyserFabricConfiguration.java | 37 ++++ .../platform/fabric/GeyserFabricDumpInfo.java | 31 +++ .../platform/fabric/GeyserFabricLogger.java | 84 ++++++++ .../platform/fabric/GeyserFabricMod.java | 138 +++++++++++++ .../command/GeyserFabricCommandManager.java | 41 ++++ .../src/main/resources/fabric.mixins.json | 13 ++ .../fabric/src/main/resources/fabric.mod.json | 30 +++ 16 files changed, 823 insertions(+) create mode 100644 bootstrap/fabric/.idea/copyright/Geyser.xml create mode 100644 bootstrap/fabric/.idea/copyright/profiles_settings.xml create mode 100644 bootstrap/fabric/LICENSE create mode 100644 bootstrap/fabric/build.gradle create mode 100644 bootstrap/fabric/gradle.properties create mode 100644 bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties create mode 100755 bootstrap/fabric/gradlew create mode 100644 bootstrap/fabric/gradlew.bat create mode 100644 bootstrap/fabric/settings.gradle create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java create mode 100644 bootstrap/fabric/src/main/resources/fabric.mixins.json create mode 100644 bootstrap/fabric/src/main/resources/fabric.mod.json diff --git a/bootstrap/fabric/.idea/copyright/Geyser.xml b/bootstrap/fabric/.idea/copyright/Geyser.xml new file mode 100644 index 00000000000..bdffbbbd662 --- /dev/null +++ b/bootstrap/fabric/.idea/copyright/Geyser.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/bootstrap/fabric/.idea/copyright/profiles_settings.xml b/bootstrap/fabric/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000000..f2d5911c9eb --- /dev/null +++ b/bootstrap/fabric/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/bootstrap/fabric/LICENSE b/bootstrap/fabric/LICENSE new file mode 100644 index 00000000000..d8ee99620db --- /dev/null +++ b/bootstrap/fabric/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 GeyserMC + +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. diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle new file mode 100644 index 00000000000..8377dab43c0 --- /dev/null +++ b/bootstrap/fabric/build.gradle @@ -0,0 +1,97 @@ +plugins { + id 'fabric-loom' version '0.5-SNAPSHOT' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +archivesBaseName = project.archives_base_name +version = project.mod_version +group = project.maven_group + +dependencies { + //to change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. + + implementation 'org.geysermc:connector:1.1.0' +} + +repositories { + maven { + url = uri('https://repo.nukkitx.com/maven-releases/') + } + + maven { + url = uri('https://repo.nukkitx.com/maven-snapshots/') + } + + maven { + url = uri('https://jitpack.io') + } + + maven { + url = uri('https://oss.sonatype.org/content/repositories/snapshots/') + } +} + +processResources { + inputs.property "version", project.version + + from(sourceSets.main.resources.srcDirs) { + include "fabric.mod.json" + expand "version": project.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude "fabric.mod.json" + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +// 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. +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource +} + +jar { + from "LICENSE" +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + // add all the jars that should be included when publishing to maven + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } + } + } + + // select the repositories you want to publish to + repositories { + // uncomment to publish to the local maven + mavenLocal() + } +} diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties new file mode 100644 index 00000000000..fbcb2816cee --- /dev/null +++ b/bootstrap/fabric/gradle.properties @@ -0,0 +1,14 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.16.3 +yarn_mappings=1.16.3+build.28 +loader_version=0.10.1+build.209 +# Mod Properties +mod_version=1.0-SNAPSHOT +maven_group=org.geysermc.platform +archives_base_name=fabric +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.23.0+build.410-1.16 diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..bb8b2fc26b2 --- /dev/null +++ b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/bootstrap/fabric/gradlew b/bootstrap/fabric/gradlew new file mode 100755 index 00000000000..fbd7c515832 --- /dev/null +++ b/bootstrap/fabric/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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 "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/bootstrap/fabric/gradlew.bat b/bootstrap/fabric/gradlew.bat new file mode 100644 index 00000000000..a9f778a7a96 --- /dev/null +++ b/bootstrap/fabric/gradlew.bat @@ -0,0 +1,104 @@ +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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/bootstrap/fabric/settings.gradle b/bootstrap/fabric/settings.gradle new file mode 100644 index 00000000000..5b60df3d25f --- /dev/null +++ b/bootstrap/fabric/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java new file mode 100644 index 00000000000..9f843693a53 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 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.platform.fabric; + +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; + +import java.nio.file.Path; + +public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { + @Override + public Path getFloodgateKeyPath() { + return null; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java new file mode 100644 index 00000000000..cd35e77f23f --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.platform.fabric; + +import org.geysermc.connector.dump.BootstrapDumpInfo; + +public class GeyserFabricDumpInfo extends BootstrapDumpInfo { +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java new file mode 100644 index 00000000000..c3f151c4cb7 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 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.platform.fabric; + +import org.geysermc.connector.GeyserLogger; + +public class GeyserFabricLogger implements GeyserLogger { + + private boolean debug; + + public GeyserFabricLogger(boolean isDebug) { + debug = isDebug; + } + + @Override + public void severe(String message) { + System.out.println(message); + } + + @Override + public void severe(String message, Throwable error) { + System.out.println(message); + } + + @Override + public void error(String message) { + System.out.println(message); + } + + @Override + public void error(String message, Throwable error) { + System.out.println(message); + } + + @Override + public void warning(String message) { + System.out.println(message); + } + + @Override + public void info(String message) { + System.out.println(message); + } + + @Override + public void debug(String message) { + if (debug) { + info(message); + } + } + + @Override + public void setDebug(boolean debug) { + this.debug = debug; + } + + @Override + public boolean isDebug() { + return debug; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java new file mode 100644 index 00000000000..fda3eb9ad7e --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020 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.platform.fabric; + +import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.dedicated.DedicatedServer; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.bootstrap.GeyserBootstrap; +import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.common.PlatformType; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.UUID; + +@Environment(EnvType.SERVER) +public class GeyserFabricMod implements DedicatedServerModInitializer, GeyserBootstrap { + + private GeyserConnector connector; + private Path dataFolder; + + private GeyserFabricCommandManager geyserCommandManager; + private GeyserFabricConfiguration geyserConfig; + private GeyserFabricLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; + + @Override + public void onInitializeServer() { + this.onEnable(); + } + + @Override + public void onEnable() { + dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); + if (!dataFolder.toFile().exists()) { + //noinspection ResultOfMethodCallIgnored + dataFolder.toFile().mkdir(); + } + try { + File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); + } catch (IOException ex) { + System.out.println(LanguageUtils.getLocaleStringLog("geyser.config.failed")); + ex.printStackTrace(); + } + + this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); + + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + } + + this.connector = GeyserConnector.start(PlatformType.ANDROID, this); + + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + + this.geyserCommandManager = new GeyserFabricCommandManager(connector); + } + + @Override + public void onDisable() { + if (connector != null) { + connector.shutdown(); + } + } + + @Override + public GeyserConfiguration getGeyserConfig() { + return geyserConfig; + } + + @Override + public GeyserLogger getGeyserLogger() { + return geyserLogger; + } + + @Override + public CommandManager getGeyserCommandManager() { + return geyserCommandManager; + } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserPingPassthrough; + } + + @Override + public Path getConfigFolder() { + return dataFolder; + } + + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserFabricDumpInfo(); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java new file mode 100644 index 00000000000..d6180e7ec86 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 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.platform.fabric.command; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; + +public class GeyserFabricCommandManager extends CommandManager { + + public GeyserFabricCommandManager(GeyserConnector connector) { + super(connector); + } + + @Override + public String getDescription(String command) { + return ""; + } +} diff --git a/bootstrap/fabric/src/main/resources/fabric.mixins.json b/bootstrap/fabric/src/main/resources/fabric.mixins.json new file mode 100644 index 00000000000..a1f71d18264 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/fabric.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.platform.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} 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..10defc82d75 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "geyser-fabric", + "version": "${version}", + "name": "Geyser-Fabric", + "description": "A bridge/proxy allowing you to connect to Minecraft: Java Edition servers with Minecraft: Bedrock Edition. ", + "authors": [ + "GeyserMC" + ], + "contact": { + "website": "https://geysermc.org", + "repo": "https://github.com/GeyserMC/Geyser-Fabric" + }, + "license": "MIT", + "icon": "assets/fabric/icon.png", + "environment": "server", + "entrypoints": { + "server": [ + "org.geysermc.platform.fabric.GeyserFabricMod" + ] + }, + "mixins": [ + "fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.10.1+build.209", + "fabric": "*", + "minecraft": "1.16.3" + } +} From e7c00d897f9a6bb66399e4dbc356a69f6d464f41 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 12:34:25 -0400 Subject: [PATCH 002/358] Add .gitignore --- bootstrap/fabric/.gitignore | 239 ++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 bootstrap/fabric/.gitignore diff --git a/bootstrap/fabric/.gitignore b/bootstrap/fabric/.gitignore new file mode 100644 index 00000000000..4215ddd837c --- /dev/null +++ b/bootstrap/fabric/.gitignore @@ -0,0 +1,239 @@ + +# Created by https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode +# Edit at https://www.gitignore.io/gitignore?templates=git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### VisualStudioCode ### +# Note: Manually edited to remove settings files +.vscode/* +# !.vscode/settings.json +# !.vscode/tasks.json +# !.vscode/launch.json +# !.vscode/extensions.json +# *.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode + +### Geyser ### +run/ From be07a47f9fad0e3e41bb99e2d02714a9be407997 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 13:41:50 -0400 Subject: [PATCH 003/358] Implement proper logger, shutdown, and IP checking --- .../platform/fabric/GeyserFabricLogger.java | 18 +++++++++++------- .../platform/fabric/GeyserFabricMod.java | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java index c3f151c4cb7..40e18e43b33 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java @@ -25,10 +25,14 @@ package org.geysermc.platform.fabric; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.geysermc.connector.GeyserLogger; public class GeyserFabricLogger implements GeyserLogger { + private final Logger logger = LogManager.getLogger("geyser-fabric"); + private boolean debug; public GeyserFabricLogger(boolean isDebug) { @@ -37,38 +41,38 @@ public GeyserFabricLogger(boolean isDebug) { @Override public void severe(String message) { - System.out.println(message); + logger.fatal(message); } @Override public void severe(String message, Throwable error) { - System.out.println(message); + logger.fatal(message, error); } @Override public void error(String message) { - System.out.println(message); + logger.error(message); } @Override public void error(String message, Throwable error) { - System.out.println(message); + logger.error(message, error); } @Override public void warning(String message) { - System.out.println(message); + logger.warn(message); } @Override public void info(String message) { - System.out.println(message); + logger.info(message); } @Override public void debug(String message) { if (debug) { - info(message); + logger.info(message); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index fda3eb9ad7e..22d48013ce8 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -28,8 +28,9 @@ import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.server.dedicated.DedicatedServer; +import org.apache.logging.log4j.LogManager; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -76,7 +77,7 @@ public void onEnable() { (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); } catch (IOException ex) { - System.out.println(LanguageUtils.getLocaleStringLog("geyser.config.failed")); + LogManager.getLogger("geyser-fabric").error(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); } @@ -86,6 +87,14 @@ public void onEnable() { if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); + ServerLifecycleEvents.SERVER_STARTING.register((server) -> { + String ip = server.getServerIp(); + int port = server.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()) { @@ -94,6 +103,9 @@ public void onEnable() { this.connector = GeyserConnector.start(PlatformType.ANDROID, this); + // Register onDisable so players are properly kicked + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); this.geyserCommandManager = new GeyserFabricCommandManager(connector); From 6ccde86057c86cd2dd5d13da49d4fc8495ed4666 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 14:11:27 -0400 Subject: [PATCH 004/358] Getting server IP now works --- .../platform/fabric/GeyserFabricMod.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 22d48013ce8..d3aa7987305 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -30,6 +30,8 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.block.Block; +import net.minecraft.util.registry.Registry; import org.apache.logging.log4j.LogManager; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; @@ -38,6 +40,7 @@ import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; @@ -85,30 +88,31 @@ public void onEnable() { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { - this.geyserConfig.setAutoconfiguredRemote(true); - ServerLifecycleEvents.SERVER_STARTING.register((server) -> { + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { + // Required to do this so we can get the proper IP and port if needed + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); String ip = server.getServerIp(); int port = server.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().getPort()); - } + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + } - this.connector = GeyserConnector.start(PlatformType.ANDROID, this); + this.connector = GeyserConnector.start(PlatformType.ANDROID, this); - // Register onDisable so players are properly kicked - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); - this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.geyserCommandManager = new GeyserFabricCommandManager(connector); + }); - this.geyserCommandManager = new GeyserFabricCommandManager(connector); + // Register onDisable so players are properly kicked + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); } @Override From 4b6f1a06b71f5b55cb970227c1c6c1c3eb568359 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 14:23:55 -0400 Subject: [PATCH 005/358] Remove unused imports --- .../java/org/geysermc/platform/fabric/GeyserFabricMod.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index d3aa7987305..f008cb06995 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -30,8 +30,6 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.block.Block; -import net.minecraft.util.registry.Registry; import org.apache.logging.log4j.LogManager; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; @@ -40,7 +38,6 @@ import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; From c34b63c8c67ac1837246776869181b561ec3cea0 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 18:43:02 -0400 Subject: [PATCH 006/358] Add proper commands support --- bootstrap/fabric/build.gradle | 3 + .../platform/fabric/GeyserFabricDumpInfo.java | 30 +++++++++ .../platform/fabric/GeyserFabricMod.java | 21 +++++- .../fabric/command/FabricCommandSender.java | 65 +++++++++++++++++++ .../command/GeyserFabricCommandExecutor.java | 54 +++++++++++++++ .../platform/fabric/command/ModInfo.java | 53 +++++++++++++++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 8377dab43c0..059b59d424c 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -23,6 +23,9 @@ dependencies { // You may need to force-disable transitiveness on them. implementation 'org.geysermc:connector:1.1.0' + + compileOnly 'org.projectlombok:lombok:1.18.4' + annotationProcessor 'org.projectlombok:lombok:1.18.4' } repositories { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java index cd35e77f23f..2c9917de9d6 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -25,7 +25,37 @@ package org.geysermc.platform.fabric; +import lombok.Getter; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; +import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.platform.fabric.command.ModInfo; +import java.util.ArrayList; +import java.util.List; + +@Getter public class GeyserFabricDumpInfo extends BootstrapDumpInfo { + + private String serverIP; + private int serverPort; + private List mods; + + public GeyserFabricDumpInfo(MinecraftServer server) { + super(); + if (AsteriskSerializer.showSensitive || (server.getServerIp() == null || server.getServerIp().equals("") || server.getServerIp().equals("0.0.0.0"))) { + this.serverIP = server.getServerIp(); + } else { + this.serverIP = "***"; + } + this.serverPort = server.getServerPort(); + this.mods = new ArrayList<>(); + + for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { + this.mods.add(new ModInfo(mod)); + } + } + } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index f008cb06995..ea5210bbc92 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -25,16 +25,20 @@ package org.geysermc.platform.fabric; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; import org.apache.logging.log4j.LogManager; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; @@ -42,11 +46,13 @@ import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.Map; import java.util.UUID; @Environment(EnvType.SERVER) @@ -54,6 +60,7 @@ public class GeyserFabricMod implements DedicatedServerModInitializer, GeyserBoo private GeyserConnector connector; private Path dataFolder; + private MinecraftServer server; private GeyserFabricCommandManager geyserCommandManager; private GeyserFabricConfiguration geyserConfig; @@ -87,6 +94,7 @@ public void onEnable() { ServerLifecycleEvents.SERVER_STARTED.register((server) -> { // Required to do this so we can get the proper IP and port if needed + this.server = server; if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); String ip = server.getServerIp(); @@ -106,6 +114,17 @@ public void onEnable() { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); this.geyserCommandManager = new GeyserFabricCommandManager(connector); + + // Start command building + // Set just "geyser" as the help command + LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") + .executes(new GeyserFabricCommandExecutor(connector, "help")); + for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { + // Register all subcommands as valid + builder.then(net.minecraft.server.command.CommandManager.literal( + command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey()))); + } + server.getCommandManager().getDispatcher().register(builder); }); // Register onDisable so players are properly kicked @@ -146,6 +165,6 @@ public Path getConfigFolder() { @Override public BootstrapDumpInfo getDumpInfo() { - return new GeyserFabricDumpInfo(); + return new GeyserFabricDumpInfo(server); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java new file mode 100644 index 00000000000..b73f8556220 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 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.platform.fabric.command; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; + +public class FabricCommandSender implements CommandSender { + + private final ServerCommandSource source; + + public FabricCommandSender(ServerCommandSource source) { + this.source = source; + } + + @Override + public String getName() { + return source.getName(); + } + + @Override + public void sendMessage(String message) { + try { + source.getPlayer().sendMessage(new LiteralText(message), false); + } catch (CommandSyntaxException e) { // why + GeyserConnector.getInstance().getLogger().info(message); + } + } + + @Override + public boolean isConsole() { + try { + source.getPlayer(); + return false; + } catch (CommandSyntaxException e) { + return true; + } + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java new file mode 100644 index 00000000000..290e452c684 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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.platform.fabric.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.command.ServerCommandSource; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.GeyserCommand; + +public class GeyserFabricCommandExecutor implements Command { + + private final String commandName; + private final GeyserConnector connector; + + public GeyserFabricCommandExecutor(GeyserConnector connector, String commandName) { + this.commandName = commandName; + this.connector = connector; + } + + @Override + public int run(CommandContext context) { + ServerCommandSource source = (ServerCommandSource) context.getSource(); + getCommand(commandName).execute(new FabricCommandSender(source), new String[0]); + return 0; + } + + private GeyserCommand getCommand(String label) { + return connector.getCommandManager().getCommands().get(label); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java new file mode 100644 index 00000000000..722fd2ded7d --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 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.platform.fabric.command; + +import lombok.Getter; +import net.fabricmc.loader.api.ModContainer; + +import java.util.ArrayList; +import java.util.List; + +/** + * A wrapper for Fabric mod information to be presented in a Geyser dump + */ +@Getter +public class ModInfo { + + private String name; + private String id; + private String version; + private List authors; + + public ModInfo(ModContainer mod) { + this.name = mod.getMetadata().getName(); + this.id = mod.getMetadata().getId(); + this.authors = new ArrayList<>(); + mod.getMetadata().getAuthors().forEach((person) -> this.authors.add(person.getName())); + this.version = mod.getMetadata().getVersion().getFriendlyString(); + } + +} From 64d5cf51d13bf1f0ed9203ed4e290638c051183b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 6 Oct 2020 18:53:11 -0400 Subject: [PATCH 007/358] Create README.md --- bootstrap/fabric/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 bootstrap/fabric/README.md diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md new file mode 100644 index 00000000000..6c9e6b3045d --- /dev/null +++ b/bootstrap/fabric/README.md @@ -0,0 +1,8 @@ +# Geyser-Fabric + +[![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) + +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) + +Geyser-Fabric is Geyser specifically built for the Fabric modding platform. From 7a91e0a80d14a43119b04f3a71dc87ffee8a3c05 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 6 Oct 2020 22:45:15 -0400 Subject: [PATCH 008/358] Start on compiling --- bootstrap/fabric/build.gradle | 30 ++++++++++++++++++++++++++++++ bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 059b59d424c..6ea04cb5dd4 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -1,6 +1,7 @@ plugins { id 'fabric-loom' version '0.5-SNAPSHOT' id 'maven-publish' + id 'com.github.johnrengelman.shadow' version '6.0.0' } sourceCompatibility = JavaVersion.VERSION_1_8 @@ -23,6 +24,11 @@ dependencies { // You may need to force-disable transitiveness on them. implementation 'org.geysermc:connector:1.1.0' + shadow('org.geysermc:connector:1.1.0') { +// exclude group: 'org.w3c' +// exclude group: 'org.xml' +// exclude group: 'javax.xml' + } compileOnly 'org.projectlombok:lombok:1.18.4' annotationProcessor 'org.projectlombok:lombok:1.18.4' @@ -74,10 +80,34 @@ task sourcesJar(type: Jar, dependsOn: classes) { from sourceSets.main.allSource } +shadowJar { + configurations = [project.configurations.shadow] + //relocate 'org.apache', 'geyser.org.apache' + relocate 'org.reflections', 'geyser.org.reflections' + relocate 'org.dom4j', 'geyser.org.dom4j' + relocate 'javax.xml', 'geyser.javaxxml' + relocate 'org.xml', 'geyser.xml' + //relocate 'org.w3c', 'geyser.w3c' +} + jar { from "LICENSE" } +remapJar { + dependsOn(shadowJar) + input.set shadowJar.archiveFile.get() +} +// +//import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation +// +//task relocateShadowJar(type: ConfigureShadowRelocation) { +// target = tasks.shadowJar +// prefix = "org.geyser.platform.fabric.relocate" +//} +// +//tasks.shadowJar.dependsOn tasks.relocateShadowJar + // configure the maven publication publishing { publications { diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index fbcb2816cee..7fe0d5d14fd 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -8,7 +8,7 @@ loader_version=0.10.1+build.209 # Mod Properties mod_version=1.0-SNAPSHOT maven_group=org.geysermc.platform -archives_base_name=fabric +archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html fabric_version=0.23.0+build.410-1.16 From b293c3478a1c323ea6443fc66cfd850ed5a96f60 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 18:35:35 -0400 Subject: [PATCH 009/358] It compiles! :D --- bootstrap/fabric/build.gradle | 25 ++++++------------- .../platform/fabric/GeyserFabricMod.java | 2 +- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 6ea04cb5dd4..728c14641ba 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -1,9 +1,13 @@ plugins { id 'fabric-loom' version '0.5-SNAPSHOT' id 'maven-publish' - id 'com.github.johnrengelman.shadow' version '6.0.0' + id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'java' } +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'java' + sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -24,17 +28,15 @@ dependencies { // You may need to force-disable transitiveness on them. implementation 'org.geysermc:connector:1.1.0' - shadow('org.geysermc:connector:1.1.0') { -// exclude group: 'org.w3c' -// exclude group: 'org.xml' -// exclude group: 'javax.xml' - } + shadow 'org.geysermc:connector:1.1.0' compileOnly 'org.projectlombok:lombok:1.18.4' annotationProcessor 'org.projectlombok:lombok:1.18.4' } repositories { + mavenLocal() + maven { url = uri('https://repo.nukkitx.com/maven-releases/') } @@ -82,12 +84,10 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] - //relocate 'org.apache', 'geyser.org.apache' relocate 'org.reflections', 'geyser.org.reflections' relocate 'org.dom4j', 'geyser.org.dom4j' relocate 'javax.xml', 'geyser.javaxxml' relocate 'org.xml', 'geyser.xml' - //relocate 'org.w3c', 'geyser.w3c' } jar { @@ -98,15 +98,6 @@ remapJar { dependsOn(shadowJar) input.set shadowJar.archiveFile.get() } -// -//import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation -// -//task relocateShadowJar(type: ConfigureShadowRelocation) { -// target = tasks.shadowJar -// prefix = "org.geyser.platform.fabric.relocate" -//} -// -//tasks.shadowJar.dependsOn tasks.relocateShadowJar // configure the maven publication publishing { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index ea5210bbc92..c5058a13094 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -109,7 +109,7 @@ public void onEnable() { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } - this.connector = GeyserConnector.start(PlatformType.ANDROID, this); + this.connector = GeyserConnector.start(PlatformType.FABRIC, this); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); From 977ce4bbece4de4693c9377e6de48f39d26072fd Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 19:02:41 -0400 Subject: [PATCH 010/358] Guess we don't need any relocations --- bootstrap/fabric/build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 728c14641ba..542fb58c6ee 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -84,10 +84,6 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] - relocate 'org.reflections', 'geyser.org.reflections' - relocate 'org.dom4j', 'geyser.org.dom4j' - relocate 'javax.xml', 'geyser.javaxxml' - relocate 'org.xml', 'geyser.xml' } jar { From 105f4f0a32eddadc9f078874270537d989ec3619 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 19:44:13 -0400 Subject: [PATCH 011/358] Fix console chat output --- .../geysermc/platform/fabric/command/FabricCommandSender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index b73f8556220..3ec5481438c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -30,6 +30,7 @@ import net.minecraft.text.LiteralText; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.common.ChatColor; public class FabricCommandSender implements CommandSender { @@ -49,7 +50,7 @@ public void sendMessage(String message) { try { source.getPlayer().sendMessage(new LiteralText(message), false); } catch (CommandSyntaxException e) { // why - GeyserConnector.getInstance().getLogger().info(message); + GeyserConnector.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); } } From 9e62c6369ce2aca0fbf690ab7c83aa1d3de86075 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:01:01 -0400 Subject: [PATCH 012/358] Jenkinsfile attempt --- bootstrap/fabric/Jenkinsfile | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 bootstrap/fabric/Jenkinsfile diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile new file mode 100644 index 00000000000..3e8155cd94b --- /dev/null +++ b/bootstrap/fabric/Jenkinsfile @@ -0,0 +1,62 @@ +pipeline { + agent any + tools { + jdk 'Java 8' + } + options { + buildDiscarder(logRotator(artifactNumToKeepStr: '20')) + } + stages { + stage ('Build') { + steps { + sh './gradlew clean build' + } + post { + success { + archiveArtifacts artifacts: 'bootstrap/**/target/*.jar', excludes: 'bootstrap/**/target/original-*.jar', fingerprint: true + archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/*-dev.jar' 'build/libs/*-sources*jar' 'build/libs/*-all.jar', fingerprint: true + } + } + } + } + + post { + always { + script { + def changeLogSets = currentBuild.changeSets + def message = "**Changes:**" + + if (changeLogSets.size() == 0) { + message += "\n*No changes.*" + } else { + def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") + def count = 0; + def extra = 0; + for (int i = 0; i < changeLogSets.size(); i++) { + def entries = changeLogSets[i].items + for (int j = 0; j < entries.length; j++) { + if (count <= 10) { + def entry = entries[j] + def commitId = entry.commitId.substring(0, 6) + message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" + count++ + } else { + extra++; + } + } + } + + if (extra != 0) { + message += "\n - ${extra} more commits" + } + } + + env.changes = message + } + deleteDir() + withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + } + } + } +} From ad42c800cd0180ef50909dc2f550723b9897fb9c Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:04:08 -0400 Subject: [PATCH 013/358] Remove erroneous line --- bootstrap/fabric/Jenkinsfile | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 3e8155cd94b..42f09947e38 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -13,7 +13,6 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'bootstrap/**/target/*.jar', excludes: 'bootstrap/**/target/original-*.jar', fingerprint: true archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/*-dev.jar' 'build/libs/*-sources*jar' 'build/libs/*-all.jar', fingerprint: true } } From f99cbfa231129353409643f8b777a9405477f3c2 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:21:46 -0400 Subject: [PATCH 014/358] Maybe this works? --- bootstrap/fabric/Jenkinsfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 42f09947e38..937a9c3d4a1 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -13,7 +13,11 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/*-dev.jar' 'build/libs/*-sources*jar' 'build/libs/*-all.jar', fingerprint: true + archiveArtifacts artifacts: 'build/libs/*.jar', excludes: { + 'build/libs/*-dev.jar' + 'build/libs/*-sources*jar' + 'build/libs/*-all.jar' + }, fingerprint: true } } } From 293f691d39dd8343f3dbd562d02b1d0b9a2489b3 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:28:43 -0400 Subject: [PATCH 015/358] Manually define Gradle --- bootstrap/fabric/Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 937a9c3d4a1..abd106ec10e 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -1,6 +1,7 @@ pipeline { agent any tools { + gradle 'Gradle 6' jdk 'Java 8' } options { @@ -9,7 +10,7 @@ pipeline { stages { stage ('Build') { steps { - sh './gradlew clean build' + sh 'gradle clean build' } post { success { From 7b0c1e9a05bdeebaa24ce654e147057a53657488 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:29:39 -0400 Subject: [PATCH 016/358] Generify Gradle --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index abd106ec10e..f5251f92e8e 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent any tools { - gradle 'Gradle 6' + gradle 'Gradle' jdk 'Java 8' } options { From 0e77f20f86e19e682d21521bc8aff46aa434c990 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:37:23 -0400 Subject: [PATCH 017/358] Specify Fabric repository --- bootstrap/fabric/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 542fb58c6ee..3f38fc84016 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -37,6 +37,10 @@ dependencies { repositories { mavenLocal() + maven { + url = uri('https://maven.fabricmc.net/') + } + maven { url = uri('https://repo.nukkitx.com/maven-releases/') } From ca65f9f45a10e4fe7db8a7db9afffab363597fb7 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 20:59:09 -0400 Subject: [PATCH 018/358] Fix ./gradlew usage --- bootstrap/fabric/Jenkinsfile | 3 +-- bootstrap/fabric/build.gradle | 4 ---- .../fabric/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes 3 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index f5251f92e8e..937a9c3d4a1 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -1,7 +1,6 @@ pipeline { agent any tools { - gradle 'Gradle' jdk 'Java 8' } options { @@ -10,7 +9,7 @@ pipeline { stages { stage ('Build') { steps { - sh 'gradle clean build' + sh './gradlew clean build' } post { success { diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 3f38fc84016..542fb58c6ee 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -37,10 +37,6 @@ dependencies { repositories { mavenLocal() - maven { - url = uri('https://maven.fabricmc.net/') - } - maven { url = uri('https://repo.nukkitx.com/maven-releases/') } diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 From 56b70927f2c087495904a258e81758b53b65a604 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 21:05:42 -0400 Subject: [PATCH 019/358] Fix archiving --- bootstrap/fabric/Jenkinsfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 937a9c3d4a1..58bfc96ad87 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -13,11 +13,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'build/libs/*.jar', excludes: { - 'build/libs/*-dev.jar' - 'build/libs/*-sources*jar' - 'build/libs/*-all.jar' - }, fingerprint: true + archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/Geyser-Fabric-*-.jar', fingerprint: true } } } From ae3896d1d00c731417d9a4a08a158abea8a6978b Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 7 Oct 2020 22:42:17 -0400 Subject: [PATCH 020/358] Add command permissions system --- .../platform/fabric/GeyserFabricDumpInfo.java | 1 - .../platform/fabric/GeyserFabricMod.java | 38 +++++++++++++++-- .../fabric/GeyserFabricPermissions.java | 42 +++++++++++++++++++ .../fabric/{command => }/ModInfo.java | 2 +- .../command/GeyserFabricCommandExecutor.java | 15 ++++++- .../src/main/resources/fabric.mixins.json | 13 ------ .../fabric/src/main/resources/fabric.mod.json | 3 -- .../fabric/src/main/resources/permissions.yml | 10 +++++ 8 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java rename bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/{command => }/ModInfo.java (97%) delete mode 100644 bootstrap/fabric/src/main/resources/fabric.mixins.json create mode 100644 bootstrap/fabric/src/main/resources/permissions.yml diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java index 2c9917de9d6..1b9d68a9ce7 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -31,7 +31,6 @@ import net.minecraft.server.MinecraftServer; import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.platform.fabric.command.ModInfo; import java.util.ArrayList; import java.util.List; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index c5058a13094..0e395dcee3f 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -50,16 +50,19 @@ import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import java.util.function.Function; @Environment(EnvType.SERVER) public class GeyserFabricMod implements DedicatedServerModInitializer, GeyserBootstrap { private GeyserConnector connector; private Path dataFolder; + private List playerCommands; private MinecraftServer server; private GeyserFabricCommandManager geyserCommandManager; @@ -83,6 +86,8 @@ public void onEnable() { File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); + File permissionsFile = fileOrCopiedFromResource(dataFolder.resolve("permissions.yml").toFile(), "permissions.yml"); + this.playerCommands = Arrays.asList(FileUtils.loadConfig(permissionsFile, GeyserFabricPermissions.class).getCommands()); } catch (IOException ex) { LogManager.getLogger("geyser-fabric").error(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); @@ -118,11 +123,12 @@ public void onEnable() { // Start command building // Set just "geyser" as the help command LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") - .executes(new GeyserFabricCommandExecutor(connector, "help")); + .executes(new GeyserFabricCommandExecutor(connector, "help", !playerCommands.contains("help"))); for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { // Register all subcommands as valid builder.then(net.minecraft.server.command.CommandManager.literal( - command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey()))); + command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey(), + !playerCommands.contains(command.getKey())))); } server.getCommandManager().getDispatcher().register(builder); }); @@ -167,4 +173,28 @@ public Path getConfigFolder() { public BootstrapDumpInfo getDumpInfo() { return new GeyserFabricDumpInfo(server); } + + private File fileOrCopiedFromResource(File file, String name) throws IOException { + if (!file.exists()) { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file); + InputStream input = GeyserFabricMod.class.getResourceAsStream("/" + name); // resources need leading "/" prefix + + byte[] bytes = new byte[input.available()]; + + //noinspection ResultOfMethodCallIgnored + input.read(bytes); + + for(char c : new String(bytes).toCharArray()) { + fos.write(c); + } + + fos.flush(); + input.close(); + fos.close(); + } + + return file; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java new file mode 100644 index 00000000000..51a7cb2e4e7 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 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.platform.fabric; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +/** + * A class outline of the permissions.yml file + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GeyserFabricPermissions { + + @Getter + @JsonProperty("commands") + private String[] commands; + +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java similarity index 97% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java rename to bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java index 722fd2ded7d..15967876660 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/ModInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.command; +package org.geysermc.platform.fabric; import lombok.Getter; import net.fabricmc.loader.api.ModContainer; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index 290e452c684..1064af3a953 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -30,21 +30,32 @@ import net.minecraft.server.command.ServerCommandSource; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.utils.LanguageUtils; public class GeyserFabricCommandExecutor implements Command { private final String commandName; private final GeyserConnector connector; + /** + * Whether the command requires an OP permission level of 2 or greater + */ + private final boolean requiresPermission; - public GeyserFabricCommandExecutor(GeyserConnector connector, String commandName) { + public GeyserFabricCommandExecutor(GeyserConnector connector, String commandName, boolean requiresPermission) { this.commandName = commandName; this.connector = connector; + this.requiresPermission = requiresPermission; } @Override public int run(CommandContext context) { ServerCommandSource source = (ServerCommandSource) context.getSource(); - getCommand(commandName).execute(new FabricCommandSender(source), new String[0]); + FabricCommandSender sender = new FabricCommandSender(source); + if (requiresPermission && !source.hasPermissionLevel(2)) { + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); + return 0; + } + getCommand(commandName).execute(sender, new String[0]); return 0; } diff --git a/bootstrap/fabric/src/main/resources/fabric.mixins.json b/bootstrap/fabric/src/main/resources/fabric.mixins.json deleted file mode 100644 index a1f71d18264..00000000000 --- a/bootstrap/fabric/src/main/resources/fabric.mixins.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "required": true, - "minVersion": "0.8", - "package": "org.geysermc.platform.fabric.mixin", - "compatibilityLevel": "JAVA_8", - "mixins": [ - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 10defc82d75..1655cc21254 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -19,9 +19,6 @@ "org.geysermc.platform.fabric.GeyserFabricMod" ] }, - "mixins": [ - "fabric.mixins.json" - ], "depends": { "fabricloader": ">=0.10.1+build.209", "fabric": "*", diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml new file mode 100644 index 00000000000..a51d3169a99 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/permissions.yml @@ -0,0 +1,10 @@ +# Uncomment any commands that you wish to be run by clients +# Commented commands require an OP permission of 2 or greater +commands: +# - dump + - help + - offhand +# - list +# - reload +# - shutdown +# - version From f82329b34f64c98e62578880ebf4abcb036a925e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 7 Oct 2020 23:05:23 -0400 Subject: [PATCH 021/358] Add Jenkins builds to the README --- bootstrap/fabric/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index 6c9e6b3045d..aca64c6234c 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -3,6 +3,7 @@ [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) Geyser-Fabric is Geyser specifically built for the Fabric modding platform. From 4d925da68a3b2dd3276eeca8d740265bc9129494 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 7 Oct 2020 23:05:52 -0400 Subject: [PATCH 022/358] Add specific Jenkins link --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index aca64c6234c..b964cd98f27 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -3,7 +3,7 @@ [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/) +[![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) Geyser-Fabric is Geyser specifically built for the Fabric modding platform. From 856ce6b588d7eaf49f20739f1a168a047bfd0c62 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Thu, 8 Oct 2020 12:54:53 -0400 Subject: [PATCH 023/358] Exclude all irrelevant builds --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 58bfc96ad87..c830b4e5373 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -13,7 +13,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/Geyser-Fabric-*-.jar', fingerprint: true + archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/Geyser-Fabric-*-sources*.jar,build/libs/Geyser-Fabric-*-all*.jar', fingerprint: true } } } From c426c335d7b55918443817912c6361b02b720113 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 11 Oct 2020 23:15:22 -0400 Subject: [PATCH 024/358] Fix reloading Geyser --- bootstrap/fabric/Jenkinsfile | 2 +- .../platform/fabric/GeyserFabricMod.java | 77 +++++++++++-------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index c830b4e5373..9a3118e15d1 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -9,7 +9,7 @@ pipeline { stages { stage ('Build') { steps { - sh './gradlew clean build' + sh './gradlew clean build --refresh-dependencies' } post { success { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 0e395dcee3f..b716e1a8bbd 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -97,50 +97,65 @@ public void onEnable() { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - ServerLifecycleEvents.SERVER_STARTED.register((server) -> { - // Required to do this so we can get the proper IP and port if needed - this.server = server; - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { - this.geyserConfig.setAutoconfiguredRemote(true); - String ip = server.getServerIp(); - int port = server.getServerPort(); - if (ip != null && !ip.isEmpty() && !ip.equals("0.0.0.0")) { - this.geyserConfig.getRemote().setAddress(ip); - } - this.geyserConfig.getRemote().setPort(port); - } + if (server == null) { + // Server has yet to start + // Set as an event so we can get the proper IP and port if needed + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { + this.server = server; + startGeyser(); + }); + + // Register onDisable so players are properly kicked + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + } else { + // Server has started and this is a reload + startGeyser(); + } + } - if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + /** + * Initialize core Geyser. + * A function, as it needs to be called in different places depending on if Geyser is being reloaded or not. + */ + public void startGeyser() { + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); + String ip = server.getServerIp(); + int port = server.getServerPort(); + if (ip != null && !ip.isEmpty() && !ip.equals("0.0.0.0")) { + this.geyserConfig.getRemote().setAddress(ip); } + this.geyserConfig.getRemote().setPort(port); + } - this.connector = GeyserConnector.start(PlatformType.FABRIC, this); + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + } - this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.connector = GeyserConnector.start(PlatformType.FABRIC, this); - this.geyserCommandManager = new GeyserFabricCommandManager(connector); + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); - // Start command building - // Set just "geyser" as the help command - LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") - .executes(new GeyserFabricCommandExecutor(connector, "help", !playerCommands.contains("help"))); - for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { - // Register all subcommands as valid - builder.then(net.minecraft.server.command.CommandManager.literal( - command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey(), - !playerCommands.contains(command.getKey())))); - } - server.getCommandManager().getDispatcher().register(builder); - }); + this.geyserCommandManager = new GeyserFabricCommandManager(connector); - // Register onDisable so players are properly kicked - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + // Start command building + // Set just "geyser" as the help command + LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") + .executes(new GeyserFabricCommandExecutor(connector, "help", !playerCommands.contains("help"))); + for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { + // Register all subcommands as valid + builder.then(net.minecraft.server.command.CommandManager.literal( + command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey(), + !playerCommands.contains(command.getKey())))); + } + server.getCommandManager().getDispatcher().register(builder); } @Override public void onDisable() { if (connector != null) { connector.shutdown(); + connector = null; } } From a9b414c675083b0124e60b1413689836570920ad Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Sun, 25 Oct 2020 17:29:19 +0100 Subject: [PATCH 025/358] Add LAN games support. --- .../platform/fabric/GeyserFabricMod.java | 36 +++++++---- .../fabric/GeyserServerPortGetter.java | 48 +++++++++++++++ .../mixin/client/IntegratedServerMixin.java | 59 +++++++++++++++++++ .../server/MinecraftDedicatedServerMixin.java | 58 ++++++++++++++++++ .../fabric/src/main/resources/fabric.mod.json | 7 ++- .../main/resources/geyser-fabric.mixins.json | 14 +++++ 6 files changed, 207 insertions(+), 15 deletions(-) create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java create mode 100644 bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index b716e1a8bbd..dd5154267eb 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -28,7 +28,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; +import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.MinecraftServer; @@ -57,8 +57,9 @@ import java.util.*; import java.util.function.Function; -@Environment(EnvType.SERVER) -public class GeyserFabricMod implements DedicatedServerModInitializer, GeyserBootstrap { +public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { + + private static GeyserFabricMod instance; private GeyserConnector connector; private Path dataFolder; @@ -71,8 +72,14 @@ public class GeyserFabricMod implements DedicatedServerModInitializer, GeyserBoo private IGeyserPingPassthrough geyserPingPassthrough; @Override - public void onInitializeServer() { + public void onInitialize() { + instance = this; + 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 @@ -99,29 +106,27 @@ public void onEnable() { if (server == null) { // Server has yet to start - // Set as an event so we can get the proper IP and port if needed - ServerLifecycleEvents.SERVER_STARTED.register((server) -> { - this.server = server; - startGeyser(); - }); - // Register onDisable so players are properly kicked ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); } else { // Server has started and this is a reload - startGeyser(); + startGeyser(this.server); } } /** * 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() { + public void startGeyser(MinecraftServer server) { + this.server = server; + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); String ip = server.getServerIp(); - int port = server.getServerPort(); + int port = ((GeyserServerPortGetter) server).geyser$getServerPort(); if (ip != null && !ip.isEmpty() && !ip.equals("0.0.0.0")) { this.geyserConfig.getRemote().setAddress(ip); } @@ -157,6 +162,7 @@ public void onDisable() { connector.shutdown(); connector = null; } + this.server = null; } @Override @@ -212,4 +218,8 @@ private File fileOrCopiedFromResource(File file, String name) throws IOException return file; } + + public static GeyserFabricMod getInstance() { + return instance; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java new file mode 100644 index 00000000000..5af7775a818 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 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.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#getServerPort()} 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/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java new file mode 100644 index 00000000000..a84f7111018 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 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.platform.fabric.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.world.GameMode; +import org.geysermc.platform.fabric.GeyserFabricMod; +import org.geysermc.platform.fabric.GeyserServerPortGetter; +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 lanPort; + + @Inject(method = "openToLan", at = @At("RETURN")) + private void onOpenToLan(GameMode gameMode, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) { + if (cir.getReturnValueZ()) { + // If the LAN is opened, starts Geyser. + GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this); + } + } + + @Override + public int geyser$getServerPort() { + return this.lanPort; + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java new file mode 100644 index 00000000000..eec6af74f9d --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 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.platform.fabric.mixin.server; + +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.datafixers.DataFixer; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldGenerationProgressListenerFactory; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.MinecraftDedicatedServer; +import net.minecraft.util.UserCache; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.world.SaveProperties; +import net.minecraft.world.level.storage.LevelStorage; +import org.geysermc.platform.fabric.GeyserServerPortGetter; +import org.spongepowered.asm.mixin.Mixin; + +import java.net.Proxy; + +@Mixin(MinecraftDedicatedServer.class) +public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter +{ + // Constructor to compile + public MinecraftDedicatedServerMixin(Thread thread, DynamicRegistryManager.Impl impl, LevelStorage.Session session, SaveProperties saveProperties, ResourcePackManager resourcePackManager, Proxy proxy, DataFixer dataFixer, ServerResourceManager serverResourceManager, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { + super(thread, impl, session, saveProperties, resourcePackManager, proxy, dataFixer, serverResourceManager, minecraftSessionService, gameProfileRepository, userCache, worldGenerationProgressListenerFactory); + } + + @Override + public int geyser$getServerPort() { + return this.getServerPort(); + } +} diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 1655cc21254..55f5ceb701c 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -13,12 +13,15 @@ }, "license": "MIT", "icon": "assets/fabric/icon.png", - "environment": "server", + "environment": "*", "entrypoints": { - "server": [ + "main": [ "org.geysermc.platform.fabric.GeyserFabricMod" ] }, + "mixins": [ + "geyser-fabric.mixins.json" + ], "depends": { "fabricloader": ">=0.10.1+build.209", "fabric": "*", 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..3965f978131 --- /dev/null +++ b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "org.geysermc.platform.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "client": [ + "client.IntegratedServerMixin" + ], + "server": [ + "server.MinecraftDedicatedServerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} From 4883a207972fde0a31e0e0506f5aee1be89eab2b Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 25 Oct 2020 16:22:50 -0400 Subject: [PATCH 026/358] Add load complete message and don't null the server if reloading --- .../geysermc/platform/fabric/GeyserFabricMod.java | 11 ++++++++--- .../fabric/command/GeyserFabricCommandExecutor.java | 4 ++++ .../fabric/mixin/client/IntegratedServerMixin.java | 12 ++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index dd5154267eb..8767df018b4 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -26,7 +26,7 @@ package org.geysermc.platform.fabric; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.fabricmc.api.DedicatedServerModInitializer; +import lombok.Setter; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -55,12 +55,14 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.*; -import java.util.function.Function; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private static GeyserFabricMod instance; + @Setter + private boolean reloading; + private GeyserConnector connector; private Path dataFolder; private List playerCommands; @@ -111,6 +113,7 @@ public void onEnable() { } else { // Server has started and this is a reload startGeyser(this.server); + reloading = false; } } @@ -162,7 +165,9 @@ public void onDisable() { connector.shutdown(); connector = null; } - this.server = null; + if (!reloading) { + this.server = null; + } } @Override diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index 1064af3a953..706ef950265 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -31,6 +31,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.platform.fabric.GeyserFabricMod; public class GeyserFabricCommandExecutor implements Command { @@ -55,6 +56,9 @@ public int run(CommandContext context) { sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); return 0; } + if (this.commandName.equals("reload")) { + GeyserFabricMod.getInstance().setReloading(true); + } getCommand(commandName).execute(sender, new String[0]); return 0; } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java index a84f7111018..5415a9d3a00 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -27,11 +27,16 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; import net.minecraft.server.MinecraftServer; import net.minecraft.server.integrated.IntegratedServer; +import net.minecraft.text.LiteralText; import net.minecraft.world.GameMode; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.GeyserServerPortGetter; +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; @@ -44,11 +49,18 @@ public class IntegratedServerMixin implements GeyserServerPortGetter { @Shadow private int lanPort; + @Shadow @Final private MinecraftClient client; + @Inject(method = "openToLan", at = @At("RETURN")) private void onOpenToLan(GameMode gameMode, 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 + LanguageUtils.loadGeyserLocale(this.client.options.language); + // Give indication that Geyser is loaded + this.client.player.sendMessage(new LiteralText(LanguageUtils.getPlayerLocaleString("geyser.core.start", + this.client.options.language, "localhost", String.valueOf(this.lanPort))), false); } } From 7455b78cbf99b3597d8bf157829848379ed0007f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 25 Oct 2020 23:19:56 -0400 Subject: [PATCH 027/358] Make downloading more clear --- bootstrap/fabric/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index b964cd98f27..4384313317c 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -2,8 +2,9 @@ [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) +Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) + [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) Geyser-Fabric is Geyser specifically built for the Fabric modding platform. From 6f698993ab9742efacfc092816f761a151385f5f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 25 Oct 2020 23:31:52 -0400 Subject: [PATCH 028/358] Add reference to the Geyser wiki --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index 4384313317c..d50255b1fef 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -7,4 +7,4 @@ Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) -Geyser-Fabric is Geyser specifically built for the Fabric modding platform. +Geyser-Fabric is Geyser specifically built for the Fabric modding platform. For more details, see [here](https://github.com/GeyserMC/Geyser/wiki/Geyser-Fabric). From 3d994db0f2389a60780eeb06532ee0802f96ca27 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 27 Oct 2020 13:51:54 -0400 Subject: [PATCH 029/358] Move to OpenCollab domain --- bootstrap/fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 542fb58c6ee..6a4b81cf026 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -38,11 +38,11 @@ repositories { mavenLocal() maven { - url = uri('https://repo.nukkitx.com/maven-releases/') + url = uri('https://repo.opencollab.dev/maven-releases/') } maven { - url = uri('https://repo.nukkitx.com/maven-snapshots/') + url = uri('https://repo.opencollab.dev/maven-snapshots/') } maven { From b11d2bcd3f286c5a4058a7b5e4f2504d980ea068 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sat, 7 Nov 2020 23:23:23 -0500 Subject: [PATCH 030/358] Support Minecraft 1.16.3 and 1.16.4 --- bootstrap/fabric/src/main/resources/fabric.mod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 55f5ceb701c..89cbd12b5d2 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ "depends": { "fabricloader": ">=0.10.1+build.209", "fabric": "*", - "minecraft": "1.16.3" + "minecraft": ">=1.16.3" } } From 566113fa1ea09e03e6fbffa6608cf4e113449f99 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 11 Nov 2020 23:35:44 -0500 Subject: [PATCH 031/358] Fix LAN support compiled? --- bootstrap/fabric/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 6a4b81cf026..de324a10b94 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -84,6 +84,7 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] + relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") } jar { From a3de72fed3da3a0471ab399fcc10fc7bd6474e97 Mon Sep 17 00:00:00 2001 From: Redned Date: Sat, 14 Nov 2020 17:53:59 -0600 Subject: [PATCH 032/358] Use PlatformType in common module --- .../main/java/org/geysermc/platform/fabric/GeyserFabricMod.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 8767df018b4..4f1eed1c9fb 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -34,12 +34,12 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import org.apache.logging.log4j.LogManager; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; From b4d3fd088aa7c2c3f4c4a70ae51485c075b772ce Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 17 Nov 2020 13:32:13 -0500 Subject: [PATCH 033/358] Update to 1.16.100 --- bootstrap/fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index de324a10b94..0f8f35cbccb 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.1.0' - shadow 'org.geysermc:connector:1.1.0' + implementation 'org.geysermc:connector:1.2.0-SNAPSHOT' + shadow 'org.geysermc:connector:1.2.0-SNAPSHOT' compileOnly 'org.projectlombok:lombok:1.18.4' annotationProcessor 'org.projectlombok:lombok:1.18.4' From be8329722e5ac68851880060fd47fc337fbff826 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 7 Jan 2021 09:56:06 +0000 Subject: [PATCH 034/358] Fix builds for new CI --- bootstrap/fabric/Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 9a3118e15d1..0efa73dfb2f 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -1,6 +1,7 @@ pipeline { agent any tools { + gradle 'Gradle 6' jdk 'Java 8' } options { @@ -9,7 +10,7 @@ pipeline { stages { stage ('Build') { steps { - sh './gradlew clean build --refresh-dependencies' + sh 'gradle clean build --refresh-dependencies' } post { success { From b51173ea97ab63a383ff6a9377da46ccdb72f74d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:13:13 -0500 Subject: [PATCH 035/358] Attempt to use Artifactory --- bootstrap/fabric/Jenkinsfile | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 0efa73dfb2f..4b6b1d315cd 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -18,6 +18,41 @@ pipeline { } } } + + stage ('Deploy') { + when { + branch "master" + } + + steps { + sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtGradleDeployer( + id: "GRADLE_DEPLOYER", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtGradleResolver( + id: "GRADLE_RESOLVER", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtGradleRun ( + usesPlugin: true, + tool: GRADLE_TOOL, + rootDir: "/", + buildFile: 'build.gradle', + tasks: 'clean artifactoryPublish', + deployerId: "GRADLE_DEPLOYER", + resolverId: "GRADLE_RESOLVER" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) + } + } + } } post { From 4255240b6e17682b860bd52318dd0d0661e60c4e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:14:41 -0500 Subject: [PATCH 036/358] Attempt 2 --- bootstrap/fabric/Jenkinsfile | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 4b6b1d315cd..1087ca373fd 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -52,7 +52,6 @@ pipeline { ) } } - } } post { From 029522c2fc9d9d530e3788ac04be6443880753e9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:18:41 -0500 Subject: [PATCH 037/358] Attempt 3 and then I start actually looking at docs --- bootstrap/fabric/Jenkinsfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 1087ca373fd..ca2d4596b21 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -25,7 +25,6 @@ pipeline { } steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' rtGradleDeployer( id: "GRADLE_DEPLOYER", serverId: "opencollab-artifactory", @@ -35,8 +34,6 @@ pipeline { rtGradleResolver( id: "GRADLE_RESOLVER", serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" ) rtGradleRun ( usesPlugin: true, From cd9dde9796491b0479ccff596f0c291e3e15f3bd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:32:10 -0500 Subject: [PATCH 038/358] Attempt 4 - we don't have a master branch --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index ca2d4596b21..2e2cdd2f002 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -21,7 +21,7 @@ pipeline { stage ('Deploy') { when { - branch "master" + branch "java-1.16" } steps { From ce78e43a5cafe0f6b55f5c09148dd18d4eb2df56 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:39:56 -0500 Subject: [PATCH 039/358] Attempt 5 --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 2e2cdd2f002..00eefa28e39 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -37,7 +37,7 @@ pipeline { ) rtGradleRun ( usesPlugin: true, - tool: GRADLE_TOOL, + tool: gradle, rootDir: "/", buildFile: 'build.gradle', tasks: 'clean artifactoryPublish', From 0dec24d0c95e1f040fc25bd7d7d608533a407e6c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:44:30 -0500 Subject: [PATCH 040/358] Attempt 6 --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 00eefa28e39..9bdf4963ad8 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -37,7 +37,7 @@ pipeline { ) rtGradleRun ( usesPlugin: true, - tool: gradle, + tool: 'Gradle 6', rootDir: "/", buildFile: 'build.gradle', tasks: 'clean artifactoryPublish', From 6c3c07b6a45c27d3bb1e74d3f9bb62006d52f6ea Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:51:39 -0500 Subject: [PATCH 041/358] Attempt 7 --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 9bdf4963ad8..2c82f17ad15 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -38,7 +38,7 @@ pipeline { rtGradleRun ( usesPlugin: true, tool: 'Gradle 6', - rootDir: "/", + rootDir: "", buildFile: 'build.gradle', tasks: 'clean artifactoryPublish', deployerId: "GRADLE_DEPLOYER", From 373ae84d2db1b1b886df1e8dc5b28f103d90ba8e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:59:45 -0500 Subject: [PATCH 042/358] Attempt 8 --- bootstrap/fabric/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0f8f35cbccb..dea3bb122f5 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -3,6 +3,7 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java' + id "com.jfrog.artifactory" } apply plugin: 'com.github.johnrengelman.shadow' From 6bf0c5d94c84077fd0eb73175a52454288316c01 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 14:04:38 -0500 Subject: [PATCH 043/358] Attempt 9 --- bootstrap/fabric/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index dea3bb122f5..946da0ccace 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -3,7 +3,7 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java' - id "com.jfrog.artifactory" + id "com.jfrog.artifactory" version '4.18.3' } apply plugin: 'com.github.johnrengelman.shadow' From fad2fc149cfe5069480f02589f8fe3bc3441f71c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Jan 2021 14:26:53 -0500 Subject: [PATCH 044/358] Publish artifacts? Attempt 1 --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 2c82f17ad15..2a2436547ec 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -40,7 +40,7 @@ pipeline { tool: 'Gradle 6', rootDir: "", buildFile: 'build.gradle', - tasks: 'clean artifactoryPublish', + tasks: 'clean build artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) From fd151d4e3a51aaf32e3a0470cf585a2be8b3af71 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 9 Jan 2021 11:41:37 -0500 Subject: [PATCH 045/358] Attempt 2 of trying to get a build out of this --- bootstrap/fabric/Jenkinsfile | 2 +- bootstrap/fabric/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 2a2436547ec..a027e87f0fb 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -36,7 +36,7 @@ pipeline { serverId: "opencollab-artifactory", ) rtGradleRun ( - usesPlugin: true, + usesPlugin: false, tool: 'Gradle 6', rootDir: "", buildFile: 'build.gradle', diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 946da0ccace..8de36084e7f 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -3,7 +3,7 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java' - id "com.jfrog.artifactory" version '4.18.3' + //id "com.jfrog.artifactory" version '4.18.3' } apply plugin: 'com.github.johnrengelman.shadow' From a5e0cab48a5ffcabeb7dba18f00e9f62419764de Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 9 Jan 2021 11:53:09 -0500 Subject: [PATCH 046/358] Clean up :) --- bootstrap/fabric/Jenkinsfile | 2 +- bootstrap/fabric/build.gradle | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index a027e87f0fb..f0a92dafacc 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -40,7 +40,7 @@ pipeline { tool: 'Gradle 6', rootDir: "", buildFile: 'build.gradle', - tasks: 'clean build artifactoryPublish', + tasks: 'build artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 8de36084e7f..0f8f35cbccb 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -3,7 +3,6 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java' - //id "com.jfrog.artifactory" version '4.18.3' } apply plugin: 'com.github.johnrengelman.shadow' From 5fcf48e1c63387439394e07ccb452963d599c5c9 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 14 Jan 2021 00:07:23 +0000 Subject: [PATCH 047/358] Add skippable discord notifications to the build --- bootstrap/fabric/Jenkinsfile | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index f0a92dafacc..9ad33223941 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -4,9 +4,15 @@ pipeline { gradle 'Gradle 6' jdk 'Java 8' } + + parameters{ + booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') + } + options { buildDiscarder(logRotator(artifactNumToKeepStr: '20')) } + stages { stage ('Build') { steps { @@ -85,8 +91,12 @@ pipeline { env.changes = message } deleteDir() - withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + script { + if(!params.SKIP_DISCORD) { + withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + } + } } } } From 49f02081e0c98bb473367366eed2895a8dbf4979 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 15 Jan 2021 14:27:55 -0500 Subject: [PATCH 048/358] Resolve https://github.com/CardboardPowered/cardboard/issues/139 --- bootstrap/fabric/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0f8f35cbccb..751a5da4fd2 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -85,6 +85,10 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") + dependencies { + // https://github.com/CardboardPowered/cardboard/issues/139 + exclude(dependency('org.objectweb:asm:*')) + } } jar { From 24da65dcb5e588a23f2ec8487ef5e2e83a84b5c7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 15 Jan 2021 19:42:47 -0500 Subject: [PATCH 049/358] Apparently exclusion isn't working. Temporary fix --- bootstrap/fabric/build.gradle | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 751a5da4fd2..72f561c7d98 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -85,10 +85,8 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") - dependencies { - // https://github.com/CardboardPowered/cardboard/issues/139 - exclude(dependency('org.objectweb:asm:*')) - } + relocate("org.objectweb.asm", "org.geysermc.relocate.asm") + relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139 } jar { From c3569093b637ac539320a65fcf0e2cf67f7bedc2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 21 Jan 2021 14:57:06 -0500 Subject: [PATCH 050/358] Update command handling --- .../platform/fabric/GeyserFabricMod.java | 5 +-- .../fabric/command/FabricCommandSender.java | 15 +++------ .../command/GeyserFabricCommandExecutor.java | 31 ++++++++++++------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 4f1eed1c9fb..32d3830173e 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -149,11 +149,12 @@ public void startGeyser(MinecraftServer server) { // Start command building // Set just "geyser" as the help command LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") - .executes(new GeyserFabricCommandExecutor(connector, "help", !playerCommands.contains("help"))); + .executes(new GeyserFabricCommandExecutor(connector, connector.getCommandManager().getCommands().get("help"), + !playerCommands.contains("help"))); for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { // Register all subcommands as valid builder.then(net.minecraft.server.command.CommandManager.literal( - command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getKey(), + command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getValue(), !playerCommands.contains(command.getKey())))); } server.getCommandManager().getDispatcher().register(builder); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index 3ec5481438c..d49a07625fa 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -25,8 +25,8 @@ package org.geysermc.platform.fabric.command; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.LiteralText; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; @@ -47,20 +47,15 @@ public String getName() { @Override public void sendMessage(String message) { - try { - source.getPlayer().sendMessage(new LiteralText(message), false); - } catch (CommandSyntaxException e) { // why + if (source.getEntity() instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) source.getEntity()).sendMessage(new LiteralText(message), false); + } else { GeyserConnector.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); } } @Override public boolean isConsole() { - try { - source.getPlayer(); - return false; - } catch (CommandSyntaxException e) { - return true; - } + return !(source.getEntity() instanceof ServerPlayerEntity); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index 706ef950265..a8469364f97 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -29,22 +29,24 @@ import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.command.ServerCommandSource; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandExecutor; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.GeyserFabricMod; -public class GeyserFabricCommandExecutor implements Command { +public class GeyserFabricCommandExecutor extends CommandExecutor implements Command { - private final String commandName; - private final GeyserConnector connector; + private final GeyserCommand command; /** * Whether the command requires an OP permission level of 2 or greater */ private final boolean requiresPermission; - public GeyserFabricCommandExecutor(GeyserConnector connector, String commandName, boolean requiresPermission) { - this.commandName = commandName; - this.connector = connector; + public GeyserFabricCommandExecutor(GeyserConnector connector, GeyserCommand command, boolean requiresPermission) { + super(connector); + this.command = command; this.requiresPermission = requiresPermission; } @@ -56,14 +58,19 @@ public int run(CommandContext context) { sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); return 0; } - if (this.commandName.equals("reload")) { + if (this.command.getName().equals("reload")) { GeyserFabricMod.getInstance().setReloading(true); } - getCommand(commandName).execute(sender, new String[0]); - return 0; - } - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); + GeyserSession session = null; + if (command.isBedrockOnly()) { + session = getGeyserSession(sender); + if (session == null) { + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + return 0; + } + } + command.execute(session, sender, new String[0]); + return 0; } } From 2020881799a7185d3f42c8de7bc8f5ede7ae4dfc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:17:06 -0500 Subject: [PATCH 051/358] Add support for new metric --- .../java/org/geysermc/platform/fabric/GeyserFabricMod.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 32d3830173e..5e156cfec95 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -48,6 +48,7 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; @@ -201,6 +202,11 @@ public BootstrapDumpInfo getDumpInfo() { return new GeyserFabricDumpInfo(server); } + @Override + public String getMinecraftServerVersion() { + return this.server.getVersion(); + } + private File fileOrCopiedFromResource(File file, String name) throws IOException { if (!file.exists()) { //noinspection ResultOfMethodCallIgnored From 31a7b798f1eed7d129566b936a3fe7faf4f22837 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 5 Feb 2021 12:29:15 -0500 Subject: [PATCH 052/358] Remove Lombok --- bootstrap/fabric/build.gradle | 3 --- .../platform/fabric/GeyserFabricDumpInfo.java | 13 ++++++++-- .../platform/fabric/GeyserFabricMod.java | 12 ++++++--- .../fabric/GeyserFabricPermissions.java | 5 ++-- .../org/geysermc/platform/fabric/ModInfo.java | 25 ++++++++++++++----- .../mixin/client/IntegratedServerMixin.java | 1 - .../server/MinecraftDedicatedServerMixin.java | 4 +-- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 72f561c7d98..0e8c2d181b4 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -29,9 +29,6 @@ dependencies { implementation 'org.geysermc:connector:1.2.0-SNAPSHOT' shadow 'org.geysermc:connector:1.2.0-SNAPSHOT' - - compileOnly 'org.projectlombok:lombok:1.18.4' - annotationProcessor 'org.projectlombok:lombok:1.18.4' } repositories { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java index 1b9d68a9ce7..f6a7bf9fa32 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -25,7 +25,6 @@ package org.geysermc.platform.fabric; -import lombok.Getter; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.server.MinecraftServer; @@ -35,7 +34,6 @@ import java.util.ArrayList; import java.util.List; -@Getter public class GeyserFabricDumpInfo extends BootstrapDumpInfo { private String serverIP; @@ -57,4 +55,15 @@ public GeyserFabricDumpInfo(MinecraftServer server) { } } + public String getServerIP() { + return this.serverIP; + } + + public int getServerPort() { + return this.serverPort; + } + + public List getMods() { + return this.mods; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 5e156cfec95..3ffff545502 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -26,7 +26,6 @@ package org.geysermc.platform.fabric; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import lombok.Setter; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -48,20 +47,21 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; -import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private static GeyserFabricMod instance; - @Setter private boolean reloading; private GeyserConnector connector; @@ -207,6 +207,10 @@ public String getMinecraftServerVersion() { return this.server.getVersion(); } + public void setReloading(boolean reloading) { + this.reloading = reloading; + } + private File fileOrCopiedFromResource(File file, String name) throws IOException { if (!file.exists()) { //noinspection ResultOfMethodCallIgnored diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java index 51a7cb2e4e7..1a446bca6fd 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; /** * A class outline of the permissions.yml file @@ -35,8 +34,10 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class GeyserFabricPermissions { - @Getter @JsonProperty("commands") private String[] commands; + public String[] getCommands() { + return this.commands; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java index 15967876660..da753c44f3b 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java @@ -25,7 +25,6 @@ package org.geysermc.platform.fabric; -import lombok.Getter; import net.fabricmc.loader.api.ModContainer; import java.util.ArrayList; @@ -34,13 +33,12 @@ /** * A wrapper for Fabric mod information to be presented in a Geyser dump */ -@Getter public class ModInfo { - private String name; - private String id; - private String version; - private List authors; + private final String name; + private final String id; + private final String version; + private final List authors; public ModInfo(ModContainer mod) { this.name = mod.getMetadata().getName(); @@ -50,4 +48,19 @@ public ModInfo(ModContainer mod) { this.version = mod.getMetadata().getVersion().getFriendlyString(); } + public String getName() { + return this.name; + } + + public String getId() { + return this.id; + } + + public String getVersion() { + return this.version; + } + + public List getAuthors() { + return this.authors; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java index 5415a9d3a00..0a3f17f6885 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -32,7 +32,6 @@ import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.text.LiteralText; import net.minecraft.world.GameMode; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.GeyserServerPortGetter; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java index eec6af74f9d..f5e9a121d49 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -32,7 +32,6 @@ import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.WorldGenerationProgressListenerFactory; -import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.MinecraftDedicatedServer; import net.minecraft.util.UserCache; import net.minecraft.util.registry.DynamicRegistryManager; @@ -44,8 +43,7 @@ import java.net.Proxy; @Mixin(MinecraftDedicatedServer.class) -public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter -{ +public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { // Constructor to compile public MinecraftDedicatedServerMixin(Thread thread, DynamicRegistryManager.Impl impl, LevelStorage.Session session, SaveProperties saveProperties, ResourcePackManager resourcePackManager, Proxy proxy, DataFixer dataFixer, ServerResourceManager serverResourceManager, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { super(thread, impl, session, saveProperties, resourcePackManager, proxy, dataFixer, serverResourceManager, minecraftSessionService, gameProfileRepository, userCache, worldGenerationProgressListenerFactory); From e8b2bff951383494a2ff6cf4d2b0c282e0a239a1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 5 Feb 2021 12:54:35 -0500 Subject: [PATCH 053/358] Add platform and environment type to dump --- .../platform/fabric/GeyserFabricDumpInfo.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java index f6a7bf9fa32..26979d6da98 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.platform.fabric; +import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.server.MinecraftServer; @@ -34,14 +35,25 @@ import java.util.ArrayList; import java.util.List; +@SuppressWarnings("unused") // The way that the dump renders makes them used public class GeyserFabricDumpInfo extends BootstrapDumpInfo { - private String serverIP; - private int serverPort; - private List mods; + private String platformVersion = null; + private final EnvType environmentType; + + private final String serverIP; + private final int serverPort; + private final List mods; public GeyserFabricDumpInfo(MinecraftServer server) { super(); + for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) { + if (modContainer.getMetadata().getId().equals("fabricloader")) { + this.platformVersion = modContainer.getMetadata().getVersion().getFriendlyString(); + break; + } + } + this.environmentType = FabricLoader.getInstance().getEnvironmentType(); if (AsteriskSerializer.showSensitive || (server.getServerIp() == null || server.getServerIp().equals("") || server.getServerIp().equals("0.0.0.0"))) { this.serverIP = server.getServerIp(); } else { @@ -55,6 +67,14 @@ public GeyserFabricDumpInfo(MinecraftServer server) { } } + public String getPlatformVersion() { + return platformVersion; + } + + public EnvType getEnvironmentType() { + return environmentType; + } + public String getServerIP() { return this.serverIP; } From 17e3895b8279dae0973b95fe8609c2d2ea2f4268 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 14 Mar 2021 13:33:53 -0400 Subject: [PATCH 054/358] Implement lectern direct access (better lecterns like Geyser-Spigot) --- .../platform/fabric/GeyserFabricMod.java | 10 ++ .../world/GeyserFabricWorldManager.java | 123 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 3ffff545502..7c87b5edeb7 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -41,12 +41,14 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; +import org.geysermc.platform.fabric.world.GeyserFabricWorldManager; import java.io.File; import java.io.FileOutputStream; @@ -73,6 +75,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; + private WorldManager geyserWorldManager; @Override public void onInitialize() { @@ -147,6 +150,8 @@ public void startGeyser(MinecraftServer server) { this.geyserCommandManager = new GeyserFabricCommandManager(connector); + this.geyserWorldManager = new GeyserFabricWorldManager(server); + // Start command building // Set just "geyser" as the help command LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") @@ -192,6 +197,11 @@ public IGeyserPingPassthrough getGeyserPingPassthrough() { return geyserPingPassthrough; } + @Override + public WorldManager getWorldManager() { + return geyserWorldManager; + } + @Override public Path getConfigFolder() { return dataFolder; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java new file mode 100644 index 00000000000..9e3afbbccd6 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 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.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 net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.LecternBlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.WritableBookItem; +import net.minecraft.item.WrittenBookItem; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; +import org.geysermc.connector.network.translators.world.GeyserWorldManager; +import org.geysermc.connector.utils.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 + PlayerEntity player = server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid()); + if (player != null) { + BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z)); + if (!(blockEntity instanceof LecternBlockEntity)) { + return; + } + + LecternBlockEntity lectern = (LecternBlockEntity) blockEntity; + 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.getCurrentPage() / 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.isValid(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.getConnector().getGeneralThreadPool().schedule(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS); + } else { + server.execute(lecternGet); + } + return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); + } +} From 5418b9e26307d8a2a915e279f92ef021eadc883c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:07:32 -0400 Subject: [PATCH 055/358] Update for Floodgate 2.0 --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- .../fabric/GeyserFabricConfiguration.java | 17 ++++++++++++++++- .../platform/fabric/GeyserFabricMod.java | 19 +++++++++++++++---- .../fabric/src/main/resources/fabric.mod.json | 2 +- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0e8c2d181b4..cb63756b24e 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.2.0-SNAPSHOT' - shadow 'org.geysermc:connector:1.2.0-SNAPSHOT' + implementation 'org.geysermc:connector:1.3.0-SNAPSHOT' + shadow 'org.geysermc:connector:1.3.0-SNAPSHOT' } repositories { diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 7fe0d5d14fd..43be1e478f7 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.16.3 yarn_mappings=1.16.3+build.28 loader_version=0.10.1+build.209 # Mod Properties -mod_version=1.0-SNAPSHOT +mod_version=1.3.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java index 9f843693a53..f3b4467608a 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java @@ -25,13 +25,28 @@ package org.geysermc.platform.fabric; +import com.fasterxml.jackson.annotation.JsonIgnore; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.geysermc.connector.FloodgateKeyLoader; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; +import java.util.Optional; public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { + @JsonIgnore + private Path floodgateKeyPath; + + public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) { + Path geyserDataFolder = geyser.getConfigFolder(); + Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); + + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); + } + @Override public Path getFloodgateKeyPath() { - return null; + return floodgateKeyPath; } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 7c87b5edeb7..cbcc452bfbe 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -30,6 +30,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import org.apache.logging.log4j.LogManager; @@ -55,10 +56,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @@ -144,6 +142,19 @@ public void startGeyser(MinecraftServer server) { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } + Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); + boolean floodgatePresent = floodgate.isPresent(); + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !floodgatePresent) { + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.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("floodgate"); + } + + geyserConfig.loadFloodgate(this, floodgate.orElse(null)); + this.connector = GeyserConnector.start(PlatformType.FABRIC, this); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 89cbd12b5d2..ceb37daf3b4 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -12,7 +12,7 @@ "repo": "https://github.com/GeyserMC/Geyser-Fabric" }, "license": "MIT", - "icon": "assets/fabric/icon.png", + "icon": "assets/geyser-fabric/icon.png", "environment": "*", "entrypoints": { "main": [ From 88cb680c3f07d79aff2bdc5dd22b2c896ebe6971 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 6 Apr 2021 00:29:00 -0400 Subject: [PATCH 056/358] Update for 1.16.220 --- bootstrap/fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0e8c2d181b4..ca7dbfc0cfa 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.2.0-SNAPSHOT' - shadow 'org.geysermc:connector:1.2.0-SNAPSHOT' + implementation 'org.geysermc:connector:1.2.1-SNAPSHOT' + shadow 'org.geysermc:connector:1.2.1-SNAPSHOT' } repositories { From d07a69be8940d14d470fb2f8f91d2b461da27b9a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 09:58:17 -0400 Subject: [PATCH 057/358] Return on config fail --- .../main/java/org/geysermc/platform/fabric/GeyserFabricMod.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 7c87b5edeb7..ac9330ddc32 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -104,6 +104,7 @@ public void onEnable() { } catch (IOException ex) { LogManager.getLogger("geyser-fabric").error(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + return; } this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); From 28fb957fd98bc53ff3cd0929c9322badfdbfb84d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 12:33:54 -0400 Subject: [PATCH 058/358] Update for 1.17-rc1 and use Java 16 --- bootstrap/fabric/Jenkinsfile | 7 +++++-- bootstrap/fabric/build.gradle | 13 ++++--------- bootstrap/fabric/gradle.properties | 8 ++++---- .../fabric/gradle/wrapper/gradle-wrapper.properties | 2 +- .../fabric/world/GeyserFabricWorldManager.java | 4 ++-- bootstrap/fabric/src/main/resources/fabric.mod.json | 4 ++-- .../src/main/resources/geyser-fabric.mixins.json | 2 +- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 9ad33223941..c688c5a2cda 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any tools { gradle 'Gradle 6' - jdk 'Java 8' + jdk 'Java 16' } parameters{ @@ -27,7 +27,10 @@ pipeline { stage ('Deploy') { when { - branch "java-1.16" + anyOf { + branch "java-1.16" + branch "java-1.17" + } } steps { diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index cb63756b24e..b3c02b2d791 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.5-SNAPSHOT' + id 'fabric-loom' version '0.8-SNAPSHOT' id 'maven-publish' id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java' @@ -8,8 +8,8 @@ plugins { apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'java' -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +sourceCompatibility = JavaVersion.VERSION_16 +targetCompatibility = JavaVersion.VERSION_16 archivesBaseName = project.archives_base_name version = project.mod_version @@ -54,14 +54,9 @@ repositories { processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } // ensure that the encoding is set to UTF-8, no matter what the system default is diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 43be1e478f7..4d3b4cf8773 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.16.3 -yarn_mappings=1.16.3+build.28 -loader_version=0.10.1+build.209 +minecraft_version=1.17-rc1 +yarn_mappings=1.17-rc1+build.5 +loader_version=0.11.3 # Mod Properties mod_version=1.3.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.23.0+build.410-1.16 +fabric_version=0.34.9+1.17 diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties index bb8b2fc26b2..0f80bbf516c 100644 --- a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties +++ b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java index 9e3afbbccd6..63f4b638bc5 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -35,7 +35,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.WritableBookItem; import net.minecraft.item.WrittenBookItem; -import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtList; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import org.geysermc.connector.network.session.GeyserSession; @@ -89,7 +89,7 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole .putString("Name", "minecraft:writable_book"); List pages = new ArrayList<>(hasBookPages ? pageCount : 1); if (hasBookPages && WritableBookItem.isValid(book.getTag())) { - ListTag listTag = book.getTag().getList("pages", 8); + NbtList listTag = book.getTag().getList("pages", 8); for (int i = 0; i < listTag.size(); i++) { String page = listTag.getString(i); diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index ceb37daf3b4..eaec51a21fd 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -23,8 +23,8 @@ "geyser-fabric.mixins.json" ], "depends": { - "fabricloader": ">=0.10.1+build.209", + "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": ">=1.16.3" + "minecraft": "1.17.x" } } diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json index 3965f978131..6081bee937c 100644 --- a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json +++ b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json @@ -1,7 +1,7 @@ { "required": true, "package": "org.geysermc.platform.fabric.mixin", - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "JAVA_16", "client": [ "client.IntegratedServerMixin" ], From d08cd542d09622095b3812dd6d65acb58acafdd0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 12:35:43 -0400 Subject: [PATCH 059/358] Use local Gradle/Gradle 7 --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index c688c5a2cda..e8821f3d6c7 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -16,7 +16,7 @@ pipeline { stages { stage ('Build') { steps { - sh 'gradle clean build --refresh-dependencies' + sh './gradlew clean build --refresh-dependencies' } post { success { From d64038d311c1ec62c7b5681ab5fe5bafc6ec5f1d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 12:42:42 -0400 Subject: [PATCH 060/358] Well that didn't commit ok --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index e8821f3d6c7..7dc757e705f 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent any tools { - gradle 'Gradle 6' + gradle 'Gradle 7' jdk 'Java 16' } From f0ba0dbf4c031dbb2accf2c558b1322edde9be2f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 17:36:25 -0400 Subject: [PATCH 061/358] ... --- bootstrap/fabric/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 7dc757e705f..24a90026a59 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -46,7 +46,7 @@ pipeline { ) rtGradleRun ( usesPlugin: false, - tool: 'Gradle 6', + tool: 'Gradle 7', rootDir: "", buildFile: 'build.gradle', tasks: 'build artifactoryPublish', From 2092a75e429fa5f6b3d49113d6f8c3fc484369a3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Jun 2021 14:02:19 -0400 Subject: [PATCH 062/358] Bump to use Geyser 1.4.0-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index b3c02b2d791..0fa9183afae 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.3.0-SNAPSHOT' - shadow 'org.geysermc:connector:1.3.0-SNAPSHOT' + implementation 'org.geysermc:connector:1.4.0-SNAPSHOT' + shadow 'org.geysermc:connector:1.4.0-SNAPSHOT' } repositories { From b08ad206cafc1e5dec3a3a585e94768445276b57 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 10 Jun 2021 10:02:59 -0400 Subject: [PATCH 063/358] Bump version to 1.4.0-SNAPSHOT --- bootstrap/fabric/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 4d3b4cf8773..69901798985 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.17-rc1 yarn_mappings=1.17-rc1+build.5 loader_version=0.11.3 # Mod Properties -mod_version=1.3.0-SNAPSHOT +mod_version=1.4.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From fa8ddde9b454df6dddc5313e05076190291a43f7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 2 Jul 2021 14:50:37 +0100 Subject: [PATCH 064/358] Update build badge --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index d50255b1fef..fee65e3e142 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -2,7 +2,7 @@ [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) -Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.16/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) +Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) From 2c1fbc544a02cd0834819807157d3b7ab26808cf Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 2 Jul 2021 14:52:45 +0100 Subject: [PATCH 065/358] Fix download link --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index fee65e3e142..26cb55da7af 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -2,7 +2,7 @@ [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) -Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/lastSuccessfulBuild/artifact/build/libs/Geyser-Fabric-1.0-SNAPSHOT.jar) +Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/java-1.17/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) From c220e5428c409e1bbedf242d0e48011026f7699a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 5 Jul 2021 22:28:41 -0400 Subject: [PATCH 066/358] Relocate Google libraries --- bootstrap/fabric/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0fa9183afae..cc11f556f99 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -79,6 +79,7 @@ shadowJar { relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") relocate("org.objectweb.asm", "org.geysermc.relocate.asm") relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139 + relocate("com.google", "org.geysermc.relocate.google") } jar { From 7660ebb48b2d2bfb61e1bb799a936a8f4cfd1e83 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 12 Jul 2021 21:55:42 -0400 Subject: [PATCH 067/358] Update to the latest Geyser version --- bootstrap/fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index cc11f556f99..65dccb9a1ac 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.4.0-SNAPSHOT' - shadow 'org.geysermc:connector:1.4.0-SNAPSHOT' + implementation 'org.geysermc:connector:1.4.1-SNAPSHOT' + shadow 'org.geysermc:connector:1.4.1-SNAPSHOT' } repositories { From fcbd90c4d608d7731b557bfbe888aea362e9e9aa Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 16 Jul 2021 10:53:48 -0400 Subject: [PATCH 068/358] Require 1.17.1 or greater --- bootstrap/fabric/gradle.properties | 8 ++++---- .../fabric/world/GeyserFabricWorldManager.java | 7 +++---- .../resources/assets/geyser-fabric/icon.png | Bin 0 -> 115461 bytes .../fabric/src/main/resources/fabric.mod.json | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 69901798985..fbbf18cd319 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.17-rc1 -yarn_mappings=1.17-rc1+build.5 -loader_version=0.11.3 +minecraft_version=1.17.1 +yarn_mappings=1.17.1+build.14 +loader_version=0.11.6 # Mod Properties mod_version=1.4.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.34.9+1.17 +fabric_version=0.37.0+1.17 diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java index 63f4b638bc5..dd0d629c837 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -66,11 +66,10 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole PlayerEntity player = server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid()); if (player != null) { BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z)); - if (!(blockEntity instanceof LecternBlockEntity)) { + if (!(blockEntity instanceof LecternBlockEntity lectern)) { return; } - LecternBlockEntity lectern = (LecternBlockEntity) blockEntity; if (!lectern.hasBook()) { if (!isChunkLoad) { BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z)); @@ -88,8 +87,8 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole .putShort("Damage", (short) 0) .putString("Name", "minecraft:writable_book"); List pages = new ArrayList<>(hasBookPages ? pageCount : 1); - if (hasBookPages && WritableBookItem.isValid(book.getTag())) { - NbtList listTag = book.getTag().getList("pages", 8); + if (hasBookPages && WritableBookItem.isValid(book.getNbt())) { + NbtList listTag = book.getNbt().getList("pages", 8); for (int i = 0; i < listTag.size(); i++) { String page = listTag.getString(i); 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 0000000000000000000000000000000000000000..4e6a38a787f0ed02a93b2580b5be0e838ffefea3 GIT binary patch literal 115461 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�? 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 literal 0 HcmV?d00001 diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index eaec51a21fd..baa4888a566 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": "1.17.x" + "minecraft": ">=1.17.1" } } From 38539c214866970e107813b3091d80c1557d2aa5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Jul 2021 09:37:51 -0400 Subject: [PATCH 069/358] Exclude Google libraries from the built jar --- bootstrap/fabric/build.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 65dccb9a1ac..fa38820b457 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -28,7 +28,11 @@ dependencies { // You may need to force-disable transitiveness on them. implementation 'org.geysermc:connector:1.4.1-SNAPSHOT' - shadow 'org.geysermc:connector:1.4.1-SNAPSHOT' + shadow('org.geysermc:connector:1.4.1-SNAPSHOT') { + exclude group: 'com.google.guava', module: "guava" + exclude group: 'com.google.code.gson', module: "gson" + exclude group: 'org.slf4j' + } } repositories { @@ -79,7 +83,6 @@ shadowJar { relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") relocate("org.objectweb.asm", "org.geysermc.relocate.asm") relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139 - relocate("com.google", "org.geysermc.relocate.google") } jar { From 7f21a68d8d54e9573098fc9f35f8fcc6bc139145 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 28 Jul 2021 19:53:08 -0400 Subject: [PATCH 070/358] Auth type refactor in internal config (#26) --- bootstrap/fabric/README.md | 2 +- .../java/org/geysermc/platform/fabric/GeyserFabricMod.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index 26cb55da7af..e5129111a29 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -1,6 +1,6 @@ # Geyser-Fabric -[![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) +[![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/) Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/java-1.17/) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 412ae860c91..0ff2a012366 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -40,6 +40,7 @@ import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.network.translators.world.WorldManager; @@ -145,13 +146,13 @@ public void startGeyser(MinecraftServer server) { Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); boolean floodgatePresent = floodgate.isPresent(); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !floodgatePresent) { + if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && !floodgatePresent) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.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("floodgate"); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this, floodgate.orElse(null)); From fda17077a067d49220f0607eecb69e5a6a785d81 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 30 Aug 2021 14:27:13 -0400 Subject: [PATCH 071/358] Bump Geyser version to 1.4.2-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index fa38820b457..ef27515b95a 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.4.1-SNAPSHOT' - shadow('org.geysermc:connector:1.4.1-SNAPSHOT') { + implementation 'org.geysermc:connector:1.4.2-SNAPSHOT' + shadow('org.geysermc:connector:1.4.2-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index fbbf18cd319..592886b186a 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.17.1 yarn_mappings=1.17.1+build.14 loader_version=0.11.6 # Mod Properties -mod_version=1.4.0-SNAPSHOT +mod_version=1.4.2-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From cef3f5f1bdab1758620d0e753ff3b45140a8a4f3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 22 Sep 2021 13:51:06 -0400 Subject: [PATCH 072/358] Update to 1.4.3-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index ef27515b95a..0a3893799ae 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.4.2-SNAPSHOT' - shadow('org.geysermc:connector:1.4.2-SNAPSHOT') { + implementation 'org.geysermc:connector:1.4.3-SNAPSHOT' + shadow('org.geysermc:connector:1.4.3-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 592886b186a..f33e6ade19b 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.17.1 yarn_mappings=1.17.1+build.14 loader_version=0.11.6 # Mod Properties -mod_version=1.4.2-SNAPSHOT +mod_version=1.4.3-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From 6cc092cc8e9759b7643bb6b16ffabbfc5d6cd3a4 Mon Sep 17 00:00:00 2001 From: byquanton <32410361+byquanton@users.noreply.github.com> Date: Sat, 9 Oct 2021 18:47:10 +0000 Subject: [PATCH 073/358] Fixed loadFloodgate method (#31) Upstream Commit 7cd3eb99ef2e44e8ac2cc4741df2d3ca9ced5946 broke it --- .../geysermc/platform/fabric/GeyserFabricConfiguration.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java index f3b4467608a..e0ee61aa5ff 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java @@ -32,7 +32,6 @@ import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -import java.util.Optional; public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { @JsonIgnore @@ -40,9 +39,9 @@ public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) { Path geyserDataFolder = geyser.getConfigFolder(); - Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); + Path floodgateDataFolder = floodgate != null ? FabricLoader.getInstance().getConfigDir().resolve("floodgate") : null; - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); } @Override From 29fa4a9443886b1e2c00e3355326f106224d5490 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:59:42 -0400 Subject: [PATCH 074/358] Relocate Jackson dependency --- bootstrap/fabric/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0a3893799ae..002cc11174b 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -83,6 +83,7 @@ shadowJar { relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") 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") } jar { From 3f88f20272ee0a663f77d9e3be8614771045f328 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 31 Oct 2021 19:46:51 -0400 Subject: [PATCH 075/358] Refactor perms and upstream (#35) * refactor permissions * upstream * remove shutdown from the command list * make FabricWorldManager#getPlayer() private --- .../platform/fabric/GeyserFabricMod.java | 28 ++++++++++----- .../fabric/GeyserFabricPermissions.java | 7 ++++ .../fabric/command/FabricCommandSender.java | 15 ++++++++ .../command/GeyserFabricCommandExecutor.java | 35 ++++++++++++++----- .../world/GeyserFabricWorldManager.java | 21 ++++++++++- .../fabric/src/main/resources/permissions.yml | 6 ++-- 6 files changed, 93 insertions(+), 19 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 0ff2a012366..339c5a9e9f7 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -67,9 +67,14 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private GeyserConnector connector; private Path dataFolder; - private List playerCommands; private MinecraftServer server; + /** + * Commands that don't require any permission level to ran + */ + private List playerCommands; + private final List commandExecutors = new ArrayList<>(); + private GeyserFabricCommandManager geyserCommandManager; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; @@ -167,14 +172,17 @@ public void startGeyser(MinecraftServer server) { // Start command building // Set just "geyser" as the help command - LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser") - .executes(new GeyserFabricCommandExecutor(connector, connector.getCommandManager().getCommands().get("help"), - !playerCommands.contains("help"))); + GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(connector, + connector.getCommandManager().getCommands().get("help"), !playerCommands.contains("help")); + commandExecutors.add(helpExecutor); + LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser").executes(helpExecutor); + + // Register all subcommands as valid for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { - // Register all subcommands as valid - builder.then(net.minecraft.server.command.CommandManager.literal( - command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getValue(), - !playerCommands.contains(command.getKey())))); + GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, command.getValue(), + !playerCommands.contains(command.getKey())); + commandExecutors.add(executor); + builder.then(net.minecraft.server.command.CommandManager.literal(command.getKey()).executes(executor)); } server.getCommandManager().getDispatcher().register(builder); } @@ -258,6 +266,10 @@ private File fileOrCopiedFromResource(File file, String name) throws IOException return file; } + public List getCommandExecutors() { + return commandExecutors; + } + public static GeyserFabricMod getInstance() { return instance; } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java index 1a446bca6fd..fc08b052aba 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java @@ -25,6 +25,7 @@ package org.geysermc.platform.fabric; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -34,6 +35,12 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class GeyserFabricPermissions { + /** + * The minimum permission level a command source must have in order for it to run commands that are restricted + */ + @JsonIgnore + public static final int RESTRICTED_MIN_LEVEL = 2; + @JsonProperty("commands") private String[] commands; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index d49a07625fa..1dbec04923f 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -31,6 +31,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.ChatColor; +import org.geysermc.platform.fabric.GeyserFabricMod; public class FabricCommandSender implements CommandSender { @@ -58,4 +59,18 @@ public void sendMessage(String message) { public boolean isConsole() { return !(source.getEntity() instanceof ServerPlayerEntity); } + + @Override + public boolean hasPermission(String s) { + // Mostly copied from fabric's world manager since the method there takes a GeyserSession + + // Workaround for our commands because fabric doesn't have native permissions + for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { + if (executor.getCommand().getPermission().equals(s)) { + return executor.canRun(source); + } + } + + return false; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index a8469364f97..bd2cdcad209 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -35,6 +35,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.fabric.GeyserFabricMod; +import org.geysermc.platform.fabric.GeyserFabricPermissions; public class GeyserFabricCommandExecutor extends CommandExecutor implements Command { @@ -50,11 +51,22 @@ public GeyserFabricCommandExecutor(GeyserConnector connector, GeyserCommand comm this.requiresPermission = requiresPermission; } + /** + * Determine whether or not a command source is allowed to run a given executor. + * + * @param source The command source attempting to run the command + * @return True if the command source is allowed to + */ + public boolean canRun(ServerCommandSource source) { + return !requiresPermission() || source.hasPermissionLevel(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL); + } + @Override public int run(CommandContext context) { ServerCommandSource source = (ServerCommandSource) context.getSource(); FabricCommandSender sender = new FabricCommandSender(source); - if (requiresPermission && !source.hasPermissionLevel(2)) { + GeyserSession session = getGeyserSession(sender); + if (!canRun(source)) { sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); return 0; } @@ -62,15 +74,22 @@ public int run(CommandContext context) { GeyserFabricMod.getInstance().setReloading(true); } - GeyserSession session = null; - if (command.isBedrockOnly()) { - session = getGeyserSession(sender); - if (session == null) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); - return 0; - } + if (command.isBedrockOnly() && session == null) { + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + return 0; } command.execute(session, sender, new String[0]); return 0; } + + public GeyserCommand getCommand() { + return command; + } + + /** + * Returns whether the command requires permission level of {@link GeyserFabricPermissions#RESTRICTED_MIN_LEVEL} or higher to be ran + */ + public boolean requiresPermission() { + return requiresPermission; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java index dd0d629c837..412266bb4e4 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -42,6 +42,8 @@ import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.platform.fabric.GeyserFabricMod; +import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import java.util.ArrayList; import java.util.List; @@ -63,7 +65,7 @@ public boolean shouldExpectLecternHandled() { public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { Runnable lecternGet = () -> { // Mostly a reimplementation of Spigot lectern support - PlayerEntity player = server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid()); + PlayerEntity player = getPlayer(session); if (player != null) { BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z)); if (!(blockEntity instanceof LecternBlockEntity lectern)) { @@ -119,4 +121,21 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole } return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); } + + @Override + public boolean hasPermission(GeyserSession session, String permission) { + + // Workaround for our commands because fabric doesn't have native permissions + for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { + if (executor.getCommand().getPermission().equals(permission)) { + return executor.canRun(getPlayer(session).getCommandSource()); + } + } + + return false; + } + + private PlayerEntity getPlayer(GeyserSession session) { + return server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid()); + } } diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml index a51d3169a99..8a995f8dc58 100644 --- a/bootstrap/fabric/src/main/resources/permissions.yml +++ b/bootstrap/fabric/src/main/resources/permissions.yml @@ -1,10 +1,12 @@ # Uncomment any commands that you wish to be run by clients # Commented commands require an OP permission of 2 or greater commands: -# - dump - help + - advancements + - statistics + - settings - offhand # - list # - reload -# - shutdown # - version +# - dump \ No newline at end of file From 4f44fa83582307507e889fcd7c75e3024e5451bd Mon Sep 17 00:00:00 2001 From: Akashii Kun <44606942+AkashiiKun@users.noreply.github.com> Date: Thu, 2 Dec 2021 16:08:25 +0000 Subject: [PATCH 076/358] Java-1.18 (WIP) (#43) --- bootstrap/fabric/.gitignore | 4 +-- bootstrap/fabric/build.gradle | 12 +++---- bootstrap/fabric/gradle.properties | 8 ++--- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../fabric/GeyserFabricConfiguration.java | 4 +-- .../platform/fabric/GeyserFabricDumpInfo.java | 4 +-- .../platform/fabric/GeyserFabricLogger.java | 2 +- .../platform/fabric/GeyserFabricMod.java | 34 +++++++++---------- .../fabric/command/FabricCommandSender.java | 10 +++--- .../command/GeyserFabricCommandExecutor.java | 18 +++++----- .../command/GeyserFabricCommandManager.java | 6 ++-- .../mixin/client/IntegratedServerMixin.java | 6 ++-- .../world/GeyserFabricWorldManager.java | 10 +++--- 13 files changed, 60 insertions(+), 60 deletions(-) diff --git a/bootstrap/fabric/.gitignore b/bootstrap/fabric/.gitignore index 4215ddd837c..0a48ed37377 100644 --- a/bootstrap/fabric/.gitignore +++ b/bootstrap/fabric/.gitignore @@ -15,6 +15,7 @@ local.properties .loadpath .recommenders + # External tool builders .externalToolBuilders/ @@ -134,6 +135,7 @@ hs_err_pid* # Gradle .idea/**/gradle.xml .idea/**/libraries +/.gradle/ # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, @@ -235,5 +237,3 @@ nbdist/ # End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode -### Geyser ### -run/ diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 002cc11174b..0e3331f985f 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -1,15 +1,15 @@ plugins { - id 'fabric-loom' version '0.8-SNAPSHOT' + id 'fabric-loom' version '0.10-SNAPSHOT' id 'maven-publish' - id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'com.github.johnrengelman.shadow' version '7.0.0' id 'java' } apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'java' -sourceCompatibility = JavaVersion.VERSION_16 -targetCompatibility = JavaVersion.VERSION_16 +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 archivesBaseName = project.archives_base_name version = project.mod_version @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:connector:1.4.3-SNAPSHOT' - shadow('org.geysermc:connector:1.4.3-SNAPSHOT') { + implementation 'org.geysermc:core:2.0.0-SNAPSHOT' + shadow('org.geysermc:core:2.0.0-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index f33e6ade19b..6f3a0e0595e 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.17.1 -yarn_mappings=1.17.1+build.14 -loader_version=0.11.6 +minecraft_version=1.18 +yarn_mappings=1.18+build.1 +loader_version=0.12.8 # Mod Properties mod_version=1.4.3-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.37.0+1.17 +fabric_version=0.43.1+1.18 diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties index 0f80bbf516c..e750102e092 100644 --- a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties +++ b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java index e0ee61aa5ff..b4970787167 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java @@ -28,8 +28,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import org.geysermc.connector.FloodgateKeyLoader; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.FloodgateKeyLoader; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java index 26979d6da98..e3997da51cc 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java @@ -29,8 +29,8 @@ import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.server.MinecraftServer; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; import java.util.ArrayList; import java.util.List; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java index 40e18e43b33..9c85a21f2c5 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java @@ -27,7 +27,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.geysermc.connector.GeyserLogger; +import org.geysermc.geyser.GeyserLogger; public class GeyserFabricLogger implements GeyserLogger { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 339c5a9e9f7..904d13d4042 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -35,19 +35,19 @@ import net.minecraft.server.command.ServerCommandSource; import org.apache.logging.log4j.LogManager; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.network.translators.world.WorldManager; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.util.FileUtils; import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; import org.geysermc.platform.fabric.world.GeyserFabricWorldManager; @@ -65,7 +65,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private boolean reloading; - private GeyserConnector connector; + private GeyserImpl connector; private Path dataFolder; private MinecraftServer server; @@ -106,7 +106,7 @@ public void onEnable() { File permissionsFile = fileOrCopiedFromResource(dataFolder.resolve("permissions.yml").toFile(), "permissions.yml"); this.playerCommands = Arrays.asList(FileUtils.loadConfig(permissionsFile, GeyserFabricPermissions.class).getCommands()); } catch (IOException ex) { - LogManager.getLogger("geyser-fabric").error(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); + LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); return; } @@ -152,7 +152,7 @@ public void startGeyser(MinecraftServer server) { Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); boolean floodgatePresent = floodgate.isPresent(); if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && !floodgatePresent) { - geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + 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 @@ -162,7 +162,7 @@ public void startGeyser(MinecraftServer server) { geyserConfig.loadFloodgate(this, floodgate.orElse(null)); - this.connector = GeyserConnector.start(PlatformType.FABRIC, this); + this.connector = GeyserImpl.start(PlatformType.FABRIC, this); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index 1dbec04923f..1fa9f55ae62 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -28,9 +28,9 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.LiteralText; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.common.ChatColor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.platform.fabric.GeyserFabricMod; public class FabricCommandSender implements CommandSender { @@ -42,7 +42,7 @@ public FabricCommandSender(ServerCommandSource source) { } @Override - public String getName() { + public String name() { return source.getName(); } @@ -51,7 +51,7 @@ public void sendMessage(String message) { if (source.getEntity() instanceof ServerPlayerEntity) { ((ServerPlayerEntity) source.getEntity()).sendMessage(new LiteralText(message), false); } else { - GeyserConnector.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); + GeyserImpl.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index bd2cdcad209..c1d8ebc29ec 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -28,12 +28,12 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.command.ServerCommandSource; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandExecutor; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandExecutor; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.GeyserFabricPermissions; @@ -45,7 +45,7 @@ public class GeyserFabricCommandExecutor extends CommandExecutor implements Comm */ private final boolean requiresPermission; - public GeyserFabricCommandExecutor(GeyserConnector connector, GeyserCommand command, boolean requiresPermission) { + public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, boolean requiresPermission) { super(connector); this.command = command; this.requiresPermission = requiresPermission; @@ -67,7 +67,7 @@ public int run(CommandContext context) { FabricCommandSender sender = new FabricCommandSender(source); GeyserSession session = getGeyserSession(sender); if (!canRun(source)) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); + sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); return 0; } if (this.command.getName().equals("reload")) { @@ -75,7 +75,7 @@ public int run(CommandContext context) { } if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); return 0; } command.execute(session, sender, new String[0]); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java index d6180e7ec86..d548aa823c1 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java @@ -25,12 +25,12 @@ package org.geysermc.platform.fabric.command; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; public class GeyserFabricCommandManager extends CommandManager { - public GeyserFabricCommandManager(GeyserConnector connector) { + public GeyserFabricCommandManager(GeyserImpl connector) { super(connector); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java index 0a3f17f6885..1fc945f2866 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -32,7 +32,7 @@ import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.text.LiteralText; import net.minecraft.world.GameMode; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.GeyserServerPortGetter; import org.spongepowered.asm.mixin.Final; @@ -56,9 +56,9 @@ private void onOpenToLan(GameMode gameMode, boolean cheatsAllowed, int port, Cal // 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 - LanguageUtils.loadGeyserLocale(this.client.options.language); + GeyserLocale.loadGeyserLocale(this.client.options.language); // Give indication that Geyser is loaded - this.client.player.sendMessage(new LiteralText(LanguageUtils.getPlayerLocaleString("geyser.core.start", + this.client.player.sendMessage(new LiteralText(GeyserLocale.getPlayerLocaleString("geyser.core.start", this.client.options.language, "localhost", String.valueOf(this.lanPort))), false); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java index 412266bb4e4..5a66f6ae812 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -38,10 +38,10 @@ import net.minecraft.nbt.NbtList; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; -import org.geysermc.connector.network.translators.world.GeyserWorldManager; -import org.geysermc.connector.utils.BlockEntityUtils; +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 org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; @@ -115,7 +115,7 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole }; if (isChunkLoad) { // Hacky hacks to allow lectern loading to be delayed - session.getConnector().getGeneralThreadPool().schedule(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS); + session.scheduleInEventLoop(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS); } else { server.execute(lecternGet); } From 5089067e4d3e957db63f514e09e69cd8ce3d2777 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Dec 2021 11:10:22 -0500 Subject: [PATCH 077/358] Bump version to 2.0.0-SNAPSHOT; update Gradle wrapper --- bootstrap/fabric/.gitignore | 3 +- bootstrap/fabric/gradle.properties | 2 +- .../fabric/gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59536 bytes bootstrap/fabric/gradlew | 257 +++++++++++------- bootstrap/fabric/gradlew.bat | 21 +- 5 files changed, 159 insertions(+), 124 deletions(-) diff --git a/bootstrap/fabric/.gitignore b/bootstrap/fabric/.gitignore index 0a48ed37377..06b9cccc4f9 100644 --- a/bootstrap/fabric/.gitignore +++ b/bootstrap/fabric/.gitignore @@ -15,7 +15,6 @@ local.properties .loadpath .recommenders - # External tool builders .externalToolBuilders/ @@ -237,3 +236,5 @@ nbdist/ # End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode +### Geyser ### +run/ \ No newline at end of file diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 6f3a0e0595e..66fbe3cada8 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18 yarn_mappings=1.18+build.1 loader_version=0.12.8 # Mod Properties -mod_version=1.4.3-SNAPSHOT +mod_version=2.0.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18328 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 Y(TzS~Xdo8@nTUZrK}a{fL*HNQe=4o%4FCWD delta 17568 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +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 -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +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" +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 - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +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 @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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 @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +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 -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -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" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +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 - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + 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 - i=`expr $i + 1` + # 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 - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# 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. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +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/bootstrap/fabric/gradlew.bat b/bootstrap/fabric/gradlew.bat index a9f778a7a96..ac1b06f9382 100644 --- a/bootstrap/fabric/gradlew.bat +++ b/bootstrap/fabric/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ 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 %CMD_LINE_ARGS% +"%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 From fafdf31fa5598d9e130a1c16d8c48a0fd333ecb8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Dec 2021 11:14:12 -0500 Subject: [PATCH 078/358] Update Jenkinsfile --- bootstrap/fabric/Jenkinsfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 24a90026a59..72eb575a086 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any tools { gradle 'Gradle 7' - jdk 'Java 16' + jdk 'Java 17' } parameters{ @@ -28,8 +28,7 @@ pipeline { stage ('Deploy') { when { anyOf { - branch "java-1.16" - branch "java-1.17" + branch "java-1.18" } } From 257e04fa6faed2d519fc0c3d7e90d48e4fd085b3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Dec 2021 11:32:57 -0500 Subject: [PATCH 079/358] Comment out deploying; we shouldn't need it here --- bootstrap/fabric/Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 72eb575a086..15df47de1f4 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { jdk 'Java 17' } - parameters{ + parameters { booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') } @@ -25,7 +25,7 @@ pipeline { } } - stage ('Deploy') { + /*stage ('Deploy') { when { anyOf { branch "java-1.18" @@ -56,7 +56,7 @@ pipeline { serverId: "opencollab-artifactory" ) } - } + }*/ } post { From dd632ef4b19a52e99107eec77334efe2cac4a9f8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 3 Dec 2021 11:08:05 -0500 Subject: [PATCH 080/358] Update for Geyser usage This fixes https://github.com/GeyserMC/Floodgate-Fabric/issues/7 on the Geyser-Fabric end. Floodgate-Fabric still needs to be fixed. --- .../platform/fabric/GeyserFabricMod.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 904d13d4042..3c175896f35 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -51,6 +51,7 @@ import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; import org.geysermc.platform.fabric.world.GeyserFabricWorldManager; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; @@ -66,6 +67,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private boolean reloading; private GeyserImpl connector; + private ModContainer mod; private Path dataFolder; private MinecraftServer server; @@ -84,6 +86,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @Override public void onInitialize() { instance = this; + mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); this.onEnable(); if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { @@ -94,6 +97,8 @@ public void onInitialize() { @Override public void onEnable() { + GeyserLocale.init(this); + dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); if (!dataFolder.toFile().exists()) { //noinspection ResultOfMethodCallIgnored @@ -101,7 +106,7 @@ public void onEnable() { } try { File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); File permissionsFile = fileOrCopiedFromResource(dataFolder.resolve("permissions.yml").toFile(), "permissions.yml"); this.playerCommands = Arrays.asList(FileUtils.loadConfig(permissionsFile, GeyserFabricPermissions.class).getCommands()); @@ -238,6 +243,21 @@ public String getMinecraftServerVersion() { return this.server.getVersion(); } + @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.getPath(resource); + + try { + return path.getFileSystem() + .provider() + .newInputStream(path); + } catch (IOException e) { + return null; + } + } + public void setReloading(boolean reloading) { this.reloading = reloading; } @@ -247,7 +267,7 @@ private File fileOrCopiedFromResource(File file, String name) throws IOException //noinspection ResultOfMethodCallIgnored file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); - InputStream input = GeyserFabricMod.class.getResourceAsStream("/" + name); // resources need leading "/" prefix + InputStream input = getResource(name); byte[] bytes = new byte[input.available()]; From 91095beb521c84fd886f8332e2026dda64426243 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 28 Dec 2021 11:14:30 -0500 Subject: [PATCH 081/358] Attempt to re-enable publishing --- bootstrap/fabric/Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index 15df47de1f4..e10609a7725 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -25,7 +25,7 @@ pipeline { } } - /*stage ('Deploy') { + stage ('Deploy') { when { anyOf { branch "java-1.18" @@ -42,6 +42,8 @@ pipeline { rtGradleResolver( id: "GRADLE_RESOLVER", serverId: "opencollab-artifactory", + releaseRepo: "maven-deploy-release", + snapshotRepo: "maven-deploy-snapshot" ) rtGradleRun ( usesPlugin: false, @@ -56,7 +58,7 @@ pipeline { serverId: "opencollab-artifactory" ) } - }*/ + } } post { From 51c77fe808d93e5c938c62583e09cfbb09bacd5b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 28 Dec 2021 11:33:24 -0500 Subject: [PATCH 082/358] Remove invalid params --- bootstrap/fabric/Jenkinsfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile index e10609a7725..c3632a8ce0c 100644 --- a/bootstrap/fabric/Jenkinsfile +++ b/bootstrap/fabric/Jenkinsfile @@ -41,9 +41,7 @@ pipeline { ) rtGradleResolver( id: "GRADLE_RESOLVER", - serverId: "opencollab-artifactory", - releaseRepo: "maven-deploy-release", - snapshotRepo: "maven-deploy-snapshot" + serverId: "opencollab-artifactory" ) rtGradleRun ( usesPlugin: false, From 8627787ea991f66b368e93648c79abf051820fe9 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:45:26 +0100 Subject: [PATCH 083/358] Added basic extension loading --- .../java/org/geysermc/geyser/GeyserImpl.java | 5 + .../geysermc/geyser/extension/Extension.java | 50 ++++ .../extension/ExtensionClassLoader.java | 97 +++++++ .../extension/ExtensionDescription.java | 94 +++++++ .../geyser/extension/ExtensionLoader.java | 173 +++++++++++++ .../geyser/extension/ExtensionManager.java | 243 ++++++++++++++++++ .../geyser/extension/GeyserExtension.java | 176 +++++++++++++ .../InvalidDescriptionException.java | 40 +++ .../exception/InvalidExtensionException.java | 40 +++ 9 files changed, 918 insertions(+) create mode 100644 core/src/main/java/org/geysermc/geyser/extension/Extension.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index eaadd15fa36..abfc7bb830d 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -52,6 +52,7 @@ import org.geysermc.geyser.command.CommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.extension.ExtensionManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.ConnectorServerEventHandler; import org.geysermc.geyser.pack.ResourcePack; @@ -161,6 +162,8 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { ResourcePack.loadPacks(); + ExtensionManager.init(); + if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { @@ -444,6 +447,8 @@ public void shutdown() { newsHandler.shutdown(); this.getCommandManager().getCommands().clear(); + ExtensionManager.getExtensionManager().disableExtensions(); + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } diff --git a/core/src/main/java/org/geysermc/geyser/extension/Extension.java b/core/src/main/java/org/geysermc/geyser/extension/Extension.java new file mode 100644 index 00000000000..a911e56ad5c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/Extension.java @@ -0,0 +1,50 @@ +/* + * 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.GeyserImpl; +import java.io.File; +import java.io.InputStream; + +public interface Extension { + void onLoad(); + void onEnable(); + void onDisable(); + + boolean isEnabled(); + boolean isDisabled(); + + File getDataFolder(); + ExtensionDescription getDescription(); + String getName(); + + InputStream getResource(String filename); + void saveResource(String filename, boolean replace); + + GeyserImpl getGeyser(); + ClassLoader getClassLoader(); + ExtensionLoader getExtensionLoader(); +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java new file mode 100644 index 00000000000..3261adc013f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java @@ -0,0 +1,97 @@ +/* + * 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.extension.exception.InvalidExtensionException; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class ExtensionClassLoader extends URLClassLoader { + private ExtensionLoader loader; + private Map classes = new HashMap<>(); + public GeyserExtension extension; + + public ExtensionClassLoader(ExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { + super(new URL[] { file.toURI().toURL() }, parent); + this.loader = loader; + + try { + Class jarClass; + try { + jarClass = Class.forName(description.getMain(), true, this); + } catch (ClassNotFoundException ex) { + throw new InvalidExtensionException("Class " + description.getMain() + " not found, extension cannot be loaded", ex); + } + + Class extensionClass; + try { + extensionClass = jarClass.asSubclass(GeyserExtension.class); + } catch (ClassCastException ex) { + throw new InvalidExtensionException("Main class " + description.getMain() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); + } + + extension = extensionClass.newInstance(); + } catch (IllegalAccessException 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("org.geysermc.connector.") || name.startsWith("net.minecraft.")) { + throw new ClassNotFoundException(name); + } + Class result = classes.get(name); + if(result == null) { + if(checkGlobal) { + result = loader.getClassByName(name); + } + if(result == null) { + result = super.findClass(name); + if (result != null) { + loader.setClass(name, result); + } + } + classes.put(name, result); + } + return result; + } + + Set getClasses() { + return classes.keySet(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java new file mode 100644 index 00000000000..9f714dc11a1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.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.extension; + +import org.geysermc.geyser.extension.exception.InvalidDescriptionException; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.util.*; + +public class ExtensionDescription { + private String name; + private String main; + private List api; + private String version; + private final List authors = new ArrayList<>(); + + public ExtensionDescription(String yamlString) throws InvalidDescriptionException { + DumperOptions dumperOptions = new DumperOptions(); + dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml yaml = new Yaml(dumperOptions); + this.loadMap(yaml.loadAs(yamlString, LinkedHashMap.class)); + } + + private void loadMap(Map yamlMap) throws InvalidDescriptionException { + this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); + if (this.name.equals("")) { + throw new InvalidDescriptionException("Invalid extension name"); + } + this.name = this.name.replace(" ", "_"); + this.version = String.valueOf(yamlMap.get("version")); + this.main = (String) yamlMap.get("main"); + + Object api = yamlMap.get("api"); + if (api instanceof List) { + this.api = (List) api; + } else { + List list = new ArrayList<>(); + list.add((String) api); + this.api = list; + } + + if (yamlMap.containsKey("author")) { + this.authors.add((String) yamlMap.get("author")); + } + + if (yamlMap.containsKey("authors")) { + this.authors.addAll((Collection) yamlMap.get("authors")); + } + } + + public String getName() { + return this.name; + } + + public String getMain() { + return this.main; + } + + public List getAPIVersions() { + return api; + } + + public String getVersion() { + return this.version; + } + + public List getAuthors() { + return this.authors; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java new file mode 100644 index 00000000000..15795c2c5b7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java @@ -0,0 +1,173 @@ +/* + * 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.GeyserImpl; +import org.geysermc.geyser.extension.exception.InvalidDescriptionException; +import org.geysermc.geyser.extension.exception.InvalidExtensionException; +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +public class ExtensionLoader { + private final Map classes = new HashMap<>(); + private final Map classLoaders = new HashMap<>(); + + public GeyserExtension loadExtension(File file) throws InvalidExtensionException { + if (file == null) { + throw new InvalidExtensionException("File is null"); + } + + if (!file.exists()) { + throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist"); + } + + final ExtensionDescription description; + try { + description = getExtensionDescription(file); + } catch (InvalidDescriptionException e) { + throw new InvalidExtensionException(e); + } + + final File parentFile = file.getParentFile(); + final File dataFolder = new File(parentFile, description.getName()); + if (dataFolder.exists() && !dataFolder.isDirectory()) { + throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.getName() + "!"); + } + + final ExtensionClassLoader loader; + try { + loader = new ExtensionClassLoader(this, getClass().getClassLoader(), description, file); + } catch (Throwable e) { + throw new InvalidExtensionException(e); + } + classLoaders.put(description.getName(), loader); + + setup(loader.extension, description, dataFolder, file); + return loader.extension; + } + + private void setup(GeyserExtension extension, ExtensionDescription description, File dataFolder, File file) { + extension.init(GeyserImpl.getInstance(), description, dataFolder, file, this); + extension.onLoad(); + } + + public ExtensionDescription getExtensionDescription(File file) throws InvalidDescriptionException { + JarFile jarFile = null; + InputStream stream = null; + + try { + jarFile = new JarFile(file); + + JarEntry descriptionEntry = jarFile.getJarEntry("extension.yml"); + if (descriptionEntry == null) { + throw new InvalidDescriptionException(new FileNotFoundException("extension.yml") + " does not exist in the jar file!"); + } + + stream = jarFile.getInputStream(descriptionEntry); + + InputStreamReader reader = new InputStreamReader(stream); + StringBuilder builder = new StringBuilder(); + String temp; + BufferedReader bufferedReader = new BufferedReader(reader); + temp = bufferedReader.readLine(); + while (temp != null) { + if (builder.length() != 0) { + builder.append("\n"); + } + builder.append(temp); + temp = bufferedReader.readLine(); + } + + return new ExtensionDescription(builder.toString()); + } catch (IOException e) { + throw new InvalidDescriptionException(e); + } finally { + if (jarFile != null) { + try { + jarFile.close(); + } catch (IOException e) { + } + } + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + } + + public Pattern[] getExtensionFilters() { + return new Pattern[] { Pattern.compile("^.+\\.jar$") }; + } + + public Class getClassByName(final String name) throws ClassNotFoundException{ + Class clazz = classes.get(name); + try { + for(ExtensionClassLoader loader : classLoaders.values()) { + try { + clazz = loader.findClass(name,false); + } catch(NullPointerException e) { + } + } + return clazz; + } catch(NullPointerException s) { + return null; + } + } + + public void setClass(String name, final Class clazz) { + if(!classes.containsKey(name)) { + classes.put(name,clazz); + } + } + + protected void removeClass(String name) { + Class clazz = classes.remove(name); + } + + public void enableExtension(Extension extension) { + if (extension instanceof GeyserExtension) { + if(!extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.getDescription().getName()); + ((GeyserExtension) extension).setEnabled(true); + } + } + } + + public void disableExtension(Extension extension) { + if (extension instanceof GeyserExtension) { + if(extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.getDescription().getName()); + ((GeyserExtension) extension).setEnabled(false); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java new file mode 100644 index 00000000000..ad757d4996b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java @@ -0,0 +1,243 @@ +/* + * 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.GeyserImpl; + +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Constructor; +import java.util.*; +import java.util.regex.Pattern; + +public class ExtensionManager { + private static ExtensionManager extensionManager = null; + + protected Map extensions = new LinkedHashMap<>(); + protected Map fileAssociations = new HashMap<>(); + + public static void init() { + GeyserImpl.getInstance().getLogger().info("Loading extensions..."); + extensionManager = new ExtensionManager(); + extensionManager.registerInterface(ExtensionLoader.class); + extensionManager.loadExtensions(new File("extensions")); + GeyserImpl.getInstance().getLogger().info("Loaded " + extensionManager.extensions.size() + " extensions."); + + for (Extension extension : extensionManager.getExtensions().values()) { + if (!extension.isEnabled()) { + extensionManager.enableExtension(extension); + } + } + } + + public static ExtensionManager getExtensionManager() { + return extensionManager; + } + + public Extension getExtension(String name) { + if (this.extensions.containsKey(name)) { + return this.extensions.get(name); + } + return null; + } + + public Map getExtensions() { + return this.extensions; + } + + public void registerInterface(Class loader) { + ExtensionLoader instance; + + if (ExtensionLoader.class.isAssignableFrom(loader)) { + Constructor constructor; + + try { + constructor = loader.getConstructor(); + instance = constructor.newInstance(); + } catch (NoSuchMethodException ex) { // This should never happen + String className = loader.getName(); + + throw new IllegalArgumentException("Class " + className + " does not have a public constructor", ex); + } catch (Exception ex) { // This should never happen + throw new IllegalArgumentException("Unexpected exception " + ex.getClass().getName() + " while attempting to construct a new instance of " + loader.getName(), ex); + } + } else { + throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader"); + } + + Pattern[] patterns = instance.getExtensionFilters(); + + synchronized (this) { + for (Pattern pattern : patterns) { + fileAssociations.put(pattern, instance); + } + } + } + + public GeyserExtension loadExtension(File file, Map loaders) { + for (ExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) { + for (Pattern pattern : loader.getExtensionFilters()) { + if (pattern.matcher(file.getName()).matches()) { + try { + ExtensionDescription description = loader.getExtensionDescription(file); + if (description != null) { + GeyserExtension extension = loader.loadExtension(file); + + if (extension != null) { + this.extensions.put(extension.getDescription().getName(), extension); + + return extension; + } + } + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Could not load extension", e); + return null; + } + } + } + } + + return null; + } + + public Map loadExtensions(File dictionary) { + if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { + GeyserImpl.getInstance().getLogger().error("Cannot load extensions in a development environment, aborting extension loading"); + return new HashMap<>(); + } + if (!GeyserImpl.VERSION.contains(".")) { + GeyserImpl.getInstance().getLogger().error("Something went wrong with the Geyser version number, aborting extension loading"); + return new HashMap<>(); + } + + if (!dictionary.exists()) { + dictionary.mkdir(); + } + if (!dictionary.isDirectory()) { + return new HashMap<>(); + } + + Map extensions = new LinkedHashMap<>(); + Map loadedExtensions = new LinkedHashMap<>(); + + for (final ExtensionLoader loader : this.fileAssociations.values()) { + for (File file : dictionary.listFiles((dir, name) -> { + for (Pattern pattern : loader.getExtensionFilters()) { + if (pattern.matcher(name).matches()) { + return true; + } + } + return false; + })) { + if (file.isDirectory()) { + continue; + } + + try { + ExtensionDescription description = loader.getExtensionDescription(file); + if (description != null) { + String name = description.getName(); + + if (extensions.containsKey(name) || this.getExtension(name) != null) { + GeyserImpl.getInstance().getLogger().warning("Found duplicate extension '" + name + "', ignoring '" + file.getName() + "'"); + continue; + } + + boolean compatible = false; + + for (String version : description.getAPIVersions()) { + try { + //Check the format: majorVersion.minorVersion.patch + if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", version)) { + throw new IllegalArgumentException(); + } + } catch (NullPointerException | IllegalArgumentException e) { + GeyserImpl.getInstance().getLogger().error("Could't load extension " + name + ": Wrong API format"); + continue; + } + + String[] versionArray = version.split("\\."); + String[] apiVersion = GeyserImpl.VERSION.split("\\."); + + //Completely different API version + if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + continue; + } + + //If the extension requires new API features, being backwards compatible + if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + continue; + } + + compatible = true; + break; + } + + if (!compatible) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name +": Incompatible API version"); + } + + extensions.put(name, file); + loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations)); + } + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Couldn't load " +file.getName()+ " in folder " + dictionary + ": ", e); + } + } + } + + return loadedExtensions; + } + + public void enableExtension(Extension extension) { + if (!extension.isEnabled()) { + try { + extension.getExtensionLoader().enableExtension(extension); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Error enabling extension " + extension.getName() + ": ", e); + this.disableExtension(extension); + } + } + } + + public void disableExtension(Extension extension) { + if (extension.isEnabled()) { + try { + extension.getExtensionLoader().disableExtension(extension); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Error disabling extension " + extension.getName() + ": ", e); + } + } + } + + public void disableExtensions() { + for (Extension extension : this.getExtensions().values()) { + this.disableExtension(extension); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java new file mode 100644 index 00000000000..c6d7b6f10c4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java @@ -0,0 +1,176 @@ +/* + * 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.GeyserImpl; +import java.io.*; +import java.net.URL; +import java.net.URLConnection; + +public class GeyserExtension implements Extension { + private boolean initialized = false; + private boolean enabled = false; + private File file = null; + private File dataFolder = null; + private ClassLoader classLoader = null; + private GeyserImpl geyser = null; + private ExtensionLoader loader; + private ExtensionDescription description = null; + + @Override + public void onLoad() { + } + + @Override + public void onEnable() { + } + + @Override + public void onDisable() { + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean value) { + if (this.enabled != value) { + this.enabled = value; + if (this.enabled) { + onEnable(); + } else { + onDisable(); + } + } + } + + @Override + public boolean isDisabled() { + return !this.enabled; + } + + @Override + public File getDataFolder() { + return this.dataFolder; + } + + @Override + public ExtensionDescription getDescription() { + return this.description; + } + + @Override + public String getName() { + return this.description.getName(); + } + + public void init(GeyserImpl geyser, ExtensionDescription description, File dataFolder, File file, ExtensionLoader loader) { + if (!this.initialized) { + this.initialized = true; + this.file = file; + this.dataFolder = dataFolder; + this.classLoader = this.getClass().getClassLoader(); + this.geyser = geyser; + this.loader = loader; + this.description = description; + } + } + + @Override + public InputStream getResource(String filename) { + if (filename == null) { + throw new IllegalArgumentException("Filename cannot be null"); + } + + try { + URL url = this.classLoader.getResource(filename); + + if (url == null) { + return null; + } + + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } catch (IOException ex) { + return null; + } + } + + @Override + public void saveResource(String filename, boolean replace) { + if (filename == null || filename.equals("")) { + throw new IllegalArgumentException("ResourcePath cannot be null or empty"); + } + + filename = filename.replace('\\', '/'); + InputStream in = getResource(filename); + if (in == null) { + throw new IllegalArgumentException("The embedded resource '" + filename + "' cannot be found in " + file); + } + + File outFile = new File(dataFolder, filename); + int lastIndex = filename.lastIndexOf('/'); + File outDir = new File(dataFolder, filename.substring(0, Math.max(lastIndex, 0))); + + if (!outDir.exists()) { + outDir.mkdirs(); + } + + try { + if (!outFile.exists() || replace) { + OutputStream out = new FileOutputStream(outFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + } else { + this.geyser.getLogger().warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); + } + } catch (IOException ex) { + this.geyser.getLogger().severe("Could not save " + outFile.getName() + " to " + outFile, ex); + } + } + + @Override + public GeyserImpl getGeyser() { + return this.geyser; + } + + @Override + public ClassLoader getClassLoader() { + return this.classLoader; + } + + @Override + public ExtensionLoader getExtensionLoader() { + return this.loader; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java b/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java new file mode 100644 index 00000000000..0764dc1d1ab --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.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.extension.exception; + +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/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java b/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java new file mode 100644 index 00000000000..f29f5d280a4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.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.extension.exception; + +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); + } +} From 6757437193b7ad0c72ffc9bf47e1fb7614f459f7 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:01:36 +0100 Subject: [PATCH 084/358] Moved the extension into geyser-api --- .../geyser/api}/extension/Extension.java | 17 ++-- .../api/extension/ExtensionDescription.java | 36 ++++++++ .../geyser/api/extension/ExtensionLoader.java | 41 +++++++++ .../geyser/api/extension/ExtensionLogger.java | 90 +++++++++++++++++++ .../api}/extension/GeyserExtension.java | 41 +++++---- .../InvalidDescriptionException.java | 2 +- .../exception/InvalidExtensionException.java | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 8 +- ...r.java => GeyserExtensionClassLoader.java} | 18 ++-- ...n.java => GeyserExtensionDescription.java} | 22 +++-- ...Loader.java => GeyserExtensionLoader.java} | 51 ++++++----- .../extension/GeyserExtensionLogger.java | 88 ++++++++++++++++++ ...nager.java => GeyserExtensionManager.java} | 65 +++++++------- 13 files changed, 381 insertions(+), 100 deletions(-) rename {core/src/main/java/org/geysermc/geyser => api/geyser/src/main/java/org/geysermc/geyser/api}/extension/Extension.java (83%) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java rename {core/src/main/java/org/geysermc/geyser => api/geyser/src/main/java/org/geysermc/geyser/api}/extension/GeyserExtension.java (81%) rename {core/src/main/java/org/geysermc/geyser => api/geyser/src/main/java/org/geysermc/geyser/api}/extension/exception/InvalidDescriptionException.java (96%) rename {core/src/main/java/org/geysermc/geyser => api/geyser/src/main/java/org/geysermc/geyser/api}/extension/exception/InvalidExtensionException.java (96%) rename core/src/main/java/org/geysermc/geyser/extension/{ExtensionClassLoader.java => GeyserExtensionClassLoader.java} (80%) rename core/src/main/java/org/geysermc/geyser/extension/{ExtensionDescription.java => GeyserExtensionDescription.java} (85%) rename core/src/main/java/org/geysermc/geyser/extension/{ExtensionLoader.java => GeyserExtensionLoader.java} (74%) create mode 100644 core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java rename core/src/main/java/org/geysermc/geyser/extension/{ExtensionManager.java => GeyserExtensionManager.java} (79%) diff --git a/core/src/main/java/org/geysermc/geyser/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java similarity index 83% rename from core/src/main/java/org/geysermc/geyser/extension/Extension.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index a911e56ad5c..8a820d8ac1a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.extension; +package org.geysermc.geyser.api.extension; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.api.GeyserApiBase; import java.io.File; import java.io.InputStream; @@ -37,14 +37,15 @@ public interface Extension { boolean isEnabled(); boolean isDisabled(); - File getDataFolder(); - ExtensionDescription getDescription(); - String getName(); + File dataFolder(); + ExtensionDescription description(); + String name(); InputStream getResource(String filename); void saveResource(String filename, boolean replace); - GeyserImpl getGeyser(); - ClassLoader getClassLoader(); - ExtensionLoader getExtensionLoader(); + ClassLoader classLoader(); + ExtensionLoader extensionLoader(); + ExtensionLogger logger(); + GeyserApiBase geyserApi(); } 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..0f752e72dcb --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -0,0 +1,36 @@ +/* + * 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 java.util.*; + +public interface ExtensionDescription { + String name(); + String main(); + List ApiVersions(); + String version(); + 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..c558957eb8c --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.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.extension; + +import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; +import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; + +import java.io.File; +import java.util.regex.Pattern; + +public interface ExtensionLoader { + GeyserExtension loadExtension(File file) throws InvalidExtensionException; + ExtensionDescription extensionDescription(File file) throws InvalidDescriptionException; + Pattern[] extensionFilters(); + Class classByName(final String name) throws ClassNotFoundException; + void enableExtension(Extension extension); + void disableExtension(Extension extension); +} 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..6b5d8615360 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.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; + +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/core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java similarity index 81% rename from core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java index c6d7b6f10c4..3ed66b444ef 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.extension; +package org.geysermc.geyser.api.extension; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.api.GeyserApiBase; import java.io.*; import java.net.URL; import java.net.URLConnection; @@ -36,9 +36,10 @@ public class GeyserExtension implements Extension { private File file = null; private File dataFolder = null; private ClassLoader classLoader = null; - private GeyserImpl geyser = null; private ExtensionLoader loader; + private ExtensionLogger logger; private ExtensionDescription description = null; + private GeyserApiBase api = null; @Override public void onLoad() { @@ -74,29 +75,30 @@ public boolean isDisabled() { } @Override - public File getDataFolder() { + public File dataFolder() { return this.dataFolder; } @Override - public ExtensionDescription getDescription() { + public ExtensionDescription description() { return this.description; } @Override - public String getName() { - return this.description.getName(); + public String name() { + return this.description.name(); } - public void init(GeyserImpl geyser, ExtensionDescription description, File dataFolder, File file, ExtensionLoader loader) { + public void init(GeyserApiBase api, ExtensionLogger logger, ExtensionLoader loader, ExtensionDescription description, File dataFolder, File file) { if (!this.initialized) { this.initialized = true; this.file = file; this.dataFolder = dataFolder; this.classLoader = this.getClass().getClassLoader(); - this.geyser = geyser; this.loader = loader; + this.logger = logger; this.description = description; + this.api = api; } } @@ -152,25 +154,30 @@ public void saveResource(String filename, boolean replace) { out.close(); in.close(); } else { - this.geyser.getLogger().warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); + this.logger.warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); } } catch (IOException ex) { - this.geyser.getLogger().severe("Could not save " + outFile.getName() + " to " + outFile, ex); + this.logger.severe("Could not save " + outFile.getName() + " to " + outFile, ex); } } @Override - public GeyserImpl getGeyser() { - return this.geyser; + public ClassLoader classLoader() { + return this.classLoader; } @Override - public ClassLoader getClassLoader() { - return this.classLoader; + public ExtensionLoader extensionLoader() { + return this.loader; } @Override - public ExtensionLoader getExtensionLoader() { - return this.loader; + public ExtensionLogger logger() { + return this.logger; + } + + @Override + public GeyserApiBase geyserApi() { + return this.api; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java index 0764dc1d1ab..ca2fabad978 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidDescriptionException.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.extension.exception; +package org.geysermc.geyser.api.extension.exception; public class InvalidDescriptionException extends Exception { public InvalidDescriptionException(Throwable cause) { diff --git a/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java index f29f5d280a4..1053e6d50ab 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/exception/InvalidExtensionException.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.extension.exception; +package org.geysermc.geyser.api.extension.exception; public class InvalidExtensionException extends Exception { public InvalidExtensionException(Throwable cause) { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 82a5fd3541b..7e61b3af764 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -52,7 +52,7 @@ import org.geysermc.geyser.command.CommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; -import org.geysermc.geyser.extension.ExtensionManager; +import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.ConnectorServerEventHandler; import org.geysermc.geyser.pack.ResourcePack; @@ -155,6 +155,8 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { MessageTranslator.init(); MinecraftLocale.init(); + GeyserExtensionManager.init(); + start(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -198,8 +200,6 @@ private void start() { ResourcePack.loadPacks(); - ExtensionManager.init(); - if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { @@ -460,7 +460,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - ExtensionManager.getExtensionManager().disableExtensions(); + GeyserExtensionManager.getExtensionManager().disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java similarity index 80% rename from core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java rename to core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 3261adc013f..a1ccc063e8c 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/ExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,7 +25,9 @@ package org.geysermc.geyser.extension; -import org.geysermc.geyser.extension.exception.InvalidExtensionException; +import org.geysermc.geyser.api.extension.ExtensionDescription; +import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -34,28 +36,28 @@ import java.util.Map; import java.util.Set; -public class ExtensionClassLoader extends URLClassLoader { - private ExtensionLoader loader; +public class GeyserExtensionClassLoader extends URLClassLoader { + private GeyserExtensionLoader loader; private Map classes = new HashMap<>(); public GeyserExtension extension; - public ExtensionClassLoader(ExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { + public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { super(new URL[] { file.toURI().toURL() }, parent); this.loader = loader; try { Class jarClass; try { - jarClass = Class.forName(description.getMain(), true, this); + jarClass = Class.forName(description.main(), true, this); } catch (ClassNotFoundException ex) { - throw new InvalidExtensionException("Class " + description.getMain() + " not found, extension cannot be loaded", ex); + throw new InvalidExtensionException("Class " + description.main() + " not found, extension cannot be loaded", ex); } Class extensionClass; try { extensionClass = jarClass.asSubclass(GeyserExtension.class); } catch (ClassCastException ex) { - throw new InvalidExtensionException("Main class " + description.getMain() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); + throw new InvalidExtensionException("Main class " + description.main() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); } extension = extensionClass.newInstance(); @@ -78,7 +80,7 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo Class result = classes.get(name); if(result == null) { if(checkGlobal) { - result = loader.getClassByName(name); + result = loader.classByName(name); } if(result == null) { result = super.findClass(name); diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java similarity index 85% rename from core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java rename to core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 9f714dc11a1..ee215f2e4d1 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/ExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,20 +25,19 @@ package org.geysermc.geyser.extension; -import org.geysermc.geyser.extension.exception.InvalidDescriptionException; +import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; - import java.util.*; -public class ExtensionDescription { +public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { private String name; private String main; private List api; private String version; private final List authors = new ArrayList<>(); - public ExtensionDescription(String yamlString) throws InvalidDescriptionException { + public GeyserExtensionDescription(String yamlString) throws InvalidDescriptionException { DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(dumperOptions); @@ -72,23 +71,28 @@ private void loadMap(Map yamlMap) throws InvalidDescriptionExcep } } - public String getName() { + @Override + public String name() { return this.name; } - public String getMain() { + @Override + public String main() { return this.main; } - public List getAPIVersions() { + @Override + public List ApiVersions() { return api; } - public String getVersion() { + @Override + public String version() { return this.version; } - public List getAuthors() { + @Override + public List authors() { return this.authors; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java similarity index 74% rename from core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java rename to core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 15795c2c5b7..9b741fb2d88 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/ExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,9 +25,13 @@ package org.geysermc.geyser.extension; +import org.geysermc.api.Geyser; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.extension.exception.InvalidDescriptionException; -import org.geysermc.geyser.extension.exception.InvalidExtensionException; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; +import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import java.io.*; import java.util.HashMap; import java.util.Map; @@ -35,10 +39,11 @@ import java.util.jar.JarFile; import java.util.regex.Pattern; -public class ExtensionLoader { +public class GeyserExtensionLoader implements ExtensionLoader { private final Map classes = new HashMap<>(); - private final Map classLoaders = new HashMap<>(); + private final Map classLoaders = new HashMap<>(); + @Override public GeyserExtension loadExtension(File file) throws InvalidExtensionException { if (file == null) { throw new InvalidExtensionException("File is null"); @@ -48,37 +53,39 @@ public GeyserExtension loadExtension(File file) throws InvalidExtensionException throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist"); } - final ExtensionDescription description; + final GeyserExtensionDescription description; try { - description = getExtensionDescription(file); + description = extensionDescription(file); } catch (InvalidDescriptionException e) { throw new InvalidExtensionException(e); } final File parentFile = file.getParentFile(); - final File dataFolder = new File(parentFile, description.getName()); + final File dataFolder = new File(parentFile, description.name()); if (dataFolder.exists() && !dataFolder.isDirectory()) { - throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.getName() + "!"); + throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.name() + "!"); } - final ExtensionClassLoader loader; + final GeyserExtensionClassLoader loader; try { - loader = new ExtensionClassLoader(this, getClass().getClassLoader(), description, file); + loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, file); } catch (Throwable e) { throw new InvalidExtensionException(e); } - classLoaders.put(description.getName(), loader); + classLoaders.put(description.name(), loader); setup(loader.extension, description, dataFolder, file); return loader.extension; } - private void setup(GeyserExtension extension, ExtensionDescription description, File dataFolder, File file) { - extension.init(GeyserImpl.getInstance(), description, dataFolder, file, this); + private void setup(GeyserExtension extension, GeyserExtensionDescription description, File dataFolder, File file) { + GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); + extension.init(Geyser.api(), logger, this, description, dataFolder, file); extension.onLoad(); } - public ExtensionDescription getExtensionDescription(File file) throws InvalidDescriptionException { + @Override + public GeyserExtensionDescription extensionDescription(File file) throws InvalidDescriptionException { JarFile jarFile = null; InputStream stream = null; @@ -105,7 +112,7 @@ public ExtensionDescription getExtensionDescription(File file) throws InvalidDes temp = bufferedReader.readLine(); } - return new ExtensionDescription(builder.toString()); + return new GeyserExtensionDescription(builder.toString()); } catch (IOException e) { throw new InvalidDescriptionException(e); } finally { @@ -124,14 +131,16 @@ public ExtensionDescription getExtensionDescription(File file) throws InvalidDes } } - public Pattern[] getExtensionFilters() { + @Override + public Pattern[] extensionFilters() { return new Pattern[] { Pattern.compile("^.+\\.jar$") }; } - public Class getClassByName(final String name) throws ClassNotFoundException{ + @Override + public Class classByName(final String name) throws ClassNotFoundException{ Class clazz = classes.get(name); try { - for(ExtensionClassLoader loader : classLoaders.values()) { + for(GeyserExtensionClassLoader loader : classLoaders.values()) { try { clazz = loader.findClass(name,false); } catch(NullPointerException e) { @@ -153,19 +162,21 @@ protected void removeClass(String name) { Class clazz = classes.remove(name); } + @Override public void enableExtension(Extension extension) { if (extension instanceof GeyserExtension) { if(!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.getDescription().getName()); + GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.description().name()); ((GeyserExtension) extension).setEnabled(true); } } } + @Override public void disableExtension(Extension extension) { if (extension instanceof GeyserExtension) { if(extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.getDescription().getName()); + GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.description().name()); ((GeyserExtension) extension).setEnabled(false); } } 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..225d6cda36f --- /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 GeyserLogger logger; + private 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/ExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java similarity index 79% rename from core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java rename to core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index ad757d4996b..c06feba1e5f 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/ExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -26,35 +26,36 @@ package org.geysermc.geyser.extension; import org.geysermc.geyser.GeyserImpl; - +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; +import org.geysermc.geyser.api.extension.GeyserExtension; import java.io.File; -import java.io.FilenameFilter; import java.lang.reflect.Constructor; import java.util.*; import java.util.regex.Pattern; -public class ExtensionManager { - private static ExtensionManager extensionManager = null; +public class GeyserExtensionManager { + private static GeyserExtensionManager geyserExtensionManager = null; protected Map extensions = new LinkedHashMap<>(); - protected Map fileAssociations = new HashMap<>(); + protected Map fileAssociations = new HashMap<>(); public static void init() { GeyserImpl.getInstance().getLogger().info("Loading extensions..."); - extensionManager = new ExtensionManager(); - extensionManager.registerInterface(ExtensionLoader.class); - extensionManager.loadExtensions(new File("extensions")); - GeyserImpl.getInstance().getLogger().info("Loaded " + extensionManager.extensions.size() + " extensions."); + geyserExtensionManager = new GeyserExtensionManager(); + geyserExtensionManager.registerInterface(GeyserExtensionLoader.class); + geyserExtensionManager.loadExtensions(new File("extensions")); + GeyserImpl.getInstance().getLogger().info("Loaded " + geyserExtensionManager.extensions.size() + " extensions."); - for (Extension extension : extensionManager.getExtensions().values()) { + for (Extension extension : geyserExtensionManager.getExtensions().values()) { if (!extension.isEnabled()) { - extensionManager.enableExtension(extension); + geyserExtensionManager.enableExtension(extension); } } } - public static ExtensionManager getExtensionManager() { - return extensionManager; + public static GeyserExtensionManager getExtensionManager() { + return geyserExtensionManager; } public Extension getExtension(String name) { @@ -68,11 +69,11 @@ public Map getExtensions() { return this.extensions; } - public void registerInterface(Class loader) { - ExtensionLoader instance; + public void registerInterface(Class loader) { + GeyserExtensionLoader instance; - if (ExtensionLoader.class.isAssignableFrom(loader)) { - Constructor constructor; + if (GeyserExtensionLoader.class.isAssignableFrom(loader)) { + Constructor constructor; try { constructor = loader.getConstructor(); @@ -88,7 +89,7 @@ public void registerInterface(Class loader) { throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader"); } - Pattern[] patterns = instance.getExtensionFilters(); + Pattern[] patterns = instance.extensionFilters(); synchronized (this) { for (Pattern pattern : patterns) { @@ -97,17 +98,17 @@ public void registerInterface(Class loader) { } } - public GeyserExtension loadExtension(File file, Map loaders) { - for (ExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) { - for (Pattern pattern : loader.getExtensionFilters()) { + public GeyserExtension loadExtension(File file, Map loaders) { + for (GeyserExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) { + for (Pattern pattern : loader.extensionFilters()) { if (pattern.matcher(file.getName()).matches()) { try { - ExtensionDescription description = loader.getExtensionDescription(file); + ExtensionDescription description = loader.extensionDescription(file); if (description != null) { GeyserExtension extension = loader.loadExtension(file); if (extension != null) { - this.extensions.put(extension.getDescription().getName(), extension); + this.extensions.put(extension.description().name(), extension); return extension; } @@ -143,9 +144,9 @@ public Map loadExtensions(File dictionary) { Map extensions = new LinkedHashMap<>(); Map loadedExtensions = new LinkedHashMap<>(); - for (final ExtensionLoader loader : this.fileAssociations.values()) { + for (final GeyserExtensionLoader loader : this.fileAssociations.values()) { for (File file : dictionary.listFiles((dir, name) -> { - for (Pattern pattern : loader.getExtensionFilters()) { + for (Pattern pattern : loader.extensionFilters()) { if (pattern.matcher(name).matches()) { return true; } @@ -157,9 +158,9 @@ public Map loadExtensions(File dictionary) { } try { - ExtensionDescription description = loader.getExtensionDescription(file); + ExtensionDescription description = loader.extensionDescription(file); if (description != null) { - String name = description.getName(); + String name = description.name(); if (extensions.containsKey(name) || this.getExtension(name) != null) { GeyserImpl.getInstance().getLogger().warning("Found duplicate extension '" + name + "', ignoring '" + file.getName() + "'"); @@ -168,7 +169,7 @@ public Map loadExtensions(File dictionary) { boolean compatible = false; - for (String version : description.getAPIVersions()) { + for (String version : description.ApiVersions()) { try { //Check the format: majorVersion.minorVersion.patch if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", version)) { @@ -217,9 +218,9 @@ public Map loadExtensions(File dictionary) { public void enableExtension(Extension extension) { if (!extension.isEnabled()) { try { - extension.getExtensionLoader().enableExtension(extension); + extension.extensionLoader().enableExtension(extension); } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Error enabling extension " + extension.getName() + ": ", e); + GeyserImpl.getInstance().getLogger().error("Error enabling extension " + extension.name() + ": ", e); this.disableExtension(extension); } } @@ -228,9 +229,9 @@ public void enableExtension(Extension extension) { public void disableExtension(Extension extension) { if (extension.isEnabled()) { try { - extension.getExtensionLoader().disableExtension(extension); + extension.extensionLoader().disableExtension(extension); } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Error disabling extension " + extension.getName() + ": ", e); + GeyserImpl.getInstance().getLogger().error("Error disabling extension " + extension.name() + ": ", e); } } } From 805f7f666a68aee6950c9bda6ed5d25dc20bfc40 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 13:50:54 +0100 Subject: [PATCH 085/358] Added javadocs & fixed API version & more --- .../geyser/api/extension/Extension.java | 51 ---------- .../api/extension/ExtensionDescription.java | 33 ++++++- .../geyser/api/extension/ExtensionLoader.java | 42 +++++++- .../geyser/api/extension/ExtensionLogger.java | 1 + .../geyser/api/extension/GeyserExtension.java | 95 ++++++++++++++----- .../InvalidDescriptionException.java | 3 + .../exception/InvalidExtensionException.java | 3 + .../extension/GeyserExtensionDescription.java | 21 ++-- .../extension/GeyserExtensionLoader.java | 28 +++--- .../extension/GeyserExtensionManager.java | 87 ++++++++--------- 10 files changed, 212 insertions(+), 152 deletions(-) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java 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 deleted file mode 100644 index 8a820d8ac1a..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.geysermc.api.GeyserApiBase; -import java.io.File; -import java.io.InputStream; - -public interface Extension { - void onLoad(); - void onEnable(); - void onDisable(); - - boolean isEnabled(); - boolean isDisabled(); - - File dataFolder(); - ExtensionDescription description(); - String name(); - - InputStream getResource(String filename); - void saveResource(String filename, boolean replace); - - ClassLoader classLoader(); - ExtensionLoader extensionLoader(); - ExtensionLogger logger(); - GeyserApiBase geyserApi(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index 0f752e72dcb..d32300e09c2 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -25,12 +25,41 @@ package org.geysermc.geyser.api.extension; -import java.util.*; +import java.util.List; public interface ExtensionDescription { + /** + * Gets the extension's name + * + * @return the extension's name + */ String name(); + + /** + * Gets the extension's main class + * + * @return the extension's main class + */ String main(); - List ApiVersions(); + + /** + * Gets the extension's api version + * + * @return the extension's api version + */ + String ApiVersion(); + + /** + * Gets the extension's description + * + * @return the extension's description + */ String version(); + + /** + * Gets the extension's authors + * + * @return the extension's authors + */ 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 index c558957eb8c..291a34daf26 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -27,15 +27,47 @@ import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; - import java.io.File; -import java.util.regex.Pattern; public interface ExtensionLoader { + /** + * Loads an extension from a given file + * + * @param file the file to load the extension from + * @return the loaded extension + * @throws InvalidExtensionException + */ GeyserExtension loadExtension(File file) throws InvalidExtensionException; + + /** + * Gets an extension's description from a given file + * + * @param file the file to get the description from + * @return the extension's description + * @throws InvalidDescriptionException + */ ExtensionDescription extensionDescription(File file) throws InvalidDescriptionException; - Pattern[] extensionFilters(); + + /** + * Gets a class by its name from the extension's classloader + * + * @param name the name of the class + * @return the class + * @throws ClassNotFoundException + */ Class classByName(final String name) throws ClassNotFoundException; - void enableExtension(Extension extension); - void disableExtension(Extension extension); + + /** + * Enables an extension + * + * @param extension the extension to enable + */ + void enableExtension(GeyserExtension extension); + + /** + * Disables an extension + * + * @param extension the extension to disable + */ + void disableExtension(GeyserExtension extension); } 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 index 6b5d8615360..60ee455723d 100644 --- 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 @@ -28,6 +28,7 @@ public interface ExtensionLogger { /** * Get the logger prefix + * * @return the logger prefix */ String prefix(); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java index 3ed66b444ef..a3f9115809a 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java @@ -30,34 +30,52 @@ import java.net.URL; import java.net.URLConnection; -public class GeyserExtension implements Extension { +public class GeyserExtension { private boolean initialized = false; private boolean enabled = false; private File file = null; private File dataFolder = null; private ClassLoader classLoader = null; - private ExtensionLoader loader; - private ExtensionLogger logger; + private ExtensionLoader loader = null; + private ExtensionLogger logger = null; private ExtensionDescription description = null; private GeyserApiBase api = null; - @Override + /** + * Called when the extension is loaded + */ public void onLoad() { + } - @Override + /** + * Called when the extension is enabled + */ public void onEnable() { + } - @Override + /** + * Called when the extension is disabled + */ public void onDisable() { + } - @Override + /** + * Gets if the extension is enabled + * + * @return true if the extension is enabled + */ public boolean isEnabled() { return this.enabled; } + /** + * Gets if the extension is enabled + * + * @return true if the extension is enabled + */ public void setEnabled(boolean value) { if (this.enabled != value) { this.enabled = value; @@ -69,27 +87,34 @@ public void setEnabled(boolean value) { } } - @Override - public boolean isDisabled() { - return !this.enabled; - } - - @Override + /** + * Gets the extension's data folder + * + * @return the extension's data folder + */ public File dataFolder() { return this.dataFolder; } - @Override + /** + * Gets the extension's description + * + * @return the extension's description + */ public ExtensionDescription description() { return this.description; } - @Override + /** + * Gets the extension's name + * + * @return the extension's name + */ public String name() { return this.description.name(); } - public void init(GeyserApiBase api, ExtensionLogger logger, ExtensionLoader loader, ExtensionDescription description, File dataFolder, File file) { + public void init(GeyserApiBase api, ExtensionLoader loader, ExtensionLogger logger, ExtensionDescription description, File dataFolder, File file) { if (!this.initialized) { this.initialized = true; this.file = file; @@ -102,7 +127,12 @@ public void init(GeyserApiBase api, ExtensionLogger logger, ExtensionLoader load } } - @Override + /** + * Gets a resource from the extension jar file + * + * @param filename the file name + * @return the input stream + */ public InputStream getResource(String filename) { if (filename == null) { throw new IllegalArgumentException("Filename cannot be null"); @@ -123,7 +153,12 @@ public InputStream getResource(String filename) { } } - @Override + /** + * Saves a resource from the extension jar file to the extension's data folder + * + * @param filename the file name + * @param replace whether to replace the file if it already exists + */ public void saveResource(String filename, boolean replace) { if (filename == null || filename.equals("")) { throw new IllegalArgumentException("ResourcePath cannot be null or empty"); @@ -161,22 +196,38 @@ public void saveResource(String filename, boolean replace) { } } - @Override + /** + * Gets the extension's class loader + * + * @return the extension's class loader + */ public ClassLoader classLoader() { return this.classLoader; } - @Override + /** + * Gets the extension's loader + * + * @return the extension's loader + */ public ExtensionLoader extensionLoader() { return this.loader; } - @Override + /** + * Gets the extension's logger + * + * @return the extension's logger + */ public ExtensionLogger logger() { return this.logger; } - @Override + /** + * Gets the {@link GeyserApiBase} instance + * + * @return the {@link GeyserApiBase} instance + */ public GeyserApiBase geyserApi() { return this.api; } 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 index ca2fabad978..1fe88e9e990 100644 --- 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 @@ -25,6 +25,9 @@ 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); 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 index 1053e6d50ab..7fb6b692241 100644 --- 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 @@ -25,6 +25,9 @@ package org.geysermc.geyser.api.extension.exception; +/** + * Thrown when an extension is invalid. + */ public class InvalidExtensionException extends Exception { public InvalidExtensionException(Throwable cause) { super(cause); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index ee215f2e4d1..e2a09c59812 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -33,7 +33,7 @@ public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { private String name; private String main; - private List api; + private String api; private String version; private final List authors = new ArrayList<>(); @@ -47,19 +47,18 @@ public GeyserExtensionDescription(String yamlString) throws InvalidDescriptionEx private void loadMap(Map yamlMap) throws InvalidDescriptionException { this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); if (this.name.equals("")) { - throw new InvalidDescriptionException("Invalid extension name"); + throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); } this.name = this.name.replace(" ", "_"); this.version = String.valueOf(yamlMap.get("version")); this.main = (String) yamlMap.get("main"); Object api = yamlMap.get("api"); - if (api instanceof List) { - this.api = (List) api; + if (api instanceof String) { + this.api = (String) api; } else { - List list = new ArrayList<>(); - list.add((String) api); - this.api = list; + this.api = "0.0.0"; + throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); } if (yamlMap.containsKey("author")) { @@ -67,7 +66,11 @@ private void loadMap(Map yamlMap) throws InvalidDescriptionExcep } if (yamlMap.containsKey("authors")) { - this.authors.addAll((Collection) yamlMap.get("authors")); + try { + this.authors.addAll((Collection) yamlMap.get("authors")); + } catch (Exception e) { + throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); + } } } @@ -82,7 +85,7 @@ public String main() { } @Override - public List ApiVersions() { + public String ApiVersion() { return api; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 9b741fb2d88..a7e4bac3dcb 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -27,7 +27,6 @@ import org.geysermc.api.Geyser; 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.GeyserExtension; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; @@ -80,7 +79,7 @@ public GeyserExtension loadExtension(File file) throws InvalidExtensionException private void setup(GeyserExtension extension, GeyserExtensionDescription description, File dataFolder, File file) { GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); - extension.init(Geyser.api(), logger, this, description, dataFolder, file); + extension.init(Geyser.api(), this, logger, description, dataFolder, file); extension.onLoad(); } @@ -131,7 +130,6 @@ public GeyserExtensionDescription extensionDescription(File file) throws Invalid } } - @Override public Pattern[] extensionFilters() { return new Pattern[] { Pattern.compile("^.+\\.jar$") }; } @@ -152,33 +150,29 @@ public Class classByName(final String name) throws ClassNotFoundException{ } } - public void setClass(String name, final Class clazz) { + void setClass(String name, final Class clazz) { if(!classes.containsKey(name)) { classes.put(name,clazz); } } - protected void removeClass(String name) { + void removeClass(String name) { Class clazz = classes.remove(name); } @Override - public void enableExtension(Extension extension) { - if (extension instanceof GeyserExtension) { - if(!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.description().name()); - ((GeyserExtension) extension).setEnabled(true); - } + public void enableExtension(GeyserExtension extension) { + if (!extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.description().name()); + extension.setEnabled(true); } } @Override - public void disableExtension(Extension extension) { - if (extension instanceof GeyserExtension) { - if(extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.description().name()); - ((GeyserExtension) extension).setEnabled(false); - } + public void disableExtension(GeyserExtension extension) { + if (extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.description().name()); + extension.setEnabled(false); } } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index c06feba1e5f..edda15adeb1 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -26,9 +26,9 @@ package org.geysermc.geyser.extension; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.GeyserExtension; + import java.io.File; import java.lang.reflect.Constructor; import java.util.*; @@ -37,35 +37,34 @@ public class GeyserExtensionManager { private static GeyserExtensionManager geyserExtensionManager = null; - protected Map extensions = new LinkedHashMap<>(); + protected Map extensions = new LinkedHashMap<>(); protected Map fileAssociations = new HashMap<>(); public static void init() { GeyserImpl.getInstance().getLogger().info("Loading extensions..."); + geyserExtensionManager = new GeyserExtensionManager(); geyserExtensionManager.registerInterface(GeyserExtensionLoader.class); geyserExtensionManager.loadExtensions(new File("extensions")); - GeyserImpl.getInstance().getLogger().info("Loaded " + geyserExtensionManager.extensions.size() + " extensions."); - for (Extension extension : geyserExtensionManager.getExtensions().values()) { - if (!extension.isEnabled()) { - geyserExtensionManager.enableExtension(extension); - } - } + String plural = geyserExtensionManager.extensions.size() == 1 ? "" : "s"; + GeyserImpl.getInstance().getLogger().info("Loaded " + geyserExtensionManager.extensions.size() + " extension" + plural); + + geyserExtensionManager.enableExtensions(); } public static GeyserExtensionManager getExtensionManager() { return geyserExtensionManager; } - public Extension getExtension(String name) { + public GeyserExtension getExtension(String name) { if (this.extensions.containsKey(name)) { return this.extensions.get(name); } return null; } - public Map getExtensions() { + public Map getExtensions() { return this.extensions; } @@ -124,8 +123,8 @@ public GeyserExtension loadExtension(File file, Map loadExtensions(File dictionary) { - if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { + public Map loadExtensions(File dictionary) { + if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { // If your IDE says this is always true, ignore it, it isn't. GeyserImpl.getInstance().getLogger().error("Cannot load extensions in a development environment, aborting extension loading"); return new HashMap<>(); } @@ -134,6 +133,8 @@ public Map loadExtensions(File dictionary) { return new HashMap<>(); } + String[] apiVersion = GeyserImpl.VERSION.split("\\."); + if (!dictionary.exists()) { dictionary.mkdir(); } @@ -142,7 +143,7 @@ public Map loadExtensions(File dictionary) { } Map extensions = new LinkedHashMap<>(); - Map loadedExtensions = new LinkedHashMap<>(); + Map loadedExtensions = new LinkedHashMap<>(); for (final GeyserExtensionLoader loader : this.fileAssociations.values()) { for (File file : dictionary.listFiles((dir, name) -> { @@ -167,47 +168,35 @@ public Map loadExtensions(File dictionary) { continue; } - boolean compatible = false; - - for (String version : description.ApiVersions()) { - try { - //Check the format: majorVersion.minorVersion.patch - if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", version)) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error("Could't load extension " + name + ": Wrong API format"); - continue; - } - - String[] versionArray = version.split("\\."); - String[] apiVersion = GeyserImpl.VERSION.split("\\."); - - //Completely different API version - if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); - continue; + try { + //Check the format: majorVersion.minorVersion.patch + if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.ApiVersion())) { + throw new IllegalArgumentException(); } + } catch (NullPointerException | IllegalArgumentException e) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version format, should be 'majorVersion.minorVersion.patch', current version: " + apiVersion[0] + "." + apiVersion[1]); + continue; + } - //If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); - continue; - } + String[] versionArray = description.ApiVersion().split("\\."); - compatible = true; - break; + //Completely different API version + if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + continue; } - if (!compatible) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name +": Incompatible API version"); + //If the extension requires new API features, being backwards compatible + if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { + GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + continue; } extensions.put(name, file); loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations)); } } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Couldn't load " +file.getName()+ " in folder " + dictionary + ": ", e); + GeyserImpl.getInstance().getLogger().error("Couldn't load " + file.getName() + " in folder " + dictionary.getAbsolutePath() + ": ", e); } } } @@ -215,7 +204,7 @@ public Map loadExtensions(File dictionary) { return loadedExtensions; } - public void enableExtension(Extension extension) { + public void enableExtension(GeyserExtension extension) { if (!extension.isEnabled()) { try { extension.extensionLoader().enableExtension(extension); @@ -226,7 +215,7 @@ public void enableExtension(Extension extension) { } } - public void disableExtension(Extension extension) { + public void disableExtension(GeyserExtension extension) { if (extension.isEnabled()) { try { extension.extensionLoader().disableExtension(extension); @@ -236,8 +225,14 @@ public void disableExtension(Extension extension) { } } + public void enableExtensions() { + for (GeyserExtension extension : this.getExtensions().values()) { + this.enableExtension(extension); + } + } + public void disableExtensions() { - for (Extension extension : this.getExtensions().values()) { + for (GeyserExtension extension : this.getExtensions().values()) { this.disableExtension(extension); } } From f3a331981fdf26afbc61b38709a27aa1e2a0a395 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 15:31:28 +0100 Subject: [PATCH 086/358] Added extension dump data & make plugins be enabled on reload --- .../api/extension/ExtensionDescription.java | 5 +++- .../geyser/api/extension/ExtensionLoader.java | 3 +++ .../geyser/api/extension/ExtensionLogger.java | 3 +++ .../geyser/api/extension/GeyserExtension.java | 3 +++ .../java/org/geysermc/geyser/GeyserImpl.java | 3 ++- .../org/geysermc/geyser/dump/DumpInfo.java | 24 +++++++++++++++---- .../extension/GeyserExtensionDescription.java | 2 +- .../extension/GeyserExtensionManager.java | 6 ++--- 8 files changed, 39 insertions(+), 10 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index d32300e09c2..4426a4a923a 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -27,6 +27,9 @@ import java.util.List; +/** + * This is the Geyer extension description + */ public interface ExtensionDescription { /** * Gets the extension's name @@ -47,7 +50,7 @@ public interface ExtensionDescription { * * @return the extension's api version */ - String ApiVersion(); + String apiVersion(); /** * Gets the extension's description diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java index 291a34daf26..1301493d59e 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -29,6 +29,9 @@ import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import java.io.File; +/** + * The extension loader is responsible for loading, unloading, enabling and disabling extensions + */ public interface ExtensionLoader { /** * Loads an extension from a given 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 index 60ee455723d..17e108455fd 100644 --- 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 @@ -25,6 +25,9 @@ package org.geysermc.geyser.api.extension; +/** + * This is the Geyser extension logger + */ public interface ExtensionLogger { /** * Get the logger prefix diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java index a3f9115809a..210452c3cca 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java @@ -30,6 +30,9 @@ import java.net.URL; import java.net.URLConnection; +/** + * This class is to be extended by a Geyser extension + */ public class GeyserExtension { private boolean initialized = false; private boolean enabled = false; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 7e61b3af764..b563892ccf5 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -460,7 +460,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - GeyserExtensionManager.getExtensionManager().disableExtensions(); + GeyserExtensionManager.getInstance().disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } @@ -468,6 +468,7 @@ public void shutdown() { @Override public void reload() { shutdown(); + GeyserExtensionManager.getInstance().enableExtensions(); bootstrap.onEnable(); } 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 2734c7443da..316f095c0b9 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -36,6 +36,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.network.MinecraftProtocol; @@ -54,10 +56,7 @@ import java.net.Socket; import java.net.UnknownHostException; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.stream.Collectors; @Getter @@ -76,6 +75,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(); @@ -125,6 +125,11 @@ public DumpInfo(boolean addLog) { this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo(); this.flagsInfo = new FlagsInfo(); + + this.extensionInfo = new ArrayList<>(); + for (GeyserExtension extension : GeyserExtensionManager.getInstance().getExtensions().values()) { + this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().main(), extension.description().authors(), extension.description().apiVersion())); + } } @Getter @@ -277,4 +282,15 @@ 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 main; + public List authors; + public String apiVersion; + } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index e2a09c59812..228b8842649 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -85,7 +85,7 @@ public String main() { } @Override - public String ApiVersion() { + public String apiVersion() { return api; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index edda15adeb1..db0515906e7 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -53,7 +53,7 @@ public static void init() { geyserExtensionManager.enableExtensions(); } - public static GeyserExtensionManager getExtensionManager() { + public static GeyserExtensionManager getInstance() { return geyserExtensionManager; } @@ -170,7 +170,7 @@ public Map loadExtensions(File dictionary) { try { //Check the format: majorVersion.minorVersion.patch - if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.ApiVersion())) { + if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.apiVersion())) { throw new IllegalArgumentException(); } } catch (NullPointerException | IllegalArgumentException e) { @@ -178,7 +178,7 @@ public Map loadExtensions(File dictionary) { continue; } - String[] versionArray = description.ApiVersion().split("\\."); + String[] versionArray = description.apiVersion().split("\\."); //Completely different API version if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { From cb18c969d7f2cb1e8e2f18bc708caf80c6f5d61d Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 15:32:49 +0100 Subject: [PATCH 087/358] I forgot the "s" in Geyser --- .../org/geysermc/geyser/api/extension/ExtensionDescription.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index 4426a4a923a..487df3926b3 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -28,7 +28,7 @@ import java.util.List; /** - * This is the Geyer extension description + * This is the Geyser extension description */ public interface ExtensionDescription { /** From 8bb8e48a55050dbeff523ad2568f322d3382b632 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 16:40:51 +0100 Subject: [PATCH 088/358] Fixed what Konicai asked --- .../geyser/api/extension/GeyserExtension.java | 4 +-- .../java/org/geysermc/geyser/GeyserImpl.java | 14 +++++++-- .../org/geysermc/geyser/dump/DumpInfo.java | 6 ++-- .../extension/GeyserExtensionClassLoader.java | 6 ++-- .../extension/GeyserExtensionDescription.java | 29 +++++++++++++++++++ .../extension/GeyserExtensionLoader.java | 18 ++---------- .../extension/GeyserExtensionManager.java | 19 +++--------- 7 files changed, 53 insertions(+), 43 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java index 210452c3cca..bd53bafd3f3 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java @@ -75,9 +75,7 @@ public boolean isEnabled() { } /** - * Gets if the extension is enabled - * - * @return true if the extension is enabled + * Enables or disables the extension */ public void setEnabled(boolean value) { if (this.enabled != value) { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index b563892ccf5..2fbcbadddba 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -123,6 +123,8 @@ public class GeyserImpl implements GeyserApi { private final PlatformType platformType; private final GeyserBootstrap bootstrap; + private final GeyserExtensionManager extensionManager; + private Metrics metrics; private static GeyserImpl instance; @@ -155,7 +157,8 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { MessageTranslator.init(); MinecraftLocale.init(); - GeyserExtensionManager.init(); + extensionManager = new GeyserExtensionManager(); + extensionManager.init(); start(); @@ -200,6 +203,8 @@ private void start() { ResourcePack.loadPacks(); + extensionManager.enableExtensions(); + if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { @@ -460,7 +465,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - GeyserExtensionManager.getInstance().disableExtensions(); + extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } @@ -468,7 +473,6 @@ public void shutdown() { @Override public void reload() { shutdown(); - GeyserExtensionManager.getInstance().enableExtensions(); bootstrap.onEnable(); } @@ -514,6 +518,10 @@ public WorldManager getWorldManager() { return bootstrap.getWorldManager(); } + public GeyserExtensionManager getExtensionManager() { + return extensionManager; + } + public static GeyserImpl getInstance() { return instance; } 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 316f095c0b9..3377f7ee5e0 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -127,8 +127,8 @@ public DumpInfo(boolean addLog) { this.flagsInfo = new FlagsInfo(); this.extensionInfo = new ArrayList<>(); - for (GeyserExtension extension : GeyserExtensionManager.getInstance().getExtensions().values()) { - this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().main(), extension.description().authors(), extension.description().apiVersion())); + for (GeyserExtension extension : GeyserImpl.getInstance().getExtensionManager().getExtensions().values()) { + this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors())); } } @@ -289,8 +289,8 @@ public static class ExtensionInfo { public boolean enabled; public String name; public String version; + public String apiVersion; public String main; public List authors; - public String apiVersion; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index a1ccc063e8c..3ce0a00aeda 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -78,11 +78,11 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo throw new ClassNotFoundException(name); } Class result = classes.get(name); - if(result == null) { - if(checkGlobal) { + if (result == null) { + if (checkGlobal) { result = loader.classByName(name); } - if(result == null) { + if (result == null) { result = super.findClass(name); if (result != null) { loader.setClass(name, result); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 228b8842649..31d6c3a4688 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -28,6 +28,10 @@ import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.*; public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { @@ -37,7 +41,32 @@ public class GeyserExtensionDescription implements org.geysermc.geyser.api.exten private String version; private final List authors = new ArrayList<>(); + public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescriptionException { + try { + InputStreamReader reader = new InputStreamReader(inputStream); + StringBuilder builder = new StringBuilder(); + String temp; + BufferedReader bufferedReader = new BufferedReader(reader); + temp = bufferedReader.readLine(); + while (temp != null) { + if (builder.length() != 0) { + builder.append("\n"); + } + builder.append(temp); + temp = bufferedReader.readLine(); + } + + this.loadString(builder.toString()); + } catch (IOException e) { + throw new InvalidDescriptionException(e); + } + } + public GeyserExtensionDescription(String yamlString) throws InvalidDescriptionException { + this.loadString(yamlString); + } + + private void loadString(String yamlString) throws InvalidDescriptionException { DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(dumperOptions); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index a7e4bac3dcb..cffd513a1ae 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -97,21 +97,7 @@ public GeyserExtensionDescription extensionDescription(File file) throws Invalid } stream = jarFile.getInputStream(descriptionEntry); - - InputStreamReader reader = new InputStreamReader(stream); - StringBuilder builder = new StringBuilder(); - String temp; - BufferedReader bufferedReader = new BufferedReader(reader); - temp = bufferedReader.readLine(); - while (temp != null) { - if (builder.length() != 0) { - builder.append("\n"); - } - builder.append(temp); - temp = bufferedReader.readLine(); - } - - return new GeyserExtensionDescription(builder.toString()); + return new GeyserExtensionDescription(stream); } catch (IOException e) { throw new InvalidDescriptionException(e); } finally { @@ -151,7 +137,7 @@ public Class classByName(final String name) throws ClassNotFoundException{ } void setClass(String name, final Class clazz) { - if(!classes.containsKey(name)) { + if (!classes.containsKey(name)) { classes.put(name,clazz); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index db0515906e7..6dcfca0b5a5 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -28,33 +28,22 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.GeyserExtension; - import java.io.File; import java.lang.reflect.Constructor; import java.util.*; import java.util.regex.Pattern; public class GeyserExtensionManager { - private static GeyserExtensionManager geyserExtensionManager = null; - protected Map extensions = new LinkedHashMap<>(); protected Map fileAssociations = new HashMap<>(); - public static void init() { + public void init() { GeyserImpl.getInstance().getLogger().info("Loading extensions..."); - geyserExtensionManager = new GeyserExtensionManager(); - geyserExtensionManager.registerInterface(GeyserExtensionLoader.class); - geyserExtensionManager.loadExtensions(new File("extensions")); - - String plural = geyserExtensionManager.extensions.size() == 1 ? "" : "s"; - GeyserImpl.getInstance().getLogger().info("Loaded " + geyserExtensionManager.extensions.size() + " extension" + plural); - - geyserExtensionManager.enableExtensions(); - } + this.registerInterface(GeyserExtensionLoader.class); + this.loadExtensions(new File("extensions")); - public static GeyserExtensionManager getInstance() { - return geyserExtensionManager; + GeyserImpl.getInstance().getLogger().info("Loaded " + this.extensions.size() + " extension(s)"); } public GeyserExtension getExtension(String name) { From f8c173aae8ed9ba70443b04ea3f48c6b5a4b2ed3 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 16:47:25 +0100 Subject: [PATCH 089/358] Actually did what Konicai wanted --- .../extension/GeyserExtensionDescription.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 31d6c3a4688..b14291b016c 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -42,28 +42,10 @@ public class GeyserExtensionDescription implements org.geysermc.geyser.api.exten private final List authors = new ArrayList<>(); public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescriptionException { - try { - InputStreamReader reader = new InputStreamReader(inputStream); - StringBuilder builder = new StringBuilder(); - String temp; - BufferedReader bufferedReader = new BufferedReader(reader); - temp = bufferedReader.readLine(); - while (temp != null) { - if (builder.length() != 0) { - builder.append("\n"); - } - builder.append(temp); - temp = bufferedReader.readLine(); - } - - this.loadString(builder.toString()); - } catch (IOException e) { - throw new InvalidDescriptionException(e); - } - } - - public GeyserExtensionDescription(String yamlString) throws InvalidDescriptionException { - this.loadString(yamlString); + DumperOptions dumperOptions = new DumperOptions(); + dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml yaml = new Yaml(dumperOptions); + this.loadMap(yaml.loadAs(inputStream, LinkedHashMap.class)); } private void loadString(String yamlString) throws InvalidDescriptionException { From 0ccd85ccfbb0735844503639ca994a69cbe3bb5b Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 18:43:10 +0100 Subject: [PATCH 090/358] Use Geyser locale for log messages --- .../extension/GeyserExtensionClassLoader.java | 6 ++--- .../extension/GeyserExtensionDescription.java | 10 -------- .../extension/GeyserExtensionLoader.java | 7 +++--- .../extension/GeyserExtensionLogger.java | 4 +-- .../extension/GeyserExtensionManager.java | 25 ++++++++++--------- core/src/main/resources/languages | 2 +- core/src/main/resources/mappings | 2 +- 7 files changed, 24 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 3ce0a00aeda..67363a40f51 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -37,8 +37,8 @@ import java.util.Set; public class GeyserExtensionClassLoader extends URLClassLoader { - private GeyserExtensionLoader loader; - private Map classes = new HashMap<>(); + private final GeyserExtensionLoader loader; + private final Map classes = new HashMap<>(); public GeyserExtension extension; public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { @@ -74,7 +74,7 @@ protected Class findClass(String name) throws ClassNotFoundException { } protected Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { - if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("net.minecraft.")) { + if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("org.geysermc.platform.") || name.startsWith("org.geysermc.floodgate.") || name.startsWith("org.geysermc.api.") || name.startsWith("org.geysermc.processor.") || name.startsWith("net.minecraft.")) { throw new ClassNotFoundException(name); } Class result = classes.get(name); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index b14291b016c..f8a3d9bbe11 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -28,10 +28,7 @@ import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; -import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.util.*; public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { @@ -48,13 +45,6 @@ public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescrip this.loadMap(yaml.loadAs(inputStream, LinkedHashMap.class)); } - private void loadString(String yamlString) throws InvalidDescriptionException { - DumperOptions dumperOptions = new DumperOptions(); - dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Yaml yaml = new Yaml(dumperOptions); - this.loadMap(yaml.loadAs(yamlString, LinkedHashMap.class)); - } - private void loadMap(Map yamlMap) throws InvalidDescriptionException { this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); if (this.name.equals("")) { diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index cffd513a1ae..f029eb79767 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -31,6 +31,7 @@ import org.geysermc.geyser.api.extension.GeyserExtension; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; +import org.geysermc.geyser.text.GeyserLocale; import java.io.*; import java.util.HashMap; import java.util.Map; @@ -143,13 +144,13 @@ void setClass(String name, final Class clazz) { } void removeClass(String name) { - Class clazz = classes.remove(name); + classes.remove(name); } @Override public void enableExtension(GeyserExtension extension) { if (!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.description().name()); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); extension.setEnabled(true); } } @@ -157,7 +158,7 @@ public void enableExtension(GeyserExtension extension) { @Override public void disableExtension(GeyserExtension extension) { if (extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.description().name()); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); extension.setEnabled(false); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java index 225d6cda36f..fe23417f8dd 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLogger.java @@ -29,8 +29,8 @@ import org.geysermc.geyser.api.extension.ExtensionLogger; public class GeyserExtensionLogger implements ExtensionLogger { - private GeyserLogger logger; - private String loggerPrefix; + private final GeyserLogger logger; + private final String loggerPrefix; public GeyserExtensionLogger(GeyserLogger logger, String prefix) { this.logger = logger; diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 6dcfca0b5a5..1690531826d 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -28,6 +28,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.text.GeyserLocale; import java.io.File; import java.lang.reflect.Constructor; import java.util.*; @@ -38,12 +39,12 @@ public class GeyserExtensionManager { protected Map fileAssociations = new HashMap<>(); public void init() { - GeyserImpl.getInstance().getLogger().info("Loading extensions..."); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); this.registerInterface(GeyserExtensionLoader.class); this.loadExtensions(new File("extensions")); - GeyserImpl.getInstance().getLogger().info("Loaded " + this.extensions.size() + " extension(s)"); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); } public GeyserExtension getExtension(String name) { @@ -102,7 +103,7 @@ public GeyserExtension loadExtension(File file, Map loadExtensions(File dictionary) { if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { // If your IDE says this is always true, ignore it, it isn't. - GeyserImpl.getInstance().getLogger().error("Cannot load extensions in a development environment, aborting extension loading"); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); return new HashMap<>(); } if (!GeyserImpl.VERSION.contains(".")) { - GeyserImpl.getInstance().getLogger().error("Something went wrong with the Geyser version number, aborting extension loading"); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); return new HashMap<>(); } @@ -153,7 +154,7 @@ public Map loadExtensions(File dictionary) { String name = description.name(); if (extensions.containsKey(name) || this.getExtension(name) != null) { - GeyserImpl.getInstance().getLogger().warning("Found duplicate extension '" + name + "', ignoring '" + file.getName() + "'"); + GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, file.getName())); continue; } @@ -163,7 +164,7 @@ public Map loadExtensions(File dictionary) { throw new IllegalArgumentException(); } } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version format, should be 'majorVersion.minorVersion.patch', current version: " + apiVersion[0] + "." + apiVersion[1]); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); continue; } @@ -171,13 +172,13 @@ public Map loadExtensions(File dictionary) { //Completely different API version if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); continue; } //If the extension requires new API features, being backwards compatible if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); continue; } @@ -185,7 +186,7 @@ public Map loadExtensions(File dictionary) { loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations)); } } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Couldn't load " + file.getName() + " in folder " + dictionary.getAbsolutePath() + ": ", e); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", file.getName(), dictionary.getAbsolutePath()), e); } } } @@ -198,7 +199,7 @@ public void enableExtension(GeyserExtension extension) { try { extension.extensionLoader().enableExtension(extension); } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Error enabling extension " + extension.name() + ": ", e); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e); this.disableExtension(extension); } } @@ -209,7 +210,7 @@ public void disableExtension(GeyserExtension extension) { try { extension.extensionLoader().disableExtension(extension); } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error("Error disabling extension " + extension.name() + ": ", e); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.failed", extension.name()), e); } } } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 1a50238c1c7..94c1851931f 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 1a50238c1c743579a1dbd93c57d02b5da3be14fa +Subproject commit 94c1851931f2319a7e7f42c2fe9066b78235bc39 diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 82ad7ba279c..b60cfcdd40c 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 82ad7ba279c68eb11a0b1a969c9efb3228c59227 +Subproject commit b60cfcdd40cd58a93143b489fc9153a347e48c41 From 142bb95c069fd92f457b805e9ff4deb5723fc15e Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 15 Jan 2022 11:56:40 -0600 Subject: [PATCH 091/358] Fix package name for `Connection` --- api/base/src/main/java/org/geysermc/api/GeyserApiBase.java | 2 +- .../org/geysermc/api/{session => connection}/Connection.java | 2 +- .../org/geysermc/geyser/api/connection/GeyserConnection.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename api/base/src/main/java/org/geysermc/api/{session => connection}/Connection.java (97%) 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..bf38f58b99d 100644 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java @@ -27,7 +27,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.api.session.Connection; +import org.geysermc.api.connection.Connection; import java.util.List; import java.util.UUID; diff --git a/api/base/src/main/java/org/geysermc/api/session/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java similarity index 97% rename from api/base/src/main/java/org/geysermc/api/session/Connection.java rename to api/base/src/main/java/org/geysermc/api/connection/Connection.java index ccf3f712244..d3879f36d4a 100644 --- a/api/base/src/main/java/org/geysermc/api/session/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/connection/Connection.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.api.session; +package org.geysermc.api.connection; import org.checkerframework.checker.nullness.qual.NonNull; 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..b673b60f612 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,7 +25,7 @@ package org.geysermc.geyser.api.connection; -import org.geysermc.api.session.Connection; +import org.geysermc.api.connection.Connection; /** * Represents a player session used in Geyser. From 778f004d9983b2e9597df159a2e262baf06b5e83 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 15 Jan 2022 16:27:35 -0600 Subject: [PATCH 092/358] Slight cleanups and make Extension an interface --- api/geyser/pom.xml | 9 + .../org/geysermc/geyser/api/GeyserApi.java | 18 ++ .../geyser/api/extension/Extension.java | 136 ++++++++++ .../api/extension/ExtensionDescription.java | 8 + .../geyser/api/extension/ExtensionLoader.java | 72 ++++-- .../api/extension/ExtensionManager.java | 120 +++++++++ .../geyser/api/extension/GeyserExtension.java | 235 ----------------- api/pom.xml | 1 + core/pom.xml | 1 - .../java/org/geysermc/geyser/GeyserImpl.java | 18 +- .../org/geysermc/geyser/dump/DumpInfo.java | 6 +- .../extension/GeyserExtensionClassLoader.java | 37 +-- .../extension/GeyserExtensionContainer.java | 50 ++++ .../extension/GeyserExtensionDescription.java | 64 ++--- .../extension/GeyserExtensionLoader.java | 243 ++++++++++++------ .../extension/GeyserExtensionManager.java | 235 ++++++----------- .../geysermc/geyser/registry/Registries.java | 9 +- pom.xml | 2 + 18 files changed, 693 insertions(+), 571 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 89349e8ac07..5c598f65b19 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -6,6 +6,7 @@ org.geysermc api-parent 2.0.0-SNAPSHOT + ../pom.xml 4.0.0 @@ -14,6 +15,8 @@ 16 16 + + 4.9.3 @@ -23,6 +26,12 @@ 3.19.0 provided + + net.kyori + adventure-api + ${adventure.version} + compile + org.geysermc base-api 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..d5fd0938127 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,10 @@ 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.extension.ExtensionManager; import java.util.List; import java.util.UUID; @@ -78,4 +80,20 @@ public interface GeyserApi extends GeyserApiBase { */ @NonNull List onlineConnections(); + + /** + * Gets the {@link ExtensionManager}. + * + * @return the extension manager + */ + ExtensionManager extensionManager(); + + /** + * Gets the current {@link GeyserApiBase} instance. + * + * @return the current geyser api instance + */ + static GeyserApi api() { + return Geyser.api(GeyserApi.class); + } } 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..097cabdc359 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -0,0 +1,136 @@ +/* + * 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.geysermc.api.GeyserApiBase; +import org.geysermc.geyser.api.GeyserApi; + +import java.nio.file.Path; + +/** + * Represents an extension within Geyser. + */ +public interface Extension { + + /** + * Called when the extension is loaded + */ + default void onLoad() { + } + + /** + * Called when the extension is enabled + */ + default void onEnable() { + } + + /** + * Called when the extension is disabled + */ + default void onDisable() { + } + + /** + * 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 + */ + default Path dataFolder() { + return this.extensionLoader().dataFolder(this); + } + + /** + * Gets the {@link ExtensionManager}. + * + * @return the extension manager + */ + default ExtensionManager extensionManager() { + return this.geyserApi().extensionManager(); + } + + /** + * Gets the extension's name + * + * @return the extension's name + */ + default String name() { + return this.description().name(); + } + + /** + * Gets this extension's {@link ExtensionDescription}. + * + * @return the extension's description + */ + default ExtensionDescription description() { + return this.extensionLoader().description(this); + } + + /** + * Gets the extension's logger + * + * @return the extension's logger + */ + default ExtensionLogger logger() { + return this.extensionLoader().logger(this); + } + + /** + * Gets the {@link ExtensionLoader}. + * + * @return the extension loader + */ + default ExtensionLoader extensionLoader() { + return this.extensionManager().extensionLoader(this); + } + + /** + * Gets the {@link GeyserApiBase} instance + * + * @return the geyser api instance + */ + default GeyserApi geyserApi() { + return GeyserApi.api(); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index 487df3926b3..e7741114498 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -25,17 +25,21 @@ package org.geysermc.geyser.api.extension; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.List; /** * This is the Geyser extension description */ public interface ExtensionDescription { + /** * Gets the extension's name * * @return the extension's name */ + @NonNull String name(); /** @@ -43,6 +47,7 @@ public interface ExtensionDescription { * * @return the extension's main class */ + @NonNull String main(); /** @@ -50,6 +55,7 @@ public interface ExtensionDescription { * * @return the extension's api version */ + @NonNull String apiVersion(); /** @@ -57,6 +63,7 @@ public interface ExtensionDescription { * * @return the extension's description */ + @NonNull String version(); /** @@ -64,5 +71,6 @@ public interface ExtensionDescription { * * @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 index 1301493d59e..651afd9eb8f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -25,52 +25,72 @@ package org.geysermc.geyser.api.extension; -import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; -import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; -import java.io.File; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.nio.file.Path; /** * The extension loader is responsible for loading, unloading, enabling and disabling extensions */ -public interface ExtensionLoader { +public abstract class ExtensionLoader { + /** - * Loads an extension from a given file + * Gets if the given {@link Extension} is enabled. * - * @param file the file to load the extension from - * @return the loaded extension - * @throws InvalidExtensionException + * @param extension the extension + * @return if the extension is enabled */ - GeyserExtension loadExtension(File file) throws InvalidExtensionException; + protected abstract boolean isEnabled(@NonNull Extension extension); /** - * Gets an extension's description from a given file + * Sets if the given {@link Extension} is enabled. * - * @param file the file to get the description from - * @return the extension's description - * @throws InvalidDescriptionException + * @param extension the extension to enable + * @param enabled if the extension should be enabled */ - ExtensionDescription extensionDescription(File file) throws InvalidDescriptionException; + protected abstract void setEnabled(@NonNull Extension extension, boolean enabled); /** - * Gets a class by its name from the extension's classloader + * Gets the given {@link Extension}'s data folder. * - * @param name the name of the class - * @return the class - * @throws ClassNotFoundException + * @param extension the extension + * @return the data folder of the given extension */ - Class classByName(final String name) throws ClassNotFoundException; + @NonNull + protected abstract Path dataFolder(@NonNull Extension extension); /** - * Enables an extension + * Gets the given {@link Extension}'s {@link ExtensionDescription}. * - * @param extension the extension to enable + * @param extension the extension + * @return the description of the given extension + */ + @NonNull + protected abstract ExtensionDescription description(@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 */ - void enableExtension(GeyserExtension extension); + protected abstract void loadAllExtensions(@NonNull ExtensionManager extensionManager); /** - * Disables an extension + * Registers the given {@link Extension} with the given {@link ExtensionManager}. * - * @param extension the extension to disable + * @param extension the extension + * @param extensionManager the extension manager */ - void disableExtension(GeyserExtension extension); -} + protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { + extensionManager.register(extension, this); + } +} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java new file mode 100644 index 00000000000..65387a8c743 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -0,0 +1,120 @@ +/* + * 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 net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Map; + +/** + * 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(@NotNull 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 the {@link ExtensionLoader} responsible for loading + * the given {@link Extension}. + * + * @return the extension loader for loading the given extension + */ + @Nullable + public abstract ExtensionLoader extensionLoader(@NotNull 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} with the given identifier. + * + * @param identifier the identifier + * @return the extension loader at the given identifier + */ + @Nullable + public abstract ExtensionLoader extensionLoader(@NonNull Key identifier); + + /** + * Registers an {@link ExtensionLoader} with the given identifier. + * + * @param identifier the identifier + * @param extensionLoader the extension loader + */ + public abstract void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader); + + /** + * Gets all the currently registered {@link ExtensionLoader}s. + * + * @return all the currently registered extension loaders + */ + @NonNull + public abstract Map extensionLoaders(); + + /** + * Registers an {@link Extension} with the given {@link ExtensionLoader}. + * + * @param extension the extension + * @param loader the loader + */ + public abstract void register(@NotNull Extension extension, @NotNull ExtensionLoader loader); + + /** + * Loads all extensions from the given {@link ExtensionLoader}. + */ + protected final void loadAllExtensions(@NotNull ExtensionLoader extensionLoader) { + extensionLoader.loadAllExtensions(this); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java deleted file mode 100644 index bd53bafd3f3..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.geysermc.api.GeyserApiBase; -import java.io.*; -import java.net.URL; -import java.net.URLConnection; - -/** - * This class is to be extended by a Geyser extension - */ -public class GeyserExtension { - private boolean initialized = false; - private boolean enabled = false; - private File file = null; - private File dataFolder = null; - private ClassLoader classLoader = null; - private ExtensionLoader loader = null; - private ExtensionLogger logger = null; - private ExtensionDescription description = null; - private GeyserApiBase api = null; - - /** - * Called when the extension is loaded - */ - public void onLoad() { - - } - - /** - * Called when the extension is enabled - */ - public void onEnable() { - - } - - /** - * Called when the extension is disabled - */ - public void onDisable() { - - } - - /** - * Gets if the extension is enabled - * - * @return true if the extension is enabled - */ - public boolean isEnabled() { - return this.enabled; - } - - /** - * Enables or disables the extension - */ - public void setEnabled(boolean value) { - if (this.enabled != value) { - this.enabled = value; - if (this.enabled) { - onEnable(); - } else { - onDisable(); - } - } - } - - /** - * Gets the extension's data folder - * - * @return the extension's data folder - */ - public File dataFolder() { - return this.dataFolder; - } - - /** - * Gets the extension's description - * - * @return the extension's description - */ - public ExtensionDescription description() { - return this.description; - } - - /** - * Gets the extension's name - * - * @return the extension's name - */ - public String name() { - return this.description.name(); - } - - public void init(GeyserApiBase api, ExtensionLoader loader, ExtensionLogger logger, ExtensionDescription description, File dataFolder, File file) { - if (!this.initialized) { - this.initialized = true; - this.file = file; - this.dataFolder = dataFolder; - this.classLoader = this.getClass().getClassLoader(); - this.loader = loader; - this.logger = logger; - this.description = description; - this.api = api; - } - } - - /** - * Gets a resource from the extension jar file - * - * @param filename the file name - * @return the input stream - */ - public InputStream getResource(String filename) { - if (filename == null) { - throw new IllegalArgumentException("Filename cannot be null"); - } - - try { - URL url = this.classLoader.getResource(filename); - - if (url == null) { - return null; - } - - URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - return connection.getInputStream(); - } catch (IOException ex) { - return null; - } - } - - /** - * Saves a resource from the extension jar file to the extension's data folder - * - * @param filename the file name - * @param replace whether to replace the file if it already exists - */ - public void saveResource(String filename, boolean replace) { - if (filename == null || filename.equals("")) { - throw new IllegalArgumentException("ResourcePath cannot be null or empty"); - } - - filename = filename.replace('\\', '/'); - InputStream in = getResource(filename); - if (in == null) { - throw new IllegalArgumentException("The embedded resource '" + filename + "' cannot be found in " + file); - } - - File outFile = new File(dataFolder, filename); - int lastIndex = filename.lastIndexOf('/'); - File outDir = new File(dataFolder, filename.substring(0, Math.max(lastIndex, 0))); - - if (!outDir.exists()) { - outDir.mkdirs(); - } - - try { - if (!outFile.exists() || replace) { - OutputStream out = new FileOutputStream(outFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - out.close(); - in.close(); - } else { - this.logger.warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); - } - } catch (IOException ex) { - this.logger.severe("Could not save " + outFile.getName() + " to " + outFile, ex); - } - } - - /** - * Gets the extension's class loader - * - * @return the extension's class loader - */ - public ClassLoader classLoader() { - return this.classLoader; - } - - /** - * Gets the extension's loader - * - * @return the extension's loader - */ - public ExtensionLoader extensionLoader() { - return this.loader; - } - - /** - * Gets the extension's logger - * - * @return the extension's logger - */ - public ExtensionLogger logger() { - return this.logger; - } - - /** - * Gets the {@link GeyserApiBase} instance - * - * @return the {@link GeyserApiBase} instance - */ - public GeyserApiBase geyserApi() { - return this.api; - } -} diff --git a/api/pom.xml b/api/pom.xml index b3d0262eae3..f66b982fb3c 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,6 +7,7 @@ org.geysermc geyser-parent 2.0.0-SNAPSHOT + ../pom.xml api-parent diff --git a/core/pom.xml b/core/pom.xml index 4da6bdbe04b..4837660f986 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -11,7 +11,6 @@ core - 4.9.3 8.5.2 2.12.4 4.1.66.Final diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 2fbcbadddba..6e740cb448e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -157,8 +157,9 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { MessageTranslator.init(); MinecraftLocale.init(); - extensionManager = new GeyserExtensionManager(); - extensionManager.init(); + /* Load Extensions */ + this.extensionManager = new GeyserExtensionManager(); + this.extensionManager.init(); start(); @@ -203,7 +204,7 @@ private void start() { ResourcePack.loadPacks(); - extensionManager.enableExtensions(); + this.extensionManager.enableExtensions(); if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting @@ -465,7 +466,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - extensionManager.disableExtensions(); + this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } @@ -488,6 +489,11 @@ public boolean productionEnvironment() { return !"DEV".equals(GeyserImpl.VERSION); } + @Override + public GeyserExtensionManager extensionManager() { + return this.extensionManager; + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); @@ -518,10 +524,6 @@ public WorldManager getWorldManager() { return bootstrap.getWorldManager(); } - public GeyserExtensionManager getExtensionManager() { - return extensionManager; - } - public static GeyserImpl getInstance() { return instance; } 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 3377f7ee5e0..28e81d8e70a 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -36,8 +36,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.extension.GeyserExtension; -import org.geysermc.geyser.extension.GeyserExtensionManager; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.network.MinecraftProtocol; @@ -127,7 +127,7 @@ public DumpInfo(boolean addLog) { this.flagsInfo = new FlagsInfo(); this.extensionInfo = new ArrayList<>(); - for (GeyserExtension extension : GeyserImpl.getInstance().getExtensionManager().getExtensions().values()) { + 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())); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 67363a40f51..426cd1de75c 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,24 +25,25 @@ package org.geysermc.geyser.extension; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; -import org.geysermc.geyser.api.extension.GeyserExtension; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; -import java.io.File; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Set; public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; - private final Map classes = new HashMap<>(); - public GeyserExtension extension; + private final Map> classes = new HashMap<>(); + private final Extension extension; - public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { - super(new URL[] { file.toURI().toURL() }, parent); + public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, Path path) throws InvalidExtensionException, MalformedURLException { + super(new URL[] { path.toUri().toURL() }, parent); this.loader = loader; try { @@ -53,15 +54,15 @@ public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader pare throw new InvalidExtensionException("Class " + description.main() + " not found, extension cannot be loaded", ex); } - Class extensionClass; + Class extensionClass; try { - extensionClass = jarClass.asSubclass(GeyserExtension.class); + extensionClass = jarClass.asSubclass(Extension.class); } catch (ClassCastException ex) { throw new InvalidExtensionException("Main class " + description.main() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); } - extension = extensionClass.newInstance(); - } catch (IllegalAccessException ex) { + this.extension = 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); @@ -74,26 +75,28 @@ protected Class findClass(String name) throws ClassNotFoundException { } protected Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { - if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("org.geysermc.platform.") || name.startsWith("org.geysermc.floodgate.") || name.startsWith("org.geysermc.api.") || name.startsWith("org.geysermc.processor.") || name.startsWith("net.minecraft.")) { + if (name.startsWith("org.geysermc.geyser.") || name.startsWith("net.minecraft.")) { throw new ClassNotFoundException(name); } - Class result = classes.get(name); + Class result = this.classes.get(name); if (result == null) { if (checkGlobal) { - result = loader.classByName(name); + result = this.loader.classByName(name); } + if (result == null) { result = super.findClass(name); if (result != null) { - loader.setClass(name, result); + this.loader.setClass(name, result); } } - classes.put(name, result); + + this.classes.put(name, result); } return result; } - Set getClasses() { - return classes.keySet(); + public Extension extension() { + return this.extension; } } 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..5b2e0184217 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java @@ -0,0 +1,50 @@ +/* + * 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.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; + + @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 index f8a3d9bbe11..5c8ff0ae214 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,78 +25,52 @@ package org.geysermc.geyser.extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; -import java.io.InputStream; +import java.io.Reader; import java.util.*; -public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { - private String name; - private String main; - private String api; - private String version; - private final List authors = new ArrayList<>(); - - public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescriptionException { +public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { + @SuppressWarnings("unchecked") + public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml yaml = new Yaml(dumperOptions); - this.loadMap(yaml.loadAs(inputStream, LinkedHashMap.class)); - } + Map yamlMap = yaml.loadAs(reader, LinkedHashMap.class); - private void loadMap(Map yamlMap) throws InvalidDescriptionException { - this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (this.name.equals("")) { + String name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); + if (name.isBlank()) { throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); } - this.name = this.name.replace(" ", "_"); - this.version = String.valueOf(yamlMap.get("version")); - this.main = (String) yamlMap.get("main"); + + name = name.replace(" ", "_"); + String version = String.valueOf(yamlMap.get("version")); + String main = (String) yamlMap.get("main"); + String apiVersion; Object api = yamlMap.get("api"); if (api instanceof String) { - this.api = (String) api; + apiVersion = (String) api; } else { - this.api = "0.0.0"; throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); } + List authors = new ArrayList<>(); if (yamlMap.containsKey("author")) { - this.authors.add((String) yamlMap.get("author")); + authors.add((String) yamlMap.get("author")); } if (yamlMap.containsKey("authors")) { try { - this.authors.addAll((Collection) yamlMap.get("authors")); + authors.addAll((Collection) yamlMap.get("authors")); } catch (Exception e) { throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); } } - } - - @Override - public String name() { - return this.name; - } - - @Override - public String main() { - return this.main; - } - - @Override - public String apiVersion() { - return api; - } - - @Override - public String version() { - return this.version; - } - @Override - public List authors() { - return this.authors; + return new GeyserExtensionDescription(name, main, apiVersion, version, authors); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index f029eb79767..a539dcb15ed 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,95 +25,81 @@ package org.geysermc.geyser.extension; -import org.geysermc.api.Geyser; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; +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.GeyserExtension; +import org.geysermc.geyser.api.extension.ExtensionLogger; +import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import org.geysermc.geyser.text.GeyserLocale; -import java.io.*; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.Objects; import java.util.regex.Pattern; -public class GeyserExtensionLoader implements ExtensionLoader { - private final Map classes = new HashMap<>(); - private final Map classLoaders = new HashMap<>(); +@RequiredArgsConstructor +public class GeyserExtensionLoader extends ExtensionLoader { + private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); + private static final Pattern API_VERSION_PATTERN = Pattern.compile("^[0-9]+\\.[0-9]+\\.[0-9]+$"); - @Override - public GeyserExtension loadExtension(File file) throws InvalidExtensionException { - if (file == null) { - throw new InvalidExtensionException("File is null"); - } + private final Map> classes = new HashMap<>(); + private final Map classLoaders = new HashMap<>(); + private final Map extensionContainers = new HashMap<>(); - if (!file.exists()) { - throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist"); + public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtensionException, InvalidDescriptionException { + if (path == null) { + throw new InvalidExtensionException("Path is null"); } - final GeyserExtensionDescription description; - try { - description = extensionDescription(file); - } catch (InvalidDescriptionException e) { - throw new InvalidExtensionException(e); + if (Files.notExists(path)) { + throw new InvalidExtensionException(new NoSuchFileException(path.toString()) + " does not exist"); } - final File parentFile = file.getParentFile(); - final File dataFolder = new File(parentFile, description.name()); - if (dataFolder.exists() && !dataFolder.isDirectory()) { - throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.name() + "!"); + GeyserExtensionDescription description = this.extensionDescription(path); + 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(), description, file); + loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, path); } catch (Throwable e) { throw new InvalidExtensionException(e); } - classLoaders.put(description.name(), loader); - setup(loader.extension, description, dataFolder, file); - return loader.extension; + this.classLoaders.put(description.name(), loader); + return this.setup(loader.extension(), description, dataFolder); } - private void setup(GeyserExtension extension, GeyserExtensionDescription description, File dataFolder, File file) { + private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder) { GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); - extension.init(Geyser.api(), this, logger, description, dataFolder, file); + GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger); extension.onLoad(); + return container; } - @Override - public GeyserExtensionDescription extensionDescription(File file) throws InvalidDescriptionException { - JarFile jarFile = null; - InputStream stream = null; - - try { - jarFile = new JarFile(file); - - JarEntry descriptionEntry = jarFile.getJarEntry("extension.yml"); - if (descriptionEntry == null) { - throw new InvalidDescriptionException(new FileNotFoundException("extension.yml") + " does not exist in the jar file!"); - } - - stream = jarFile.getInputStream(descriptionEntry); - return new GeyserExtensionDescription(stream); - } catch (IOException e) { - throw new InvalidDescriptionException(e); - } finally { - if (jarFile != null) { - try { - jarFile.close(); - } catch (IOException e) { - } - } - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } + 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"); + return GeyserExtensionDescription.fromYaml(Files.newBufferedReader(extensionYml)); + } catch (IOException ex) { + throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); } } @@ -121,14 +107,13 @@ public Pattern[] extensionFilters() { return new Pattern[] { Pattern.compile("^.+\\.jar$") }; } - @Override public Class classByName(final String name) throws ClassNotFoundException{ - Class clazz = classes.get(name); + Class clazz = this.classes.get(name); try { - for(GeyserExtensionClassLoader loader : classLoaders.values()) { + for(GeyserExtensionClassLoader loader : this.classLoaders.values()) { try { clazz = loader.findClass(name,false); - } catch(NullPointerException e) { + } catch(NullPointerException ignored) { } } return clazz; @@ -138,28 +123,132 @@ public Class classByName(final String name) throws ClassNotFoundException{ } void setClass(String name, final Class clazz) { - if (!classes.containsKey(name)) { - classes.put(name,clazz); + if (!this.classes.containsKey(name)) { + this.classes.put(name,clazz); } } - void removeClass(String name) { - classes.remove(name); + @Override + protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { + // noinspection ConstantConditions + if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); + return; + } + + if (!GeyserImpl.VERSION.contains(".")) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); + return; + } + + String[] apiVersion = GeyserImpl.VERSION.split("\\."); + + try { + if (Files.notExists(EXTENSION_DIRECTORY)) { + Files.createDirectory(EXTENSION_DIRECTORY); + } + + Map extensions = new LinkedHashMap<>(); + Map loadedExtensions = new LinkedHashMap<>(); + + Pattern[] extensionFilters = this.extensionFilters(); + + Files.walk(EXTENSION_DIRECTORY).forEach(path -> { + if (Files.isDirectory(path)) { + return; + } + + for (Pattern filter : extensionFilters) { + if (!filter.matcher(path.getFileName().toString()).matches()) { + return; + } + } + + try { + ExtensionDescription description = this.extensionDescription(path); + if (description == null) { + return; + } + + 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; + } + + try { + // Check the format: majorVersion.minorVersion.patch + if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { + throw new IllegalArgumentException(); + } + } catch (NullPointerException | IllegalArgumentException e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + String[] versionArray = description.apiVersion().split("\\."); + + // Completely different API version + if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + // If the extension requires new API features, being backwards compatible + if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + extensions.put(name, path); + loadedExtensions.put(name, this.loadExtension(path)); + } 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 - public void enableExtension(GeyserExtension extension) { - if (!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); - extension.setEnabled(true); - } + protected boolean isEnabled(@NonNull Extension extension) { + return this.extensionContainers.get(extension).enabled; } @Override - public void disableExtension(GeyserExtension extension) { - if (extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); - extension.setEnabled(false); + protected void setEnabled(@NonNull Extension extension, boolean enabled) { + boolean isEnabled = this.extensionContainers.get(extension).enabled; + if (isEnabled != enabled) { + this.extensionContainers.get(extension).enabled = enabled; + if (enabled) { + extension.onEnable(); + } else { + extension.onDisable(); + } } } + + @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 ExtensionLogger logger(@NonNull Extension extension) { + return this.extensionContainers.get(extension).logger(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 1690531826d..a72712f1657 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -25,205 +25,126 @@ package org.geysermc.geyser.extension; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.extension.ExtensionDescription; -import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionManager; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.text.GeyserLocale; -import java.io.File; -import java.lang.reflect.Constructor; +import org.jetbrains.annotations.NotNull; + import java.util.*; -import java.util.regex.Pattern; -public class GeyserExtensionManager { - protected Map extensions = new LinkedHashMap<>(); - protected Map fileAssociations = new HashMap<>(); +public class GeyserExtensionManager extends ExtensionManager { + private static final Key BASE_KEY = Key.key("geysermc", "base"); + + private final Map extensions = new LinkedHashMap<>(); + private final Map extensionsLoaders = new LinkedHashMap<>(); public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - this.registerInterface(GeyserExtensionLoader.class); - this.loadExtensions(new File("extensions")); + this.registerExtensionLoader(BASE_KEY, new GeyserExtensionLoader()); + + for (ExtensionLoader loader : this.extensionLoaders().values()) { + this.loadAllExtensions(loader); + } GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); } - public GeyserExtension getExtension(String name) { + @Override + public Extension extension(@NonNull String name) { if (this.extensions.containsKey(name)) { return this.extensions.get(name); } - return null; - } - public Map getExtensions() { - return this.extensions; + return null; } - public void registerInterface(Class loader) { - GeyserExtensionLoader instance; - - if (GeyserExtensionLoader.class.isAssignableFrom(loader)) { - Constructor constructor; - + @Override + public void enable(@NonNull Extension extension) { + if (!extension.isEnabled()) { try { - constructor = loader.getConstructor(); - instance = constructor.newInstance(); - } catch (NoSuchMethodException ex) { // This should never happen - String className = loader.getName(); - - throw new IllegalArgumentException("Class " + className + " does not have a public constructor", ex); - } catch (Exception ex) { // This should never happen - throw new IllegalArgumentException("Unexpected exception " + ex.getClass().getName() + " while attempting to construct a new instance of " + loader.getName(), ex); + this.enableExtension(extension); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e); + this.disable(extension); } - } else { - throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader"); } + } - Pattern[] patterns = instance.extensionFilters(); - - synchronized (this) { - for (Pattern pattern : patterns) { - fileAssociations.put(pattern, instance); + @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 GeyserExtension loadExtension(File file, Map loaders) { - for (GeyserExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) { - for (Pattern pattern : loader.extensionFilters()) { - if (pattern.matcher(file.getName()).matches()) { - try { - ExtensionDescription description = loader.extensionDescription(file); - if (description != null) { - GeyserExtension extension = loader.loadExtension(file); - - if (extension != null) { - this.extensions.put(extension.description().name(), extension); - - return extension; - } - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed"), e); - return null; - } - } - } + public void enableExtension(Extension extension) { + if (!extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); + extension.setEnabled(true); } - - return null; } - public Map loadExtensions(File dictionary) { - if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { // If your IDE says this is always true, ignore it, it isn't. - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); - return new HashMap<>(); - } - if (!GeyserImpl.VERSION.contains(".")) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); - return new HashMap<>(); + private void disableExtension(@NonNull Extension extension) { + if (extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); + extension.setEnabled(false); } + } - String[] apiVersion = GeyserImpl.VERSION.split("\\."); - - if (!dictionary.exists()) { - dictionary.mkdir(); - } - if (!dictionary.isDirectory()) { - return new HashMap<>(); + public void enableExtensions() { + for (Extension extension : this.extensions()) { + this.enable(extension); } + } - Map extensions = new LinkedHashMap<>(); - Map loadedExtensions = new LinkedHashMap<>(); - - for (final GeyserExtensionLoader loader : this.fileAssociations.values()) { - for (File file : dictionary.listFiles((dir, name) -> { - for (Pattern pattern : loader.extensionFilters()) { - if (pattern.matcher(name).matches()) { - return true; - } - } - return false; - })) { - if (file.isDirectory()) { - continue; - } - - try { - ExtensionDescription description = loader.extensionDescription(file); - if (description != null) { - String name = description.name(); - - if (extensions.containsKey(name) || this.getExtension(name) != null) { - GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, file.getName())); - continue; - } - - try { - //Check the format: majorVersion.minorVersion.patch - if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.apiVersion())) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - String[] versionArray = description.apiVersion().split("\\."); - - //Completely different API version - if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - //If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - extensions.put(name, file); - loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations)); - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", file.getName(), dictionary.getAbsolutePath()), e); - } - } + public void disableExtensions() { + for (Extension extension : this.extensions()) { + this.disable(extension); } + } - return loadedExtensions; + @Override + public ExtensionLoader extensionLoader(@NonNull Extension extension) { + return this.extensionsLoaders.get(extension); } - public void enableExtension(GeyserExtension extension) { - if (!extension.isEnabled()) { - try { - extension.extensionLoader().enableExtension(extension); - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e); - this.disableExtension(extension); - } - } + @NonNull + @Override + public Collection extensions() { + return Collections.unmodifiableCollection(this.extensions.values()); } - public void disableExtension(GeyserExtension extension) { - if (extension.isEnabled()) { - try { - extension.extensionLoader().disableExtension(extension); - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.failed", extension.name()), e); - } - } + @Nullable + @Override + public ExtensionLoader extensionLoader(@NonNull Key identifier) { + return Registries.EXTENSION_LOADERS.get(identifier); } - public void enableExtensions() { - for (GeyserExtension extension : this.getExtensions().values()) { - this.enableExtension(extension); - } + @Override + public void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader) { + Registries.EXTENSION_LOADERS.register(identifier, extensionLoader); } - public void disableExtensions() { - for (GeyserExtension extension : this.getExtensions().values()) { - this.disableExtension(extension); - } + @NonNull + @Override + public Map extensionLoaders() { + return Collections.unmodifiableMap(Registries.EXTENSION_LOADERS.get()); + } + + @Override + public void register(@NotNull Extension extension, @NotNull ExtensionLoader loader) { + this.extensionsLoaders.put(extension, loader); + this.extensions.put(extension.name(), extension); } } 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 3b0c1f13805..173bfbd1a76 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -42,6 +41,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.extension.ExtensionLoader; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.translator.collision.BlockCollision; @@ -62,7 +63,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.IntFunction; /** * Holds all the common registries in Geyser. @@ -113,6 +113,11 @@ public final class Registries { */ public static final SimpleMappedRegistry> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(() -> new EnumMap<>(EntityType.class))); + /** + * A map containing all the extension loaders. + */ + public static final SimpleMappedRegistry EXTENSION_LOADERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + /** * A map containing all Java entity identifiers and their respective Geyser definitions */ diff --git a/pom.xml b/pom.xml index 004d58666e4..db512e4777f 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,8 @@ UTF-8 16 16 + + 4.9.3 From b82c66168883045896bae92866a5ecb580b10ca8 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 15 Jan 2022 22:54:08 -0600 Subject: [PATCH 093/358] Implement simple event system --- api/geyser/pom.xml | 6 + .../org/geysermc/geyser/api/GeyserApi.java | 9 ++ .../api/connection/GeyserConnection.java | 2 +- .../geyser/api/event/Cancellable.java | 46 ++++++++ .../org/geysermc/geyser/api/event/Event.java | 41 +++++++ .../geysermc/geyser/api/event/EventBus.java | 100 ++++++++++++++++ .../geyser/api/event/EventSubscription.java | 93 +++++++++++++++ .../geysermc/geyser/api/event/Subscribe.java | 99 ++++++++++++++++ .../lifecycle/GeyserPostInitializeEvent.java | 34 ++++++ .../event/lifecycle/GeyserShutdownEvent.java | 34 ++++++ core/pom.xml | 7 ++ .../java/org/geysermc/geyser/GeyserImpl.java | 15 +++ .../geysermc/geyser/event/GeyserEventBus.java | 110 ++++++++++++++++++ .../geyser/event/GeyserEventSubscription.java | 82 +++++++++++++ 14 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java create mode 100644 core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java create mode 100644 core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 5c598f65b19..88f1cafdc12 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -32,6 +32,12 @@ ${adventure.version} compile + + net.kyori + event-api + 3.0.0 + provided + org.geysermc base-api 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 d5fd0938127..dbdee8b4e0f 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 @@ -30,6 +30,7 @@ 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.extension.ExtensionManager; import java.util.List; @@ -88,6 +89,14 @@ public interface GeyserApi extends GeyserApiBase { */ ExtensionManager extensionManager(); + /** + * Gets the {@link EventBus} for handling + * Geyser events. + * + * @return the event bus + */ + EventBus eventBus(); + /** * Gets the current {@link GeyserApiBase} instance. * 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 b673b60f612..036c7c591ab 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 @@ -28,7 +28,7 @@ import org.geysermc.api.connection.Connection; /** - * Represents a player session used in Geyser. + * Represents a player connection used in Geyser. */ public interface GeyserConnection extends Connection { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java new file mode 100644 index 00000000000..94d0b832dfa --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.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.api.event; + +/** + * Represents a cancellable event. + */ +public interface Cancellable { + + /** + * Gets if the event is cancelled. + * + * @return if the event is cancelled + */ + boolean isCancelled(); + + /** + * Cancels the event. + * + * @param cancelled if the event is cancelled + */ + void setCancelled(boolean cancelled); +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java new file mode 100644 index 00000000000..c32e1701e03 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.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; + +/** + * Represents an event. + */ +public interface Event { + + /** + * Gets if the event is async. + * + * @return if the event is async + */ + default boolean isAsync() { + return false; + } +} 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..a26e8c653b6 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.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.api.event; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.Set; +import java.util.function.Consumer; + +/** + * Represents a bus capable of subscribing + * or "listening" to events and firing them. + */ +public interface EventBus { + + /** + * Subscribes to the given event see {@link EventSubscription}. + * + * @param eventClass the class of the event + * @param consumer the consumer for handling the event + * @param the event class + * @return the event subscription + */ + @NonNull + EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer); + + /** + * Subscribes to the given event see {@link EventSubscription}. + * + * The difference between this method and {@link #subscribe(Class, Consumer)} + * is that this method takes in an extension parameter which allows for + * the event to be unsubscribed upon extension disable and reloads. + * + * @param extension the extension to subscribe the event to + * @param eventClass the class of the event + * @param consumer the consumer for handling the event + * @param the event class + * @return the event subscription + */ + @NonNull + EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer); + + /** + * Unsubscribes the given {@link EventSubscription}. + * + * @param subscription the event subscription + */ + void unsubscribe(@NonNull EventSubscription subscription); + + /** + * Registers events for the given listener. + * + * @param extension the extension registering the event + * @param eventHolder the listener + */ + void register(@NonNull Extension extension, @NonNull Object eventHolder); + + /** + * Fires the given {@link Event}. + * + * @param event the event to fire + * + * @return true if the event successfully fired + */ + boolean fire(@NonNull Event event); + + /** + * Gets the subscriptions for the given event class. + * + * @param eventClass the event class + * @param the value + * @return the subscriptions for the event class + */ + @NonNull + Set> subscriptions(@NonNull Class eventClass); +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java new file mode 100644 index 00000000000..65939a7b0b6 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.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.api.event; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.function.Consumer; + +/** + * Represents a subscribed listener to a {@link Event}. Wraps around + * the event and is capable of unsubscribing from the event or give + * information about it. + * + * @param the class of the event + */ +public interface EventSubscription { + + /** + * Gets the event class. + * + * @return the event class + */ + @NonNull + Class eventClass(); + + /** + * Gets the consumer responsible for handling + * this event. + * + * @return the consumer responsible for this event + */ + @NonNull + Consumer eventConsumer(); + + /** + * Gets the {@link Extension} that owns this + * event subscription. + * + * @return the extension that owns this subscription + */ + @NonNull + Extension owner(); + + /** + * Gets the priority of this event subscription. + * + * @return the priority of this event subscription + */ + Subscribe.Priority priority(); + + /** + * Gets if this event subscription is active. + * + * @return if this event subscription is active + */ + boolean isActive(); + + /** + * Unsubscribes from this event listener + */ + void unsubscribe(); + + /** + * Invokes the given event + * + * @param event the event + */ + void invoke(@NonNull T event) throws Throwable; +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java new file mode 100644 index 00000000000..581aa6fa051 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java @@ -0,0 +1,99 @@ +/* + * 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 lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to signify the given method is + * an {@link Event}. Only should be applied to methods + * where the class containing them is designated for + * events specifically. + * + * When using {@link EventBus#subscribe}, this annotation should + * not be applied whatsoever as it will have no use and potentially + * throw errors due to it being used wrongly. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Subscribe { + + /** + * The {@link Priority} of the event + * + * @return the priority of the event + */ + @NonNull + Priority priority() default Priority.NORMAL; + + /** + * Represents the priority of an event. + */ + @Accessors(fluent = true) + @Getter + @AllArgsConstructor + enum Priority { + + /** + * The lowest priority. Called first to + * allow for other events to customize + * the outcome + */ + LOWEST(net.kyori.event.PostOrders.FIRST), + + /** + * The second lowest priority. + */ + LOW(net.kyori.event.PostOrders.EARLY), + + /** + * Normal priority. Event is neither + * important nor unimportant + */ + NORMAL(net.kyori.event.PostOrders.NORMAL), + + /** + * The second highest priority + */ + HIGH(net.kyori.event.PostOrders.LATE), + + /** + * The highest of importance! Event is called + * last and has the final say in the outcome + */ + HIGHEST(net.kyori.event.PostOrders.LAST); + + private final int postOrder; + } +} \ No newline at end of file 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..94d3d8cb864 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.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.api.event.lifecycle; + +import org.geysermc.geyser.api.event.Event; + +/** + * Called when Geyser has completed initializing. + */ +public class GeyserPostInitializeEvent 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..baf8b3445f6 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.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.api.event.lifecycle; + +import org.geysermc.geyser.api.event.Event; + +/** + * Called when Geyser is shutting down. + */ +public class GeyserShutdownEvent implements Event { +} diff --git a/core/pom.xml b/core/pom.xml index 4837660f986..6f4e0fa3c83 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -244,6 +244,13 @@ ${adventure.version} compile + + + net.kyori + event-api + 3.0.0 + compile + junit diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 6e740cb448e..573934b919d 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -49,9 +49,13 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; import org.geysermc.geyser.command.CommandManager; 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; @@ -123,6 +127,7 @@ public class GeyserImpl implements GeyserApi { private final PlatformType platformType; private final GeyserBootstrap bootstrap; + private final EventBus eventBus; private final GeyserExtensionManager extensionManager; private Metrics metrics; @@ -158,6 +163,7 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { MinecraftLocale.init(); /* Load Extensions */ + this.eventBus = new GeyserEventBus(); this.extensionManager = new GeyserExtensionManager(); this.extensionManager.init(); @@ -411,6 +417,8 @@ private void start() { } newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); + + this.eventBus.fire(new GeyserPostInitializeEvent()); } @Override @@ -466,6 +474,8 @@ public void shutdown() { ResourcePack.PACKS.clear(); + this.eventBus.fire(new GeyserShutdownEvent()); + this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); @@ -494,6 +504,11 @@ public GeyserExtensionManager extensionManager() { return this.extensionManager; } + @Override + public EventBus eventBus() { + return this.eventBus; + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); 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..93a0428266c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -0,0 +1,110 @@ +/* + * 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 net.kyori.event.SimpleEventBus; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventSubscription; +import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.extension.Extension; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class GeyserEventBus implements EventBus { + private final SimpleEventBus bus = new SimpleEventBus<>(Event.class); + + @NonNull + @Override + public EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer) { + return this.subscribe(eventClass, consumer, null, Subscribe.Priority.NORMAL); + } + + @NonNull + @Override + public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { + return this.subscribe(eventClass, consumer, extension, Subscribe.Priority.NORMAL); + } + + @Override + public void unsubscribe(@NonNull EventSubscription subscription) { + this.bus.unregister((GeyserEventSubscription) subscription); + } + + @SuppressWarnings("unchecked") + @Override + public void register(@NonNull Extension extension, @NonNull Object eventHolder) { + for (Method method : eventHolder.getClass().getMethods()) { + if (!method.isAnnotationPresent(Subscribe.class)) { + continue; + } + + if (method.getParameterCount() > 1) { + continue; + } + + if (!Event.class.isAssignableFrom(method.getParameters()[0].getType())) { + continue; + } + + Subscribe subscribe = method.getAnnotation(Subscribe.class); + this.subscribe((Class) method.getParameters()[0].getType(), (event) -> { + try { + method.invoke(eventHolder, event); + } catch (IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + } + }, extension, subscribe.priority()); + } + } + + @Override + public boolean fire(@NonNull Event event) { + return this.bus.post(event).wasSuccessful(); + } + + @SuppressWarnings("unchecked") + @NonNull + @Override + public Set> subscriptions(@NonNull Class eventClass) { + return bus.subscribers().values() + .stream() + .filter(sub -> sub instanceof EventSubscription && ((EventSubscription) sub).eventClass().isAssignableFrom(eventClass)) + .map(sub -> ((EventSubscription) sub)) + .collect(Collectors.toSet()); + } + + private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.Priority priority) { + GeyserEventSubscription eventSubscription = new GeyserEventSubscription<>(this, eventClass, handler, extension, priority); + this.bus.register(eventClass, eventSubscription); + return eventSubscription; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java new file mode 100644 index 00000000000..e160291c93a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.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.event; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import net.kyori.event.EventSubscriber; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventSubscription; +import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.function.Consumer; + +@Getter +@Accessors(fluent = true) +@RequiredArgsConstructor +public class GeyserEventSubscription implements EventSubscription, EventSubscriber { + private final EventBus eventBus; + private final Class eventClass; + private final Consumer eventConsumer; + private final Extension owner; + private final Subscribe.Priority priority; + @Getter(AccessLevel.NONE) private boolean active; + + @Override + public boolean isActive() { + return this.active; + } + + @Override + public void unsubscribe() { + if (!this.active) { + return; + } + + this.active = false; + this.eventBus.unsubscribe(this); + } + + @Override + public void invoke(@NonNull T event) throws Throwable { + try { + this.eventConsumer.accept(event); + } catch (Throwable ex) { + this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); + ex.printStackTrace(); + } + } + + @Override + public int postOrder() { + return this.priority.postOrder(); + } +} From 57345fa102cd45cf3b7f2e59b40c2bad64145e8b Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 15 Jan 2022 23:01:40 -0600 Subject: [PATCH 094/358] Event owner can be null --- .../java/org/geysermc/geyser/api/event/EventSubscription.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java index 65939a7b0b6..daa05ab21bf 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.extension.Extension; import java.util.function.Consumer; @@ -62,7 +63,7 @@ public interface EventSubscription { * * @return the extension that owns this subscription */ - @NonNull + @Nullable Extension owner(); /** From 30303d5f16bf10212bcbc5a52f2fc7bf50b068db Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 15:09:53 -0600 Subject: [PATCH 095/358] Implement support for adding Geyser subcommands --- api/geyser/pom.xml | 14 +- .../org/geysermc/geyser/api/GeyserApi.java | 8 + .../geysermc/geyser/api/command/Command.java | 124 +++++++++ .../geyser/api/command/CommandExecutor.java | 44 +++ .../geyser/api/command/CommandManager.java | 67 +++++ .../geyser/api/command/CommandSource.java | 46 ++-- .../api/connection/GeyserConnection.java | 3 +- .../geysermc/geyser/api/event/EventBus.java | 20 +- .../geyser/api/event/EventSubscription.java | 2 +- .../geyser/api/event/ExtensionEventBus.java | 61 +++++ .../api/event/connection/ConnectionEvent.java | 47 ++++ .../downstream/ServerDefineCommandsEvent.java | 81 ++++++ .../lifecycle/GeyserDefineCommandsEvent.java | 42 +++ .../geyser/api/extension/Extension.java | 10 + .../geyser/api/extension/ExtensionLoader.java | 10 + .../bungeecord/GeyserBungeePlugin.java | 5 +- ...ndSender.java => BungeeCommandSource.java} | 10 +- .../command/GeyserBungeeCommandExecutor.java | 16 +- .../command/GeyserBungeeCommandManager.java | 6 +- .../platform/spigot/GeyserSpigotPlugin.java | 9 +- .../command/GeyserSpigotCommandExecutor.java | 14 +- .../command/GeyserSpigotCommandManager.java | 6 +- ...ndSender.java => SpigotCommandSource.java} | 8 +- .../platform/sponge/GeyserSpongePlugin.java | 5 +- .../command/GeyserSpongeCommandExecutor.java | 12 +- .../command/GeyserSpongeCommandManager.java | 6 +- ...ndSender.java => SpongeCommandSource.java} | 4 +- .../standalone/GeyserStandaloneBootstrap.java | 11 +- .../standalone/GeyserStandaloneLogger.java | 6 +- ...va => GeyserStandaloneCommandManager.java} | 8 +- .../standalone/gui/GeyserStandaloneGUI.java | 28 +- .../velocity/GeyserVelocityPlugin.java | 5 +- .../GeyserVelocityCommandExecutor.java | 16 +- .../command/GeyserVelocityCommandManager.java | 6 +- ...Sender.java => VelocityCommandSource.java} | 10 +- .../network/session/GeyserSession.java | 2 +- .../org/geysermc/geyser/GeyserBootstrap.java | 4 +- .../java/org/geysermc/geyser/GeyserImpl.java | 14 +- .../geyser/command/CommandManager.java | 122 --------- .../geyser/command/GeyserCommand.java | 28 +- ...ecutor.java => GeyserCommandExecutor.java} | 18 +- .../geyser/command/GeyserCommandManager.java | 256 ++++++++++++++++++ .../geyser/command/GeyserCommandSource.java | 43 +++ .../defaults/AdvancedTooltipsCommand.java | 6 +- .../command/defaults/AdvancementsCommand.java | 4 +- .../geyser/command/defaults/DumpCommand.java | 26 +- .../geyser/command/defaults/HelpCommand.java | 17 +- .../geyser/command/defaults/ListCommand.java | 6 +- .../command/defaults/OffhandCommand.java | 4 +- .../command/defaults/ReloadCommand.java | 6 +- .../command/defaults/SettingsCommand.java | 4 +- .../command/defaults/StatisticsCommand.java | 4 +- .../geyser/command/defaults/StopCommand.java | 6 +- .../command/defaults/VersionCommand.java | 14 +- .../geysermc/geyser/entity/type/Entity.java | 2 +- .../geysermc/geyser/event/GeyserEventBus.java | 13 +- .../extension/GeyserExtensionContainer.java | 2 + .../extension/GeyserExtensionLoader.java | 14 +- .../extension/GeyserExtensionManager.java | 7 +- .../event/GeyserExtensionEventBus.java | 87 ++++++ .../updater/AnvilInventoryUpdater.java | 4 +- .../geyser/network/UpstreamPacketHandler.java | 4 +- .../geyser/session/GeyserSession.java | 12 +- .../geyser/session/SessionManager.java | 2 +- .../session/cache/AdvancementsCache.java | 8 +- .../geyser/session/cache/BossBar.java | 4 +- .../inventory/item/ItemTranslator.java | 6 +- .../item/nbt/AxolotlBucketTranslator.java | 2 +- .../item/nbt/BasicItemTranslator.java | 2 +- .../item/nbt/PlayerHeadTranslator.java | 2 +- .../nbt/TropicalFishBucketTranslator.java | 8 +- .../BedrockCommandRequestTranslator.java | 4 +- .../bedrock/BedrockFilterTextTranslator.java | 4 +- .../protocol/java/JavaChatTranslator.java | 2 +- .../protocol/java/JavaCommandsTranslator.java | 16 +- .../java/JavaDisconnectTranslator.java | 2 +- .../java/JavaLoginDisconnectTranslator.java | 2 +- .../protocol/java/JavaLoginTranslator.java | 2 +- .../JavaUpdateAdvancementsTranslator.java | 4 +- .../inventory/JavaOpenScreenTranslator.java | 4 +- .../java/level/JavaGameEventTranslator.java | 2 +- .../java/level/JavaLevelEventTranslator.java | 2 +- .../JavaSetPlayerTeamTranslator.java | 8 +- .../title/JavaSetActionBarTextTranslator.java | 2 +- .../title/JavaSetSubtitleTextTranslator.java | 2 +- .../title/JavaSetTitleTextTranslator.java | 2 +- .../translator/text/MessageTranslator.java | 2 +- .../geyser/util/LoginEncryptionUtils.java | 12 +- .../geysermc/geyser/util/SettingsUtils.java | 2 +- .../geysermc/geyser/util/StatisticsUtils.java | 2 +- 90 files changed, 1207 insertions(+), 402 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java rename core/src/main/java/org/geysermc/geyser/command/CommandSender.java => api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java (66%) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java rename bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/{BungeeCommandSender.java => BungeeCommandSource.java} (89%) rename bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/{SpigotCommandSender.java => SpigotCommandSource.java} (95%) rename bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/{SpongeCommandSender.java => SpongeCommandSource.java} (94%) rename bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/{GeyserCommandManager.java => GeyserStandaloneCommandManager.java} (85%) rename bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/{VelocityCommandSender.java => VelocityCommandSource.java} (90%) delete mode 100644 core/src/main/java/org/geysermc/geyser/command/CommandManager.java rename core/src/main/java/org/geysermc/geyser/command/{CommandExecutor.java => GeyserCommandExecutor.java} (85%) create mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java create mode 100644 core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 88f1cafdc12..b2f44858966 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -26,6 +26,12 @@ 3.19.0 provided + + net.kyori + event-api + 3.0.0 + provided + net.kyori adventure-api @@ -33,10 +39,10 @@ compile - net.kyori - event-api - 3.0.0 - provided + org.yaml + snakeyaml + 1.27 + compile org.geysermc 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 dbdee8b4e0f..2bddc9ef585 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; import org.geysermc.api.GeyserApiBase; +import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; @@ -89,6 +90,13 @@ public interface GeyserApi extends GeyserApiBase { */ ExtensionManager extensionManager(); + /** + * Gets the {@link CommandManager}. + * + * @return the command manager + */ + CommandManager commandManager(); + /** * Gets the {@link EventBus} for handling * Geyser events. 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..2c48473c6dd --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -0,0 +1,124 @@ +/* + * 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 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 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; + } + + static Command.Builder builder(Class sourceType) { + return GeyserApi.api().commandManager().provideBuilder(sourceType); + } + + interface Builder { + + Builder name(String name); + + Builder description(String description); + + Builder permission(String permission); + + Builder aliases(List aliases); + + Builder executableOnConsole(boolean executableOnConsole); + + Builder subCommands(List subCommands); + + Builder bedrockOnly(boolean bedrockOnly); + + Builder executor(CommandExecutor executor); + + 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..d384d097c99 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * 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(T source, Command command, String[] args); +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java new file mode 100644 index 00000000000..864ba71a4ca --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java @@ -0,0 +1,67 @@ +/* + * 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 java.util.Map; + +/** + * Manages Bedrock commands within Geyser. + */ +public abstract class CommandManager { + + /** + * Provides a {@link Command.Builder}. + * + * @param sourceType the command source type + * @param the type + * @return a command builder + */ + protected abstract Command.Builder provideBuilder(Class sourceType); + + /** + * Registers the given {@link Command}. + * + * @param command the command to register + */ + public abstract void register(@NonNull Command command); + + /** + * Unregisters the given {@link Command}. + * + * @param command the command to unregister + */ + public abstract void unregister(@NonNull Command command); + + /** + * Gets all the registered {@link Command}s. + * + * @return all the registered commands + */ + @NonNull + public abstract Map commands(); +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java similarity index 66% rename from core/src/main/java/org/geysermc/geyser/command/CommandSender.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index d9d1bcfbc8a..aabf0c4e8d9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -23,45 +23,57 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.command; - -import org.geysermc.geyser.text.GeyserLocale; +package org.geysermc.geyser.api.command; /** - * Implemented on top of any class that can send a command. - * For example, it wraps around Spigot's CommandSender class. + * Represents an instance capable of sending commands. */ -public interface CommandSender { +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(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); } } - void sendMessage(String message); - /** - * @return true if the specified sender is from the console. + * If this source is the console. + * + * @return true if this source is the console */ boolean isConsole(); /** - * Returns the locale of the command sender. Defaults to the default locale at {@link GeyserLocale#getDefaultLocale()}. - * - * @return the locale of the command sender. + * Returns the locale of the command source. + * + * @return the locale of the command source. */ - default String getLocale() { - return GeyserLocale.getDefaultLocale(); - } + String locale(); /** - * Checks if the CommandSender has a permission + * Checks if this command source has the given permission * * @param permission The permission node to check - * @return true if the CommandSender has the requested permission, false if not + * @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 036c7c591ab..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 @@ -26,9 +26,10 @@ package org.geysermc.geyser.api.connection; import org.geysermc.api.connection.Connection; +import org.geysermc.geyser.api.command.CommandSource; /** * 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 index a26e8c653b6..0352dcc9e7e 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java @@ -40,18 +40,7 @@ public interface EventBus { /** * Subscribes to the given event see {@link EventSubscription}. * - * @param eventClass the class of the event - * @param consumer the consumer for handling the event - * @param the event class - * @return the event subscription - */ - @NonNull - EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer); - - /** - * Subscribes to the given event see {@link EventSubscription}. - * - * The difference between this method and {@link #subscribe(Class, Consumer)} + * The difference between this method and {@link ExtensionEventBus#subscribe(Class, Consumer)} * is that this method takes in an extension parameter which allows for * the event to be unsubscribed upon extension disable and reloads. * @@ -79,6 +68,13 @@ public interface EventBus { */ void register(@NonNull Extension extension, @NonNull Object eventHolder); + /** + * Unregisters all events from a given {@link Extension}. + * + * @param extension the extension + */ + void unregisterAll(@NonNull Extension extension); + /** * Fires the given {@link Event}. * diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java index daa05ab21bf..2870c0ba692 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java @@ -63,7 +63,7 @@ public interface EventSubscription { * * @return the extension that owns this subscription */ - @Nullable + @NonNull Extension owner(); /** 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..db0209e5ff9 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java @@ -0,0 +1,61 @@ +/* + * 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 java.util.function.Consumer; + +/** + * An {@link EventBus} with additional methods that implicitly + * set the extension instance. + * + */ +public interface ExtensionEventBus extends EventBus { + + /** + * Subscribes to the given event see {@link EventSubscription}. + * + * @param eventClass the class of the event + * @param consumer the consumer for handling the event + * @param the event class + * @return the event subscription + */ + @NonNull + EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer); + + /** + * Registers events for the given listener. + * + * @param eventHolder the listener + */ + void register(@NonNull Object eventHolder); + + /** + * Unregisters all events for this extension. + */ + void unregisterAll(); +} 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..9dcb6890815 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.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.connection; + +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.Event; + +/** + * An event that contains a {@link GeyserConnection}. + */ +@RequiredArgsConstructor +public abstract class ConnectionEvent implements Event { + private final GeyserConnection connection; + + /** + * Gets the {@link GeyserConnection}. + * + * @return the connection + */ + 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..559631acf06 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.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.event.downstream; + +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.Cancellable; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; + +import java.util.Set; + +/** + * Called when the downstream server defines the commands available on the server. + */ +public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable { + private final Set commands; + private boolean cancelled; + + public ServerDefineCommandsEvent(GeyserConnection connection, Set commands) { + super(connection); + this.commands = commands; + } + + /** + * A mutable collection of the commands sent over. + * + * @return a mutable collection of the commands sent over + */ + 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..7f64a462299 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.lifecycle; + +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandManager; +import org.geysermc.geyser.api.event.Event; + +import java.util.Map; + +/** + * Called when commands are defined within Geyser. + * + * @param commandManager the command manager + * @param commands a mutable list of the currently + * registered default commands + */ +public record GeyserDefineCommandsEvent(CommandManager commandManager, Map commands) implements Event { +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 097cabdc359..357165ffe4f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -27,6 +27,7 @@ import org.geysermc.api.GeyserApiBase; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.ExtensionEventBus; import java.nio.file.Path; @@ -80,6 +81,15 @@ default Path dataFolder() { return this.extensionLoader().dataFolder(this); } + /** + * Gets the {@link ExtensionEventBus}. + * + * @return the extension event bus + */ + default ExtensionEventBus eventBus() { + return this.extensionLoader().eventBus(this); + } + /** * Gets the {@link ExtensionManager}. * diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java index 651afd9eb8f..c84c37919b5 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -26,6 +26,7 @@ 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; @@ -68,6 +69,15 @@ public abstract class ExtensionLoader { @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}. * 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 f35082359a9..bb90e500043 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 @@ -30,7 +30,7 @@ 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.command.GeyserCommandManager; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -126,6 +126,7 @@ public void onEnable() { this.geyserInjector.initializeLocalChannel(this); this.geyserCommandManager = new GeyserBungeeCommandManager(geyser); + this.geyserCommandManager.init(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -157,7 +158,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/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java similarity index 89% 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..801fc877738 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 @@ -27,17 +27,17 @@ 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 { +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 @@ -56,7 +56,7 @@ public boolean isConsole() { } @Override - public String getLocale() { + public String locale() { if (handle instanceof ProxiedPlayer player) { String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); return GeyserLocale.formatLocale(locale); 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..87e5f9e13d9 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,7 +30,7 @@ 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.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -39,30 +39,30 @@ import java.util.Collections; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private final CommandExecutor commandExecutor; + private final GeyserCommandExecutor commandExecutor; public GeyserBungeeCommandExecutor(GeyserImpl geyser) { super("geyser"); - this.commandExecutor = new CommandExecutor(geyser); + this.commandExecutor = new GeyserCommandExecutor(geyser); } @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; @@ -77,7 +77,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/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java index 019544c28c5..e0fd7a4acc6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java @@ -26,16 +26,16 @@ package org.geysermc.geyser.platform.bungeecord.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; -public class GeyserBungeeCommandManager extends CommandManager { +public class GeyserBungeeCommandManager extends GeyserCommandManager { public GeyserBungeeCommandManager(GeyserImpl geyser) { super(geyser); } @Override - public String getDescription(String command) { + public String description(String command) { return ""; // no support for command descriptions in bungee } } 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 bdf28a203bd..e0ad866c878 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 @@ -34,7 +34,7 @@ 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.command.GeyserCommandManager; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -48,7 +48,7 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters; 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.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; @@ -161,6 +161,7 @@ public void onEnable() { } this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); + this.geyserCommandManager.init(); boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { @@ -182,7 +183,7 @@ public void onEnable() { 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); + SpigotCommandSource.setUseLegacyLocaleMethod(isPre1_12); // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib // To do their job injecting, then connect into *that* @@ -267,7 +268,7 @@ public GeyserSpigotLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } 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..35cb2d03a1f 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,7 +30,7 @@ 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.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -39,7 +39,7 @@ import java.util.Collections; import java.util.List; -public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor { +public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor { public GeyserSpigotCommandExecutor(GeyserImpl geyser) { super(geyser); @@ -47,20 +47,20 @@ public GeyserSpigotCommandExecutor(GeyserImpl geyser) { @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]); @@ -76,7 +76,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 103390ab863..22cb95c324b 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 @@ -29,11 +29,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 CommandMap COMMAND_MAP; @@ -52,7 +52,7 @@ 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() : ""; } 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/SpigotCommandSource.java similarity index 95% rename from bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index a05a6ebe0d1..839e4d88e4e 100644 --- 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/SpigotCommandSource.java @@ -28,13 +28,13 @@ 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.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -public class SpigotCommandSender implements CommandSender { +public class SpigotCommandSource implements GeyserCommandSource { /** * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. @@ -46,7 +46,7 @@ public class SpigotCommandSender implements CommandSender { private final org.bukkit.command.CommandSender handle; private final String locale; - public SpigotCommandSender(org.bukkit.command.CommandSender handle) { + public SpigotCommandSource(org.bukkit.command.CommandSender handle) { this.handle = handle; this.locale = getSpigotLocale(); // Ensure even Java players' languages are loaded @@ -69,7 +69,7 @@ public boolean isConsole() { } @Override - public String getLocale() { + public String locale() { return locale; } 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..894368e5ee2 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 @@ -29,7 +29,7 @@ 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.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -120,6 +120,7 @@ public void onEnable() { } this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); + this.geyserCommandManager.init(); Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser), "geyser"); } @@ -139,7 +140,7 @@ public GeyserSpongeLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } 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..c8338e44094 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 @@ -26,8 +26,8 @@ package org.geysermc.geyser.platform.sponge.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandExecutor; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandExecutor; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.session.GeyserSession; @@ -45,7 +45,7 @@ import java.util.List; import java.util.Optional; -public class GeyserSpongeCommandExecutor extends CommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements CommandCallable { public GeyserSpongeCommandExecutor(GeyserImpl geyser) { super(geyser); @@ -53,14 +53,14 @@ public GeyserSpongeCommandExecutor(GeyserImpl geyser) { @Override public CommandResult process(CommandSource source, String arguments) { - CommandSender commandSender = new SpongeCommandSender(source); + GeyserCommandSource commandSender = new SpongeCommandSource(source); GeyserSession session = getGeyserSession(commandSender); String[] args = arguments.split(" "); if (args.length > 0) { GeyserCommand command = getCommand(args[0]); if (command != null) { - if (!source.hasPermission(command.getPermission())) { + if (!source.hasPermission(command.permission())) { // Not ideal to use log here but we dont get a session source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); @@ -80,7 +80,7 @@ public CommandResult process(CommandSource source, String arguments) { @Override public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { if (arguments.split(" ").length == 1) { - return tabComplete(new SpongeCommandSender(source)); + return tabComplete(new SpongeCommandSource(source)); } return Collections.emptyList(); } 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..8e981f72a33 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 @@ -26,12 +26,12 @@ package org.geysermc.geyser.platform.sponge.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandMapping; import org.spongepowered.api.text.Text; -public class GeyserSpongeCommandManager extends CommandManager { +public class GeyserSpongeCommandManager extends GeyserCommandManager { private final org.spongepowered.api.command.CommandManager handle; public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserImpl geyser) { @@ -41,7 +41,7 @@ public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager h } @Override - public String getDescription(String command) { + public String description(String command) { return handle.get(command).map(CommandMapping::getCallable) .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) .orElse(Text.EMPTY).toPlain(); 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 94% 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..664727c501d 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 @@ -27,13 +27,13 @@ import lombok.AllArgsConstructor; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.command.source.ConsoleSource; import org.spongepowered.api.text.Text; @AllArgsConstructor -public class SpongeCommandSender implements CommandSender { +public class SpongeCommandSource implements GeyserCommandSource { private CommandSource handle; 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 43ab4b3fe21..ca41d3c1ddb 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 @@ -41,15 +41,15 @@ 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.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.platform.standalone.command.GeyserStandaloneCommandManager; 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 java.io.File; @@ -63,7 +63,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { - private GeyserCommandManager geyserCommandManager; + private GeyserStandaloneCommandManager geyserCommandManager; private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -215,7 +215,8 @@ public void onEnable() { logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); geyser = GeyserImpl.start(PlatformType.STANDALONE, this); - geyserCommandManager = new GeyserCommandManager(geyser); + geyserCommandManager = new GeyserStandaloneCommandManager(geyser); + geyserCommandManager.init(); if (gui != null) { gui.setupInterface(geyserLogger, geyserCommandManager); @@ -260,7 +261,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..f7ce6d052c2 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 @@ -31,11 +31,11 @@ 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 { +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 diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java similarity index 85% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java index 03d780f3cc1..e7b4cbe3799 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java @@ -26,16 +26,16 @@ package org.geysermc.geyser.platform.standalone.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; -public class GeyserCommandManager extends CommandManager { +public class GeyserStandaloneCommandManager extends GeyserCommandManager { - public GeyserCommandManager(GeyserImpl geyser) { + public GeyserStandaloneCommandManager(GeyserImpl geyser) { super(geyser); } @Override - public String getDescription(String command) { + public String description(String command) { return ""; // this is not sent over the protocol, so we return none } } 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..5901da47024 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.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger; -import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; +import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandManager; import javax.swing.*; import javax.swing.table.DefaultTableModel; @@ -255,33 +256,34 @@ public void write(byte[] b) { * @param geyserStandaloneLogger The current logger * @param geyserCommandManager The commands manager */ - public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) { + public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserStandaloneCommandManager geyserCommandManager) { 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/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 6645ef59523..a79fbb30b18 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 @@ -38,6 +38,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -145,6 +146,8 @@ public void onEnable() { // Will be initialized after the proxy has been bound this.geyserCommandManager = new GeyserVelocityCommandManager(geyser); + this.geyserCommandManager.init(); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser)); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -174,7 +177,7 @@ public GeyserVelocityLogger getGeyserLogger() { } @Override - public org.geysermc.geyser.command.CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return this.geyserCommandManager; } 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..d30d9ae9e8e 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,8 +27,8 @@ 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.command.GeyserCommandExecutor; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.session.GeyserSession; @@ -38,7 +38,7 @@ import java.util.Collections; import java.util.List; -public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand { +public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand { public GeyserVelocityCommandExecutor(GeyserImpl geyser) { super(geyser); @@ -46,18 +46,18 @@ public GeyserVelocityCommandExecutor(GeyserImpl geyser) { @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]); @@ -71,7 +71,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/GeyserVelocityCommandManager.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java index b42c8f76ec1..6f9faba8fdd 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java @@ -26,16 +26,16 @@ package org.geysermc.geyser.platform.velocity.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; -public class GeyserVelocityCommandManager extends CommandManager { +public class GeyserVelocityCommandManager extends GeyserCommandManager { public GeyserVelocityCommandManager(GeyserImpl geyser) { super(geyser); } @Override - public String getDescription(String command) { + public String description(String command) { return ""; // no support for command descriptions in velocity } } 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 90% 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..fa70d1cf754 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 @@ -29,19 +29,19 @@ import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; 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 @@ -65,7 +65,7 @@ public boolean isConsole() { } @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/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 932761d4bba..e24146cb26b 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 @@ -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/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index 54ca3678739..bc6a07ae3aa 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 573934b919d..b685741e73f 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -52,7 +52,7 @@ import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.event.GeyserEventBus; @@ -470,12 +470,11 @@ 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.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); @@ -504,6 +503,11 @@ public GeyserExtensionManager extensionManager() { return this.extensionManager; } + @Override + public GeyserCommandManager commandManager() { + return this.bootstrap.getGeyserCommandManager(); + } + @Override public EventBus eventBus() { return this.eventBus; @@ -531,10 +535,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/command/CommandManager.java b/core/src/main/java/org/geysermc/geyser/command/CommandManager.java deleted file mode 100644 index 60af8c4e50f..00000000000 --- a/core/src/main/java/org/geysermc/geyser/command/CommandManager.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.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")); - 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 20451b5e80a..2b3a855aae0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -27,7 +27,9 @@ 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; @@ -35,9 +37,10 @@ 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 +49,16 @@ public abstract class GeyserCommand { protected final String description; protected final String permission; - @Setter private List aliases = new ArrayList<>(); - 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,25 +68,22 @@ 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; } } \ No newline at end of file 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..3d08600d10f 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; @@ -39,16 +40,16 @@ * Represents helper functions for listening to {@code /geyser} commands. */ @AllArgsConstructor -public class CommandExecutor { +public class GeyserCommandExecutor { protected final GeyserImpl geyser; public GeyserCommand getCommand(String label) { - return geyser.getCommandManager().getCommands().get(label); + return (GeyserCommand) geyser.commandManager().commands().get(label); } @Nullable - public GeyserSession getGeyserSession(CommandSender sender) { + public GeyserSession getGeyserSession(GeyserCommandSource sender) { if (sender.isConsole()) { return null; } @@ -70,20 +71,19 @@ 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(); + Map commands = geyser.commandManager().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..9cce0e9ce64 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -0,0 +1,256 @@ +/* + * 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 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.CommandManager; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; +import org.geysermc.geyser.command.defaults.*; +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.*; + +@RequiredArgsConstructor +public abstract class GeyserCommandManager extends CommandManager { + + @Getter + private final Map commands = new HashMap<>(); + + private final GeyserImpl geyser; + + public void init() { + register(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help")); + register(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); + register(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + register(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + register(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + register(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); + register(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); + register(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); + register(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + register(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); + if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { + register(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + } + + this.geyser.eventBus().fire(new GeyserDefineCommandsEvent(this, this.commands)); + } + + @Override + public void register(@NonNull Command command) { + this.commands.put(command.name(), command); + this.geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); + + if (command.aliases().isEmpty()) { + return; + } + + for (String alias : command.aliases()) { + this.commands.put(alias, command); + } + } + + @Override + public void unregister(@NonNull Command command) { + this.commands.remove(command.name(), command); + + if (command.aliases().isEmpty()) { + return; + } + + for (String alias : command.aliases()) { + this.commands.remove(alias, command); + } + } + + @NotNull + @Override + public Map commands() { + return Collections.unmodifiableMap(this.commands); + } + + public void runCommand(GeyserCommandSource 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 }; + } + + Command cmd = commands.get(label); + if (cmd == null) { + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); + return; + } + + 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")); + } + } + } + } + + /** + * Returns the description of the given command + * + * @param command Command to get the description for + * @return Command description + */ + public abstract String description(String command); + + @Override + protected Command.Builder provideBuilder(Class sourceType) { + return new CommandBuilder<>(sourceType); + } + + @RequiredArgsConstructor + public static class CommandBuilder implements Command.Builder { + private final Class sourceType; + private String name; + private String description = ""; + private String permission = ""; + private List aliases; + private boolean executableOnConsole = true; + private List subCommands; + private boolean bedrockOnly; + private CommandExecutor executor; + + public CommandBuilder name(String name) { + this.name = name; + return this; + } + + public CommandBuilder description(String description) { + this.description = description; + return this; + } + + public CommandBuilder permission(String permission) { + this.permission = permission; + return this; + } + + public CommandBuilder aliases(List aliases) { + this.aliases = aliases; + return this; + } + + public CommandBuilder executableOnConsole(boolean executableOnConsole) { + this.executableOnConsole = executableOnConsole; + return this; + } + + public CommandBuilder subCommands(List subCommands) { + this.subCommands = subCommands; + return this; + } + + public CommandBuilder bedrockOnly(boolean bedrockOnly) { + this.bedrockOnly = bedrockOnly; + return this; + } + + public CommandBuilder executor(CommandExecutor executor) { + this.executor = executor; + return this; + } + + public GeyserCommand build() { + if (this.name == null || this.name.isBlank()) { + throw new IllegalArgumentException("Command cannot be null or blank!"); + } + + return new GeyserCommand(this.name, this.description, this.permission) { + + @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; + } + + @NonNull + @Override + public List subCommands() { + return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands; + } + + @Override + public boolean isBedrockOnly() { + return CommandBuilder.this.bedrockOnly; + } + + @Override + public boolean isExecutableOnConsole() { + return CommandBuilder.this.executableOnConsole; + } + }; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java new file mode 100644 index 00000000000..eabccc2439b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.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.command; + +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.text.GeyserLocale; + +/** + * Implemented on top of any class that can send a command. + * For example, it wraps around Spigot's CommandSender class. + */ +public interface GeyserCommandSource extends CommandSource { + + /** + * {@inheritDoc} + */ + default String locale() { + return GeyserLocale.getDefaultLocale(); + } +} 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..350b98f04ac 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,7 +25,7 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; 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..2aea5f4df38 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,7 +25,7 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; @@ -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/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index bd98d2b31e1..32382526a2e 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,9 +29,10 @@ 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.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.AsteriskSerializer; @@ -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/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 85682b29414..fccb1b267e4 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,7 +27,8 @@ 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.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.session.GeyserSession; @@ -54,25 +55,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(); + Map cmds = geyser.commandManager().getCommands(); + for (Map.Entry entry : cmds.entrySet()) { + 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; } sender.sendMessage(ChatColor.YELLOW + "/geyser " + entry.getKey() + ChatColor.WHITE + ": " + - GeyserLocale.getPlayerLocaleString(cmd.getDescription(), sender.getLocale())); + 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 f1004c3fbe8..8382d7a2933 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,7 +26,7 @@ package org.geysermc.geyser.command.defaults; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -44,8 +44,8 @@ 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(" "))); 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 1d29d5122d4..a9f265f6e48 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 @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockUtils; @@ -41,7 +41,7 @@ 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; } 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 b6a72838268..31e17faad58 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,7 +27,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; 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..a8e8a374e19 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,7 +26,7 @@ package org.geysermc.geyser.command.defaults; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; 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..c898f32a9a8 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,7 +28,7 @@ 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.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.session.GeyserSession; @@ -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 903e3bf4bdf..b038fa0ffc0 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,7 +27,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; 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 6ec816b12dc..89bea334334 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,7 +28,7 @@ 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.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.session.GeyserSession; @@ -54,7 +54,7 @@ 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; if (supportedCodecs.size() > 1) { @@ -70,12 +70,12 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) 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())); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { Properties gitProp = new Properties(); gitProp.load(stream); @@ -86,17 +86,17 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); 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) { 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/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index adeccdd01ca..77a1a199cc0 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 @@ -356,7 +356,7 @@ protected int 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/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index 93a0428266c..eaf71e0878d 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.event; +import net.kyori.event.EventSubscriber; import net.kyori.event.SimpleEventBus; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.Event; @@ -37,17 +38,12 @@ import java.lang.reflect.Method; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; public class GeyserEventBus implements EventBus { private final SimpleEventBus bus = new SimpleEventBus<>(Event.class); - @NonNull - @Override - public EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer) { - return this.subscribe(eventClass, consumer, null, Subscribe.Priority.NORMAL); - } - @NonNull @Override public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { @@ -86,6 +82,11 @@ public void register(@NonNull Extension extension, @NonNull Object eventHolder) } } + @Override + public void unregisterAll(@NonNull Extension extension) { + this.bus.unregister((Predicate>) subscriber -> extension.equals(((GeyserEventSubscription) subscriber).owner())); + } + @Override public boolean fire(@NonNull Event event) { return this.bus.post(event).wasSuccessful(); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java index 5b2e0184217..a26415a139b 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java @@ -29,6 +29,7 @@ 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; @@ -45,6 +46,7 @@ public class GeyserExtensionContainer { 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/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index a539dcb15ed..972b106d63e 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -28,6 +28,7 @@ import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; +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; @@ -35,6 +36,7 @@ import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; +import org.geysermc.geyser.extension.event.GeyserExtensionEventBus; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; @@ -83,12 +85,12 @@ public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtension } this.classLoaders.put(description.name(), loader); - return this.setup(loader.extension(), description, dataFolder); + return this.setup(loader.extension(), description, dataFolder, new GeyserExtensionEventBus(GeyserImpl.getInstance().eventBus(), loader.extension())); } - private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder) { + private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder, ExtensionEventBus eventBus) { GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); - GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger); + GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus); extension.onLoad(); return container; } @@ -246,6 +248,12 @@ 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) { diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index a72712f1657..936cde471ea 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -90,15 +90,18 @@ public void disable(@NonNull Extension extension) { public void enableExtension(Extension extension) { if (!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); extension.setEnabled(true); + GeyserImpl.getInstance().eventBus().register(extension, extension); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); } } private void disableExtension(@NonNull Extension extension) { if (extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); + GeyserImpl.getInstance().eventBus().unregisterAll(extension); + extension.setEnabled(false); + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); } } 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..4104871faff --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -0,0 +1,87 @@ +/* + * 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.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventSubscription; +import org.geysermc.geyser.api.event.ExtensionEventBus; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.Set; +import java.util.function.Consumer; + +public record GeyserExtensionEventBus(EventBus eventBus, + Extension extension) implements ExtensionEventBus { + @NonNull + @Override + public EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer) { + return this.eventBus.subscribe(this.extension, eventClass, consumer); + } + + @Override + public void register(@NonNull Object eventHolder) { + this.eventBus.register(this.extension, eventHolder); + } + + @Override + public void unregisterAll() { + this.eventBus.unregisterAll(this.extension); + } + + @NonNull + @Override + public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { + return this.eventBus.subscribe(extension, eventClass, consumer); + } + + @Override + public void unsubscribe(@NonNull EventSubscription subscription) { + this.eventBus.unsubscribe(subscription); + } + + @Override + public void register(@NonNull Extension extension, @NonNull Object eventHolder) { + this.eventBus.register(extension, eventHolder); + } + + @Override + public void unregisterAll(@NonNull Extension extension) { + this.eventBus.unregisterAll(extension); + } + + @Override + public boolean fire(@NonNull Event event) { + return this.eventBus.fire(event); + } + + @NonNull + @Override + public Set> subscriptions(@NonNull Class eventClass) { + return this.eventBus.subscriptions(eventClass); + } +} 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 d6f72b8d097..d203b031177 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 @@ -115,7 +115,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 +427,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/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index f547c4dceac..5686e3d986f 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -112,7 +112,7 @@ public boolean handle(LoginPacket loginPacket) { resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); - GeyserLocale.loadGeyserLocale(session.getLocale()); + GeyserLocale.loadGeyserLocale(session.locale()); return true; } @@ -208,7 +208,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); 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 7ea65e49e60..591934d6dde 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -79,7 +79,7 @@ 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.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.entity.InteractiveTagManager; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; @@ -122,7 +122,7 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter -public class GeyserSession implements GeyserConnection, CommandSender { +public class GeyserSession implements GeyserConnection, GeyserCommandSource { private final GeyserImpl geyser; private final UpstreamSession upstream; @@ -876,7 +876,7 @@ public void disconnected(DisconnectedEvent event) { if (cause instanceof UnexpectedEncryptionException) { if (remoteAuthType != 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 ? @@ -886,14 +886,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()); } @@ -1156,7 +1156,7 @@ public boolean isConsole() { } @Override - public String getLocale() { + public String locale() { return clientData.getLanguageCode(); } 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..fc6c3735616 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -80,7 +80,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 9d3e4f5aade..5d5779ae740 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 @@ -72,14 +72,14 @@ 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"); boolean hasAdvancements = false; for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement hasAdvancements = true; - builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.locale())); } } @@ -128,7 +128,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() @@ -190,7 +190,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/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 6a2182279b2..34e9364ced6 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 @@ -157,7 +157,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack nbt = translateDisplayProperties(session, nbt, bedrockItem); if (session.isAdvancedTooltips()) { - nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.getLocale()); + nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.locale()); } ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); @@ -474,7 +474,7 @@ public static CompoundTag translateDisplayProperties(GeyserSession session, Comp String name = ((StringTag) display.get("Name")).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)); @@ -500,7 +500,7 @@ 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; 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..4a91110dc3d 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 @@ -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/BasicItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java index 42cfc04392c..d50a8c579fe 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 @@ -61,7 +61,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/PlayerHeadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java index 680be00fdaa..44308aeeef3 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 @@ -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/TropicalFishBucketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java index dbacc75fe48..5fd101e8b59 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 @@ -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/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 73ca9b22279..1a955c1a811 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 @@ -27,7 +27,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -42,7 +42,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator commandData = new ArrayList<>(); IntSet commandNodes = new IntOpenHashSet(); @@ -141,15 +142,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 @@ -236,7 +242,7 @@ private static Object mapCommandType(GeyserSession session, CommandParser parser /** * Stores the command description and parameter data for best optimizing the Bedrock commands packet. */ - private static record BedrockCommandInfo(String description, CommandParamData[][] paramData) { + private static record BedrockCommandInfo(String name, String description, CommandParamData[][] paramData) implements ServerDefineCommandsEvent.CommandInfo { } @Getter 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 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/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java index 5a1063a10b1..8fd079702f1 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -226,7 +226,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) @@ -257,14 +257,14 @@ public static void buildAndShowLoginWindow(GeyserSession session) { return; } - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); })); } 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", "") @@ -286,7 +286,7 @@ public static void buildAndShowLoginDetailsWindow(GeyserSession session) { public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { session.sendForm( SimpleForm.builder() - .translator(GeyserLocale::getPlayerLocaleString, session.getLocale()) + .translator(GeyserLocale::getPlayerLocaleString, session.locale()) .title("geyser.auth.login.form.notice.btn_login.microsoft") .button("geyser.auth.login.method.browser") .button("geyser.auth.login.method.password") @@ -303,7 +303,7 @@ public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession sessi } else if (response.getClickedButtonId() == 1) { buildAndShowLoginDetailsWindow(session); } else { - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); } })); } @@ -326,7 +326,7 @@ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAut } if (response.getClickedButtonId() == 1) { - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); } }) ); 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 ea341245197..75c26cade32 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -45,7 +45,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) 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 447661e21b0..3324ec577e4 100644 --- a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java @@ -53,7 +53,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() From 2277b98dfd8935600e4474fb0c42fd3d90e99102 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 15:16:52 -0600 Subject: [PATCH 096/358] Rename Priority to PostOrder --- .../geysermc/geyser/api/event/EventSubscription.java | 7 +++---- .../org/geysermc/geyser/api/event/Subscribe.java | 12 +++++------- .../org/geysermc/geyser/event/GeyserEventBus.java | 8 ++++---- .../geyser/event/GeyserEventSubscription.java | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java index 2870c0ba692..8603dac37b1 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.extension.Extension; import java.util.function.Consumer; @@ -67,11 +66,11 @@ public interface EventSubscription { Extension owner(); /** - * Gets the priority of this event subscription. + * Gets the post order of this event subscription. * - * @return the priority of this event subscription + * @return the post order of this event subscription */ - Subscribe.Priority priority(); + Subscribe.PostOrder order(); /** * Gets if this event subscription is active. diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java index 581aa6fa051..6b3cfe638db 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java @@ -28,7 +28,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -50,20 +49,19 @@ public @interface Subscribe { /** - * The {@link Priority} of the event + * The {@link PostOrder} of the event * - * @return the priority of the event + * @return the post order of the event */ - @NonNull - Priority priority() default Priority.NORMAL; + Subscribe.PostOrder postOrder() default PostOrder.NORMAL; /** - * Represents the priority of an event. + * Represents the post order of an event. */ @Accessors(fluent = true) @Getter @AllArgsConstructor - enum Priority { + enum PostOrder { /** * The lowest priority. Called first to diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index eaf71e0878d..edda59e9572 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -47,7 +47,7 @@ public class GeyserEventBus implements EventBus { @NonNull @Override public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { - return this.subscribe(eventClass, consumer, extension, Subscribe.Priority.NORMAL); + return this.subscribe(eventClass, consumer, extension, Subscribe.PostOrder.NORMAL); } @Override @@ -78,7 +78,7 @@ public void register(@NonNull Extension extension, @NonNull Object eventHolder) } catch (IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); } - }, extension, subscribe.priority()); + }, extension, subscribe.postOrder()); } } @@ -103,8 +103,8 @@ public Set> subscriptions(@NonNull Class< .collect(Collectors.toSet()); } - private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.Priority priority) { - GeyserEventSubscription eventSubscription = new GeyserEventSubscription<>(this, eventClass, handler, extension, priority); + private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.PostOrder postOrder) { + GeyserEventSubscription eventSubscription = new GeyserEventSubscription<>(this, eventClass, handler, extension, postOrder); this.bus.register(eventClass, eventSubscription); return eventSubscription; } diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java index e160291c93a..33fe5a9e1dc 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java @@ -47,7 +47,7 @@ public class GeyserEventSubscription implements EventSubscripti private final Class eventClass; private final Consumer eventConsumer; private final Extension owner; - private final Subscribe.Priority priority; + private final Subscribe.PostOrder order; @Getter(AccessLevel.NONE) private boolean active; @Override @@ -77,6 +77,6 @@ public void invoke(@NonNull T event) throws Throwable { @Override public int postOrder() { - return this.priority.postOrder(); + return this.order.postOrder(); } } From 4c297092a58be847be7bc9ffd302985848dc9bbd Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 15:31:32 -0600 Subject: [PATCH 097/358] Update PostOrder names and don't use lombok in API --- .../geysermc/geyser/api/event/Subscribe.java | 28 +++++++++++-------- .../api/event/connection/ConnectionEvent.java | 8 ++++-- .../downstream/ServerDefineCommandsEvent.java | 4 ++- .../lifecycle/GeyserDefineCommandsEvent.java | 3 +- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java index 6b3cfe638db..488fa0ea3a5 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java @@ -25,10 +25,6 @@ package org.geysermc.geyser.api.event; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.experimental.Accessors; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -58,9 +54,6 @@ /** * Represents the post order of an event. */ - @Accessors(fluent = true) - @Getter - @AllArgsConstructor enum PostOrder { /** @@ -68,12 +61,12 @@ enum PostOrder { * allow for other events to customize * the outcome */ - LOWEST(net.kyori.event.PostOrders.FIRST), + FIRST(net.kyori.event.PostOrders.FIRST), /** * The second lowest priority. */ - LOW(net.kyori.event.PostOrders.EARLY), + EARLY(net.kyori.event.PostOrders.EARLY), /** * Normal priority. Event is neither @@ -84,14 +77,27 @@ enum PostOrder { /** * The second highest priority */ - HIGH(net.kyori.event.PostOrders.LATE), + LATE(net.kyori.event.PostOrders.LATE), /** * The highest of importance! Event is called * last and has the final say in the outcome */ - HIGHEST(net.kyori.event.PostOrders.LAST); + LAST(net.kyori.event.PostOrders.LAST); private final int postOrder; + + PostOrder(int postOrder) { + this.postOrder = postOrder; + } + + /** + * The numerical post order value. + * + * @return numerical post order value + */ + public int postOrder() { + return this.postOrder; + } } } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java index 9dcb6890815..48f3acdb7d2 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java @@ -25,22 +25,26 @@ package org.geysermc.geyser.api.event.connection; -import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.Event; /** * An event that contains a {@link GeyserConnection}. */ -@RequiredArgsConstructor 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 index 559631acf06..ba7254c9433 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.api.event.downstream; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.Cancellable; import org.geysermc.geyser.api.event.connection.ConnectionEvent; @@ -38,7 +39,7 @@ public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancel private final Set commands; private boolean cancelled; - public ServerDefineCommandsEvent(GeyserConnection connection, Set commands) { + public ServerDefineCommandsEvent(@NonNull GeyserConnection connection, @NonNull Set commands) { super(connection); this.commands = commands; } @@ -48,6 +49,7 @@ public ServerDefineCommandsEvent(GeyserConnection connection, Set commands() { return this.commands; } 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 index 7f64a462299..1a2c7b4d418 100644 --- 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 @@ -25,6 +25,7 @@ package org.geysermc.geyser.api.event.lifecycle; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.event.Event; @@ -38,5 +39,5 @@ * @param commands a mutable list of the currently * registered default commands */ -public record GeyserDefineCommandsEvent(CommandManager commandManager, Map commands) implements Event { +public record GeyserDefineCommandsEvent(@NonNull CommandManager commandManager, @NonNull Map commands) implements Event { } From ac18ef605469dd6efd81cb275af45d62a38b0be6 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 15:58:47 -0600 Subject: [PATCH 098/358] Remove adventure usage in API Unfortunately due to various platforms we support not having adventure support, we are unable to fully implement adventure into our API without having issues with shading or conflicts with other plugins. May look into what we can do in regards to classloading in the future but unfortunately it may not be a possibility at this point in time to support adventure inside of the API. --- api/geyser/pom.xml | 12 +++-------- .../api/extension/ExtensionManager.java | 16 +++++++------- .../extension/GeyserExtensionManager.java | 21 +++++++++---------- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index b2f44858966..435643b0a96 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -33,9 +33,9 @@ provided - net.kyori - adventure-api - ${adventure.version} + org.geysermc + base-api + 2.0.0-SNAPSHOT compile @@ -44,11 +44,5 @@ 1.27 compile - - org.geysermc - base-api - 2.0.0-SNAPSHOT - compile - \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java index 65387a8c743..65d6c66da58 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -25,10 +25,8 @@ package org.geysermc.geyser.api.extension; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Map; @@ -45,7 +43,7 @@ public abstract class ExtensionManager { * @return an extension with the given name */ @Nullable - public abstract Extension extension(@NotNull String name); + public abstract Extension extension(@NonNull String name); /** * Enables the given {@link Extension}. @@ -68,7 +66,7 @@ public abstract class ExtensionManager { * @return the extension loader for loading the given extension */ @Nullable - public abstract ExtensionLoader extensionLoader(@NotNull Extension extension); + public abstract ExtensionLoader extensionLoader(@NonNull Extension extension); /** * Gets all the {@link Extension}s currently loaded. @@ -85,7 +83,7 @@ public abstract class ExtensionManager { * @return the extension loader at the given identifier */ @Nullable - public abstract ExtensionLoader extensionLoader(@NonNull Key identifier); + public abstract ExtensionLoader extensionLoader(@NonNull String identifier); /** * Registers an {@link ExtensionLoader} with the given identifier. @@ -93,7 +91,7 @@ public abstract class ExtensionManager { * @param identifier the identifier * @param extensionLoader the extension loader */ - public abstract void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader); + public abstract void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader); /** * Gets all the currently registered {@link ExtensionLoader}s. @@ -101,7 +99,7 @@ public abstract class ExtensionManager { * @return all the currently registered extension loaders */ @NonNull - public abstract Map extensionLoaders(); + public abstract Map extensionLoaders(); /** * Registers an {@link Extension} with the given {@link ExtensionLoader}. @@ -109,12 +107,12 @@ public abstract class ExtensionManager { * @param extension the extension * @param loader the loader */ - public abstract void register(@NotNull Extension extension, @NotNull ExtensionLoader loader); + public abstract void register(@NonNull Extension extension, @NonNull ExtensionLoader loader); /** * Loads all extensions from the given {@link ExtensionLoader}. */ - protected final void loadAllExtensions(@NotNull ExtensionLoader extensionLoader) { + protected final void loadAllExtensions(@NonNull ExtensionLoader extensionLoader) { extensionLoader.loadAllExtensions(this); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 936cde471ea..c125d010b21 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -34,12 +34,12 @@ import org.geysermc.geyser.api.extension.ExtensionLoader; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.text.GeyserLocale; -import org.jetbrains.annotations.NotNull; import java.util.*; +import java.util.stream.Collectors; public class GeyserExtensionManager extends ExtensionManager { - private static final Key BASE_KEY = Key.key("geysermc", "base"); + private static final Key BASE_EXTENSION_LOADER_KEY = Key.key("geysermc", "base"); private final Map extensions = new LinkedHashMap<>(); private final Map extensionsLoaders = new LinkedHashMap<>(); @@ -47,8 +47,7 @@ public class GeyserExtensionManager extends ExtensionManager { public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - this.registerExtensionLoader(BASE_KEY, new GeyserExtensionLoader()); - + Registries.EXTENSION_LOADERS.register(BASE_EXTENSION_LOADER_KEY, new GeyserExtensionLoader()); for (ExtensionLoader loader : this.extensionLoaders().values()) { this.loadAllExtensions(loader); } @@ -130,23 +129,23 @@ public Collection extensions() { @Nullable @Override - public ExtensionLoader extensionLoader(@NonNull Key identifier) { - return Registries.EXTENSION_LOADERS.get(identifier); + public ExtensionLoader extensionLoader(@NonNull String identifier) { + return Registries.EXTENSION_LOADERS.get(Key.key(identifier)); } @Override - public void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader) { - Registries.EXTENSION_LOADERS.register(identifier, extensionLoader); + public void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader) { + Registries.EXTENSION_LOADERS.register(Key.key(identifier), extensionLoader); } @NonNull @Override - public Map extensionLoaders() { - return Collections.unmodifiableMap(Registries.EXTENSION_LOADERS.get()); + public Map extensionLoaders() { + return Registries.EXTENSION_LOADERS.get().entrySet().stream().collect(Collectors.toMap(key -> key.getKey().asString(), Map.Entry::getValue)); } @Override - public void register(@NotNull Extension extension, @NotNull ExtensionLoader loader) { + public void register(@NonNull Extension extension, @NonNull ExtensionLoader loader) { this.extensionsLoaders.put(extension, loader); this.extensions.put(extension.name(), extension); } From 115b10362812d48b471ab0e43a9e3e21aa548fc3 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 16:35:27 -0600 Subject: [PATCH 099/358] Add extensions command --- .../geyser/command/GeyserCommandManager.java | 1 + .../command/defaults/ExtensionsCommand.java | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 9cce0e9ce64..0214a44fd19 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -63,6 +63,7 @@ public void init() { register(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); register(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); register(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); + register(new ExtensionsCommand(geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { register(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } 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); + } +} From 34d1dfde5e7ed8d9c46eef4794afd3d9e8f8e021 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 16:43:57 -0600 Subject: [PATCH 100/358] Add extensions permission to Spigot plugin.yml --- bootstrap/spigot/src/main/resources/plugin.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 18773402e13..88b2ea1a160 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -40,3 +40,6 @@ permissions: geyser.command.version: description: Shows the current Geyser version and checks for updates. default: op + geyser.command.extensions: + description: Shows all the loaded extensions. + default: op From ac134b84f243590a8b763cbeefae3f5ac2bf8d4b Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 18:28:39 -0600 Subject: [PATCH 101/358] Remove onEnable, onDisable and onLoad and replace it with lifecycle events --- .../lifecycle/GeyserPostInitializeEvent.java | 8 +++- .../lifecycle/GeyserPreInitializeEvent.java | 40 +++++++++++++++++++ .../event/lifecycle/GeyserShutdownEvent.java | 6 ++- .../geyser/api/extension/Extension.java | 18 --------- .../java/org/geysermc/geyser/GeyserImpl.java | 7 +++- .../extension/GeyserExtensionLoader.java | 14 +------ 6 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java index 94d3d8cb864..94e42e07596 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java @@ -25,10 +25,16 @@ package org.geysermc.geyser.api.event.lifecycle; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.extension.ExtensionManager; /** * Called when Geyser has completed initializing. + * + * @param extensionManager the extension manager + * @param eventBus the event bus */ -public class GeyserPostInitializeEvent implements Event { +public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java new file mode 100644 index 00000000000..fa130c88301 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.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.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +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 index baf8b3445f6..a0fc2294bdc 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -25,10 +25,14 @@ package org.geysermc.geyser.api.event.lifecycle; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.extension.ExtensionManager; /** * Called when Geyser is shutting down. */ -public class GeyserShutdownEvent implements Event { +public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull CommandManager commandManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 357165ffe4f..2982a76fb4a 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -36,24 +36,6 @@ */ public interface Extension { - /** - * Called when the extension is loaded - */ - default void onLoad() { - } - - /** - * Called when the extension is enabled - */ - default void onEnable() { - } - - /** - * Called when the extension is disabled - */ - default void onDisable() { - } - /** * Gets if the extension is enabled * diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index b685741e73f..47aa905ea66 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -51,6 +51,7 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.EventBus; 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.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -167,6 +168,8 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { this.extensionManager = new GeyserExtensionManager(); this.extensionManager.init(); + this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); + start(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -418,7 +421,7 @@ private void start() { newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); - this.eventBus.fire(new GeyserPostInitializeEvent()); + this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); } @Override @@ -474,7 +477,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - this.eventBus.fire(new GeyserShutdownEvent()); + this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.commandManager(), this.eventBus)); this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 972b106d63e..00e0e6701fc 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -90,9 +90,7 @@ public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtension private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder, ExtensionEventBus eventBus) { GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); - GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus); - extension.onLoad(); - return container; + return new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus); } public GeyserExtensionDescription extensionDescription(Path path) throws InvalidDescriptionException { @@ -225,15 +223,7 @@ protected boolean isEnabled(@NonNull Extension extension) { @Override protected void setEnabled(@NonNull Extension extension, boolean enabled) { - boolean isEnabled = this.extensionContainers.get(extension).enabled; - if (isEnabled != enabled) { - this.extensionContainers.get(extension).enabled = enabled; - if (enabled) { - extension.onEnable(); - } else { - extension.onDisable(); - } - } + this.extensionContainers.get(extension).enabled = enabled; } @NonNull From 5abf989139be312b504e976890296e75dc6d3eca Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 16 Jan 2022 22:52:27 -0600 Subject: [PATCH 102/358] Use lambda metadata factory for events stonks --- .../geyser/api/event/EventSubscription.java | 11 ---- ...on.java => AbstractEventSubscription.java} | 24 ++------ .../geyser/event/BaseEventSubscription.java | 58 ++++++++++++++++++ .../event/GeneratedEventSubscription.java | 60 +++++++++++++++++++ .../geysermc/geyser/event/GeyserEventBus.java | 32 ++++++---- .../extension/GeyserExtensionClassLoader.java | 1 - .../extension/GeyserExtensionLoader.java | 4 +- 7 files changed, 146 insertions(+), 44 deletions(-) rename core/src/main/java/org/geysermc/geyser/event/{GeyserEventSubscription.java => AbstractEventSubscription.java} (72%) create mode 100644 core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java create mode 100644 core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java index 8603dac37b1..9a04b697c1c 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java @@ -28,8 +28,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.Extension; -import java.util.function.Consumer; - /** * Represents a subscribed listener to a {@link Event}. Wraps around * the event and is capable of unsubscribing from the event or give @@ -47,15 +45,6 @@ public interface EventSubscription { @NonNull Class eventClass(); - /** - * Gets the consumer responsible for handling - * this event. - * - * @return the consumer responsible for this event - */ - @NonNull - Consumer eventConsumer(); - /** * Gets the {@link Extension} that owns this * event subscription. diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java similarity index 72% rename from core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java rename to core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java index 33fe5a9e1dc..69dc7493582 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscription.java +++ b/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java @@ -30,24 +30,20 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import net.kyori.event.EventSubscriber; -import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.Event; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventSubscription; import org.geysermc.geyser.api.event.Subscribe; import org.geysermc.geyser.api.extension.Extension; -import java.util.function.Consumer; - @Getter @Accessors(fluent = true) @RequiredArgsConstructor -public class GeyserEventSubscription implements EventSubscription, EventSubscriber { - private final EventBus eventBus; - private final Class eventClass; - private final Consumer eventConsumer; - private final Extension owner; - private final Subscribe.PostOrder order; +public abstract class AbstractEventSubscription implements EventSubscription, EventSubscriber { + protected final EventBus eventBus; + protected final Class eventClass; + protected final Extension owner; + protected final Subscribe.PostOrder order; @Getter(AccessLevel.NONE) private boolean active; @Override @@ -65,16 +61,6 @@ public void unsubscribe() { this.eventBus.unsubscribe(this); } - @Override - public void invoke(@NonNull T event) throws Throwable { - try { - this.eventConsumer.accept(event); - } catch (Throwable ex) { - this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); - ex.printStackTrace(); - } - } - @Override public int postOrder() { return this.order.postOrder(); diff --git a/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java new file mode 100644 index 00000000000..79df94e5d6b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.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 lombok.Getter; +import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.function.Consumer; + +@Getter +@Accessors(fluent = true) +public class BaseEventSubscription extends AbstractEventSubscription { + private final Consumer eventConsumer; + + public BaseEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Consumer eventConsumer) { + super(eventBus, eventClass, owner, order); + + this.eventConsumer = eventConsumer; + } + + @Override + public void invoke(@NonNull T event) throws Throwable { + try { + this.eventConsumer.accept(event); + } catch (Throwable ex) { + this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); + ex.printStackTrace(); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java new file mode 100644 index 00000000000..b1ba7bf8b10 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.event; + +import lombok.Getter; +import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.function.BiConsumer; + +@Getter +@Accessors(fluent = true) +public class GeneratedEventSubscription extends AbstractEventSubscription { + private final Object eventHolder; + private final BiConsumer eventConsumer; + + public GeneratedEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Object eventHolder, BiConsumer eventConsumer) { + super(eventBus, eventClass, owner, order); + + this.eventHolder = eventHolder; + this.eventConsumer = eventConsumer; + } + + @Override + public void invoke(@NonNull T event) throws Throwable { + try { + this.eventConsumer.accept(this.eventHolder, event); + } catch (Throwable ex) { + this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); + ex.printStackTrace(); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index edda59e9572..60e354ac970 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -33,15 +33,19 @@ import org.geysermc.geyser.api.event.EventSubscription; import org.geysermc.geyser.api.event.Subscribe; import org.geysermc.geyser.api.extension.Extension; +import org.lanternpowered.lmbda.LambdaFactory; -import java.lang.reflect.InvocationTargetException; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; public class GeyserEventBus implements EventBus { + private static final MethodHandles.Lookup CALLER = MethodHandles.lookup(); + private final SimpleEventBus bus = new SimpleEventBus<>(Event.class); @NonNull @@ -52,7 +56,7 @@ public EventSubscription subscribe(@NonNull Extension exten @Override public void unsubscribe(@NonNull EventSubscription subscription) { - this.bus.unregister((GeyserEventSubscription) subscription); + this.bus.unregister((AbstractEventSubscription) subscription); } @SuppressWarnings("unchecked") @@ -72,19 +76,19 @@ public void register(@NonNull Extension extension, @NonNull Object eventHolder) } Subscribe subscribe = method.getAnnotation(Subscribe.class); - this.subscribe((Class) method.getParameters()[0].getType(), (event) -> { - try { - method.invoke(eventHolder, event); - } catch (IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - } - }, extension, subscribe.postOrder()); + + try { + Class type = (Class) method.getParameters()[0].getType(); + this.subscribe(type, eventHolder, LambdaFactory.createBiConsumer(CALLER.unreflect(method)), extension, subscribe.postOrder()); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } } } @Override public void unregisterAll(@NonNull Extension extension) { - this.bus.unregister((Predicate>) subscriber -> extension.equals(((GeyserEventSubscription) subscriber).owner())); + this.bus.unregister((Predicate>) subscriber -> extension.equals(((AbstractEventSubscription) subscriber).owner())); } @Override @@ -104,7 +108,13 @@ public Set> subscriptions(@NonNull Class< } private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.PostOrder postOrder) { - GeyserEventSubscription eventSubscription = new GeyserEventSubscription<>(this, eventClass, handler, extension, postOrder); + BaseEventSubscription eventSubscription = new BaseEventSubscription<>(this, eventClass, extension, postOrder, handler); + this.bus.register(eventClass, eventSubscription); + return eventSubscription; + } + + private EventSubscription subscribe(Class eventClass, Object eventHolder, BiConsumer handler, Extension extension, Subscribe.PostOrder postOrder) { + GeneratedEventSubscription eventSubscription = new GeneratedEventSubscription<>(this, eventClass, extension, postOrder, eventHolder, handler); this.bus.register(eventClass, eventSubscription); return eventSubscription; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 426cd1de75c..28b9930b48e 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -35,7 +35,6 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; -import java.util.Set; public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 00e0e6701fc..7092d1fc2dd 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -110,14 +110,14 @@ public Pattern[] extensionFilters() { public Class classByName(final String name) throws ClassNotFoundException{ Class clazz = this.classes.get(name); try { - for(GeyserExtensionClassLoader loader : this.classLoaders.values()) { + for (GeyserExtensionClassLoader loader : this.classLoaders.values()) { try { clazz = loader.findClass(name,false); } catch(NullPointerException ignored) { } } return clazz; - } catch(NullPointerException s) { + } catch (NullPointerException s) { return null; } } From dcb91f34b87ceb075386539020a3b280eac1264e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:55:08 -0500 Subject: [PATCH 103/358] Bump to 2.0.1-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0e3331f985f..cd8b811a7cc 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:core:2.0.0-SNAPSHOT' - shadow('org.geysermc:core:2.0.0-SNAPSHOT') { + implementation 'org.geysermc:core:2.0.1-SNAPSHOT' + shadow('org.geysermc:core:2.0.1-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 66fbe3cada8..14539564854 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18 yarn_mappings=1.18+build.1 loader_version=0.12.8 # Mod Properties -mod_version=2.0.0-SNAPSHOT +mod_version=2.0.1-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From 6321ecc1669d12680d71633f10c7f234404f5992 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 16:38:55 -0600 Subject: [PATCH 104/358] Initial move to gradle --- Jenkinsfile | 6 +- ap/build.gradle.kts | 0 .../geysermc/processor/ClassProcessor.java | 1 + .../javax.annotation.processing.Processor | 0 api/base/build.gradle.kts | 1 + api/base/pom.xml | 27 -- api/geyser/build.gradle.kts | 3 + api/geyser/pom.xml | 48 --- .../org/geysermc/geyser/api/GeyserApi.java | 4 +- api/pom.xml | 25 -- bootstrap/bungeecord/build.gradle.kts | 35 ++ bootstrap/bungeecord/pom.xml | 103 ----- .../bungeecord/GeyserBungeePlugin.java | 2 +- .../bungeecord/src/main/resources/bungee.yml | 8 +- bootstrap/pom.xml | 49 --- bootstrap/spigot/build.gradle.kts | 46 +++ bootstrap/spigot/pom.xml | 127 ------ .../spigot/src/main/resources/plugin.yml | 8 +- bootstrap/sponge/build.gradle.kts | 36 ++ bootstrap/sponge/pom.xml | 101 ----- bootstrap/standalone/build.gradle.kts | 33 ++ bootstrap/standalone/pom.xml | 140 ------- .../standalone/GeyserStandaloneBootstrap.java | 2 + .../standalone/GeyserStandaloneLogger.java | 8 +- .../standalone/src/main/resources/log4j2.xml | 2 +- bootstrap/velocity/build.gradle.kts | 68 ++++ bootstrap/velocity/pom.xml | 106 ----- build-logic/build.gradle.kts | 21 + build-logic/src/main/kotlin/Versions.kt | 44 +++ build-logic/src/main/kotlin/extensions.kt | 79 ++++ .../kotlin/geyser.api-conventions.gradle.kts | 9 + .../kotlin/geyser.base-conventions.gradle.kts | 41 ++ .../main/kotlin/geyser.build-logic.gradle.kts | 0 .../geyser.platform-conventions.gradle.kts | 4 + .../geyser.shadow-conventions.gradle.kts | 55 +++ build.gradle.kts | 43 ++ common/build.gradle.kts | 3 + common/pom.xml | 31 -- core/build.gradle.kts | 86 ++++ core/pom.xml | 374 ------------------ .../geysermc/connector/GeyserConnector.java | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 33 +- .../command/defaults/VersionCommand.java | 13 +- core/src/main/resources/languages | 2 +- gradle.properties | 6 + gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 +++++++++++ gradlew.bat | 89 +++++ pom.xml | 95 ----- settings.gradle.kts | 73 ++++ 50 files changed, 1052 insertions(+), 1279 deletions(-) create mode 100644 ap/build.gradle.kts rename {core => ap}/src/main/resources/META-INF/services/javax.annotation.processing.Processor (100%) create mode 100644 api/base/build.gradle.kts delete mode 100644 api/base/pom.xml create mode 100644 api/geyser/build.gradle.kts delete mode 100644 api/geyser/pom.xml delete mode 100644 api/pom.xml create mode 100644 bootstrap/bungeecord/build.gradle.kts delete mode 100644 bootstrap/bungeecord/pom.xml delete mode 100644 bootstrap/pom.xml create mode 100644 bootstrap/spigot/build.gradle.kts delete mode 100644 bootstrap/spigot/pom.xml create mode 100644 bootstrap/sponge/build.gradle.kts delete mode 100644 bootstrap/sponge/pom.xml create mode 100644 bootstrap/standalone/build.gradle.kts delete mode 100644 bootstrap/standalone/pom.xml create mode 100644 bootstrap/velocity/build.gradle.kts delete mode 100644 bootstrap/velocity/pom.xml create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/src/main/kotlin/Versions.kt create mode 100644 build-logic/src/main/kotlin/extensions.kt create mode 100644 build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/geyser.build-logic.gradle.kts create mode 100644 build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts create mode 100644 build.gradle.kts create mode 100644 common/build.gradle.kts delete mode 100644 common/pom.xml create mode 100644 core/build.gradle.kts delete mode 100644 core/pom.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle.kts diff --git a/Jenkinsfile b/Jenkinsfile index 481c023107a..b8566b2f71d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent any tools { - maven 'Maven 3' + gradle 'Gradle 7' jdk 'Java 16' } options { @@ -11,11 +11,11 @@ pipeline { stage ('Build') { steps { sh 'git submodule update --init --recursive' - sh 'mvn clean package' + sh './gradlew shadowJar' } post { success { - archiveArtifacts artifacts: 'bootstrap/**/target/*.jar', excludes: 'bootstrap/**/target/original-*.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/original-*.jar', fingerprint: true } } } diff --git a/ap/build.gradle.kts b/ap/build.gradle.kts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ap/src/main/java/org/geysermc/processor/ClassProcessor.java b/ap/src/main/java/org/geysermc/processor/ClassProcessor.java index a6259a853e2..0f730aabad9 100644 --- a/ap/src/main/java/org/geysermc/processor/ClassProcessor.java +++ b/ap/src/main/java/org/geysermc/processor/ClassProcessor.java @@ -163,6 +163,7 @@ 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()); diff --git a/core/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor similarity index 100% rename from core/src/main/resources/META-INF/services/javax.annotation.processing.Processor rename to ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts new file mode 100644 index 00000000000..d7500fdaaf4 --- /dev/null +++ b/api/base/build.gradle.kts @@ -0,0 +1 @@ +provided("net.kyori", "event-api", Versions.eventVersion) \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml deleted file mode 100644 index 17edb1a8588..00000000000 --- a/api/base/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - org.geysermc - api-parent - 2.0.1-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/geyser/build.gradle.kts b/api/geyser/build.gradle.kts new file mode 100644 index 00000000000..f9f8e66a89b --- /dev/null +++ b/api/geyser/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + api(projects.api) +} \ No newline at end of file diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml deleted file mode 100644 index de9c63e8346..00000000000 --- a/api/geyser/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - org.geysermc - api-parent - 2.0.1-SNAPSHOT - ../pom.xml - - 4.0.0 - - geyser-api - - - 16 - 16 - - 4.9.3 - - - - - org.checkerframework - checker-qual - 3.19.0 - provided - - - net.kyori - event-api - 3.0.0 - provided - - - org.geysermc - base-api - 2.0.1-SNAPSHOT - compile - - - org.yaml - snakeyaml - 1.27 - 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 2bddc9ef585..b5a0c7897f4 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 @@ -55,9 +55,9 @@ public interface GeyserApi extends GeyserApiBase { * 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. * - * @return true if the version number is not 'DEV'. + * @return if we are in a production environment */ - boolean productionEnvironment(); + boolean isProductionEnvironment(); /** * {@inheritDoc} diff --git a/api/pom.xml b/api/pom.xml deleted file mode 100644 index b6d865cb459..00000000000 --- a/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.1-SNAPSHOT - ../pom.xml - - - 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..873df692ade --- /dev/null +++ b/bootstrap/bungeecord/build.gradle.kts @@ -0,0 +1,35 @@ +val bungeeVersion = "a7c6ede"; + +dependencies { + api(projects.core) +} + +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("com.github.SpigotMC.BungeeCord", "bungeecord-proxy", bungeeVersion) + +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 45a08c7db77..00000000000 --- a/bootstrap/bungeecord/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.1-SNAPSHOT - - bootstrap-bungeecord - - - - org.geysermc - core - 2.0.1-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-SNAPSHOT - - - 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/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index bb90e500043..0aa82d9cdc6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -27,6 +27,7 @@ import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; @@ -40,7 +41,6 @@ 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 java.io.File; import java.io.IOException; 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/pom.xml b/bootstrap/pom.xml deleted file mode 100644 index 58c651455d4..00000000000 --- a/bootstrap/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.1-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.1-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..affb243b356 --- /dev/null +++ b/bootstrap/spigot/build.gradle.kts @@ -0,0 +1,46 @@ +val paperVersion = "1.17.1-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet +val viaVersion = "4.0.0" +val adaptersVersion = "1.3-SNAPSHOT" + +dependencies { + api(projects.core) + + implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion) +} + +platformRelocate("it.unimi.dsi.fastutil") +platformRelocate("com.fasterxml.jackson") +platformRelocate("net.kyori") +platformRelocate("org.objectweb.asm") + +// These dependencies are already present on the platform +provided("io.papermc.paper", "paper-api", paperVersion) +provided("com.viaversion", "viaversion", 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:.*")) + } +} \ No newline at end of file diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml deleted file mode 100644 index 6eda527f3f2..00000000000 --- a/bootstrap/spigot/pom.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.1-SNAPSHOT - - bootstrap-spigot - - - - papermc - https://papermc.io/repo/repository/maven-public/ - - - viaversion-repo - https://repo.viaversion.com - - - - - - org.geysermc - core - 2.0.1-SNAPSHOT - compile - - - io.papermc.paper - paper-api - 1.18.1-R0.1-SNAPSHOT - provided - - - com.viaversion - viaversion - 4.0.0 - provided - - - org.geysermc.geyser.adapters - spigot-all - 1.3-SNAPSHOT - - - - ${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-SNAPSHOT - - - 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 - - - - - - - - - com.google.*:* - org.yaml:* - - - 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-dns:* - io.netty:netty-resolver-dns:* - io.netty:netty-resolver-dns-native-macos:* - - - - - - - \ No newline at end of file diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 88b2ea1a160..27f51acd1bb 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -1,8 +1,8 @@ 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: diff --git a/bootstrap/sponge/build.gradle.kts b/bootstrap/sponge/build.gradle.kts new file mode 100644 index 00000000000..2850b2c5e69 --- /dev/null +++ b/bootstrap/sponge/build.gradle.kts @@ -0,0 +1,36 @@ +val spongeVersion = "7.1.0" + +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") + +// Exclude these dependencies +exclude("com.google.code.gson:*") +exclude("org.yaml:*") +exclude("org.slf4j:*") +exclude("org.ow2.asm:*") + +// These dependencies are already present on the platform +provided("org.spongepowered", "spongeapi", spongeVersion) + +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:.*")) + } +} \ No newline at end of file diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml deleted file mode 100644 index ab3b7d9707c..00000000000 --- a/bootstrap/sponge/pom.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.1-SNAPSHOT - - bootstrap-sponge - - - - org.geysermc - core - 2.0.1-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-SNAPSHOT - - - 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/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts new file mode 100644 index 00000000000..977a4a7a6b5 --- /dev/null +++ b/bootstrap/standalone/build.gradle.kts @@ -0,0 +1,33 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + +val terminalConsoleVersion = "1.2.0" +val jlineVersion = "3.20.0" + +dependencies { + api(projects.core) + + implementation("net.minecrell", "terminalconsoleappender", terminalConsoleVersion) { + exclude("org.apache.logging.log4j", "log4j-core") + exclude("org.jline", "jline-reader") + exclude("org.jline", "jline-terminal") + exclude("org.jline", "jline-terminal-jna") + } + + implementation("org.jline", "jline-terminal", jlineVersion) + implementation("org.jline", "jline-terminal-jna", jlineVersion) + implementation("org.jline", "jline-reader", jlineVersion) + + implementation("org.apache.logging.log4j", "log4j-api", Versions.log4jVersion) + implementation("org.apache.logging.log4j", "log4j-core", Versions.log4jVersion) + implementation("org.apache.logging.log4j", "log4j-slf4j18-impl", Versions.log4jVersion) +} + +application { + mainClass.set("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap") +} + +tasks.withType { + archiveBaseName.set("Geyser") + + 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 881c87e6c59..00000000000 --- a/bootstrap/standalone/pom.xml +++ /dev/null @@ -1,140 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.1-SNAPSHOT - - bootstrap-standalone - - - 2.17.1 - - - - - org.geysermc - core - 2.0.1-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.20.0 - - - org.jline - jline-terminal-jna - 3.20.0 - - - org.jline - jline-reader - 3.20.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-SNAPSHOT - - - 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 - - - - - - ${project.build.directory}/dependency-reduced-pom.xml - - - - - 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 ca41d3c1ddb..9869fa478ee 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 @@ -38,6 +38,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; @@ -179,6 +180,7 @@ public void onEnable() { logger.removeAppender(appender); } } + if (useGui && gui == null) { gui = new GeyserStandaloneGUI(); gui.redirectSystemStreams(); 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 f7ce6d052c2..31b395a6136 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,7 +25,7 @@ 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; @@ -34,7 +34,7 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; -@Log4j2 +@Slf4j public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, GeyserCommandSource { @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 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..f9fcafb2cfa --- /dev/null +++ b/bootstrap/velocity/build.gradle.kts @@ -0,0 +1,68 @@ +val velocityVersion = "3.0.0" + +dependencies { + api(projects.core) +} + +platformRelocate("com.fasterxml.jackson") +platformRelocate("it.unimi.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("com.velocitypowered", "velocity-api", velocityVersion) + +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 ff052471d63..00000000000 --- a/bootstrap/velocity/pom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - 4.0.0 - - org.geysermc - bootstrap-parent - 2.0.1-SNAPSHOT - - bootstrap-velocity - - - - org.geysermc - core - 2.0.1-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-SNAPSHOT - - - 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/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000000..25cbfe9de90 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,21 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation("net.kyori", "indra-common", "2.0.6") + implementation("org.jfrog.buildinfo", "build-info-extractor-gradle", "4.26.1") + implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.1") +} + +tasks.withType { + kotlinOptions { + jvmTarget = "16" + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt new file mode 100644 index 00000000000..c6348bc6546 --- /dev/null +++ b/build-logic/src/main/kotlin/Versions.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +object Versions { + const val jacksonVersion= "2.12.4" + const val fastutilVersion= "8.5.2" + const val nettyVersion= "4.1.66.Final" + const val guavaVersion= "29.0-jre" + const val nbtVersion= "2.1.0" + const val websocketVersion= "1.5.1" + const val protocolVersion= "0cd24c0" + const val raknetVersion= "1.6.28-SNAPSHOT" + const val mcauthlibVersion= "6c99331" + const val mcprotocollibversion= "6a23a780" + const val packetlibVersion= "2.1-SNAPSHOT" + const val adventureVersion= "4.9.3" + const val eventVersion= "3.0.0" + const val junitVersion= "4.13.1" + const val checkerQualVersion= "3.19.0" + const val cumulusVersion = "1.0-SNAPSHOT" + const val log4jVersion = "2.17.1" +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt new file mode 100644 index 00000000000..a2c90cd1f79 --- /dev/null +++ b/build-logic/src/main/kotlin/extensions.kt @@ -0,0 +1,79 @@ +/* + * 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 net.kyori.indra.git.IndraGitExtension +import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.the + +fun Project.lastCommitHash(): String? = + the().commit()?.name?.substring(0, 7) + +// retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project +// some properties might be specific to Jenkins +fun Project.branchName(): String = + System.getProperty("GIT_BRANCH", "local/dev") +fun Project.commitHashAbbrev(): String = + System.getProperty("GIT_COMMIT", "0000000") +fun Project.versionName(): String = + System.getProperty("GIT_VERSION", "local/dev") +fun Project.buildNumber(): Int = + Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")) + +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) { + tasks.named("shadowJar") { + relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") + } +} + +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!!) + +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..0781436c44e --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("geyser.shadow-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..211455d31c7 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -0,0 +1,41 @@ +plugins { + `java-library` + `maven-publish` +} + +dependencies { + compileOnly("org.checkerframework", "checker-qual", Versions.checkerQualVersion) +} + +tasks { + processResources { + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.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() +} + +publishing { + publications.create("mavenJava") { + groupId = project.group as String + artifactId = project.name + version = project.version as String + } +} \ No newline at end of file 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..07968f231bf --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts @@ -0,0 +1,4 @@ +plugins { + application + id("geyser.shadow-conventions") +} \ No newline at end of file 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..ddd42789765 --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts @@ -0,0 +1,55 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("geyser.base-conventions") + id("com.github.johnrengelman.shadow") + id("com.jfrog.artifactory") +} + +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) + } +} + +publishing { + publications.named("mavenJava") { + artifact(tasks["shadowJar"]) + artifact(tasks["sourcesJar"]) + } +} + +artifactory { + publish { + repository { + setRepoKey("maven-snapshots") + setMavenCompatible(true) + } + defaults { + publishConfigs("archives") + setPublishArtifacts(true) + setPublishPom(true) + setPublishIvy(false) + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..573687c7f71 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +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." +} + +val platforms = setOf( + 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")) { + group = rootProject.group as String + ".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.shadow-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..205b20c0e1d --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) +} \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml deleted file mode 100644 index fde2605bc81..00000000000 --- a/common/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.1-SNAPSHOT - - common - - - - 8 - 8 - - - - - org.geysermc.cumulus - cumulus - 1.0-SNAPSHOT - - - com.google.code.gson - gson - 2.8.6 - - - \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000000..0a1883bc4b8 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,86 @@ +import net.kyori.blossom.BlossomExtension + +plugins { + id("net.kyori.blossom") +} + +dependencies { + api(projects.geyserApi) + api(projects.common) + + // Jackson JSON and YAML serialization + api("com.fasterxml.jackson.core", "jackson-annotations", Versions.jacksonVersion) + api("com.fasterxml.jackson.core", "jackson-databind", Versions.jacksonVersion) + api("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", Versions.jacksonVersion) + api("com.google.guava", "guava", Versions.guavaVersion) + + api("com.nukkitx", "nbt", Versions.nbtVersion) + + // Fastutil Maps + implementation("com.nukkitx.fastutil", "fastutil-int-int-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-int-long-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-int-byte-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-int-boolean-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-object-int-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-object-object-maps", Versions.fastutilVersion) + + // Network libraries + implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) + + api("com.github.CloudburstMC.Protocol", "bedrock-v486", Versions.protocolVersion) { + exclude("com.nukkitx.network", "raknet") + exclude("com.nukkitx", "nbt") + } + + api("com.github.RednedEpic", "MCAuthLib", Versions.mcauthlibVersion) + api("com.github.GeyserMC", "MCProtocolLib", Versions.mcprotocollibversion) { + exclude("com.github.steveice10", "packetlib") + exclude("com.github.steveice10", "mcauthlib") + } + + api("com.github.steveice10", "packetlib", Versions.packetlibVersion) { + exclude("io.netty", "netty-all") + // This is still experimental - additionally, it could only really benefit standalone + exclude("io.netty.incubator", "netty-incubator-transport-native-io_uring") + } + + implementation("com.nukkitx.network", "raknet", Versions.raknetVersion) { + exclude("io.netty", "*"); + } + + implementation("io.netty", "netty-resolver-dns", Versions.nettyVersion) + implementation("io.netty", "netty-resolver-dns-native-macos", Versions.nettyVersion, null, "osx-x86_64") + implementation("io.netty", "netty-codec-haproxy", Versions.nettyVersion) + + // Network dependencies we are updating ourselves + api("io.netty", "netty-handler", Versions.nettyVersion) + + implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-x86_64") + implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-aarch_64") + implementation("io.netty", "netty-transport-native-kqueue", Versions.nettyVersion, null, "osx-x86_64") + + // Adventure text serialization + implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) + implementation("net.kyori", "adventure-text-serializer-plain", Versions.adventureVersion) + + // Kyori Misc + implementation("net.kyori", "event-api", Versions.eventVersion) + + // Test + testImplementation("junit", "junit", Versions.junitVersion) + + // Annotation Processors + annotationProcessor(projects.ap) +} + +provided(projects.ap) + +configure { + val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" + val gitVersion = "git-${branchName()}-${commitHashAbbrev()}" + + replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) + replaceToken("\${gitVersion}", gitVersion, mainFile) + replaceToken("\${buildNumber}", buildNumber(), mainFile) + replaceToken("\${branch}", branchName(), mainFile) +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml deleted file mode 100644 index 31f9a075f8a..00000000000 --- a/core/pom.xml +++ /dev/null @@ -1,374 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.1-SNAPSHOT - - core - - - 8.5.2 - 2.12.4 - 4.1.66.Final - - - - - org.geysermc - ap - 2.0.1-SNAPSHOT - provided - - - org.geysermc - geyser-api - 2.0.1-SNAPSHOT - compile - - - org.geysermc - common - 2.0.1-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} - compile - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - compile - - - com.google.guava - guava - 29.0-jre - compile - - - - com.nukkitx - nbt - - 2.1.0 - 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-v486 - 0cd24c0 - compile - - - com.nukkitx.network - raknet - - - com.nukkitx - nbt - - - - - com.nukkitx.network - raknet - 1.6.28-20211202.034102-5 - compile - - - io.netty - * - - - - - com.github.RednedEpic - MCAuthLib - 6c99331 - compile - - - com.github.GeyserMC - MCProtocolLib - 6a23a780 - compile - - - com.github.steveice10 - packetlib - - - com.github.steveice10 - mcauthlib - - - - - com.github.steveice10 - packetlib - 2.1-SNAPSHOT - 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 - event-api - 3.0.0 - 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..d9ff694bdf1 100644 --- a/core/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/core/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -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/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 68bb69b4624..681b38449d9 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -99,8 +99,11 @@ 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 int BUILD_NUMBER = Integer.parseInt("${buildNumber}"); + public static final String BRANCH = "${branch}"; /** * Oauth client ID for Microsoft authentication @@ -268,25 +271,7 @@ private void start() { } } - 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."); - } - - this.newsHandler = new NewsHandler(branch, buildNumber); + this.newsHandler = new NewsHandler(BRANCH, BUILD_NUMBER); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether @@ -499,9 +484,9 @@ public void reload() { * @return true if the version number is not 'DEV'. */ @Override - public boolean productionEnvironment() { - //noinspection ConstantConditions - changes in production - return !"DEV".equals(GeyserImpl.VERSION); + public boolean isProductionEnvironment() { + // noinspection ConstantConditions - changes in production + return !"git-local/dev-0000000".equals(GeyserImpl.GIT_VERSION); } @Override 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 89bea334334..6dd2a4c4193 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 @@ -74,17 +74,14 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] 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)) { + if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { - Properties gitProp = new Properties(); - gitProp.load(stream); - + 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 = GeyserImpl.BUILD_NUMBER; if (latestBuildNum == buildNum) { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); } else { @@ -94,7 +91,7 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] } 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.locale())); } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 94c1851931f..cef75b34461 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 94c1851931f2319a7e7f42c2fe9066b78235bc39 +Subproject commit cef75b34461d195d50bfcd274b499bd9d641c5aa diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000000..8f6ac8e85b0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +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/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..41dfb87909a --- /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.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 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 191a4070446..00000000000 --- a/pom.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - 4.0.0 - org.geysermc - geyser-parent - 2.0.1-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 - - 4.9.3 - - - - 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 - - - - - - apache.snapshots - https://repository.apache.org/snapshots/ - - - - - - - apache.snapshots - https://repository.apache.org/snapshots/ - - - 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..51c1b479df9 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,73 @@ +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + // Floodgate, Cumulus etc. + maven("https://repo.opencollab.dev/maven-releases") { + mavenContent { releasesOnly() } + } + maven("https://repo.opencollab.dev/maven-snapshots") { + mavenContent { snapshotsOnly() } + } + + // Paper, Velocity + maven("https://papermc.io/repo/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() + + maven("https://jitpack.io") { + content { includeGroupByRegex("com\\.github\\..*") } + } + } +} + +pluginManagement { + repositories { + gradlePluginPortal() + } + 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(":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(":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 From f00c18c9fc8381dc1ca232284ab30c7ec6c0f2a9 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 16:43:15 -0600 Subject: [PATCH 105/358] This is what floodgate uses; lets use it --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b8566b2f71d..82fdcfb1834 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ pipeline { stage ('Build') { steps { sh 'git submodule update --init --recursive' - sh './gradlew shadowJar' + sh './gradlew clean build' } post { success { From fe6c87c95590e16d5d10714d4ca229ea5da767af Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 16:44:17 -0600 Subject: [PATCH 106/358] Update these as well --- Jenkinsfile | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 82fdcfb1834..14e0fd4e374 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,23 +26,25 @@ pipeline { } steps { - rtMavenDeployer( + rtGradleDeployer( id: "maven-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: 'build artifactoryPublish', + deployerId: "GRADLE_DEPLOYER", + resolverId: "GRADLE_RESOLVER" ) rtPublishBuildInfo( serverId: "opencollab-artifactory" From d1c7fe9f7e22991917c2361274ca42279d1d980e Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 16:51:34 -0600 Subject: [PATCH 107/358] Make gradlew executable --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 865ee5684c98e512faac379f7ba36a20d03b47cb Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 16:52:52 -0600 Subject: [PATCH 108/358] Add the wrapper too --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59821 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..41d9927a4d4fb3f96a785543079b8df6723c946b GIT binary patch literal 59821 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 literal 0 HcmV?d00001 From bf20f2821ddf89f4413528fd669fa23b3690233f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 17:17:34 -0600 Subject: [PATCH 109/358] Publish jar for ap & other fixes --- ap/pom.xml | 14 -------------- .../processor/BlockEntityProcessor.java | 2 +- .../{ => geyser}/processor/ClassProcessor.java | 2 +- .../processor/CollisionRemapperProcessor.java | 2 +- .../processor/ItemRemapperProcessor.java | 2 +- .../processor/PacketTranslatorProcessor.java | 2 +- .../processor/SoundHandlerProcessor.java | 2 +- .../services/javax.annotation.processing.Processor | 10 +++++----- .../main/kotlin/geyser.base-conventions.gradle.kts | 2 ++ build.gradle.kts | 2 ++ 10 files changed, 15 insertions(+), 25 deletions(-) delete mode 100644 ap/pom.xml rename ap/src/main/java/org/geysermc/{ => geyser}/processor/BlockEntityProcessor.java (97%) rename ap/src/main/java/org/geysermc/{ => geyser}/processor/ClassProcessor.java (99%) rename ap/src/main/java/org/geysermc/{ => geyser}/processor/CollisionRemapperProcessor.java (97%) rename ap/src/main/java/org/geysermc/{ => geyser}/processor/ItemRemapperProcessor.java (97%) rename ap/src/main/java/org/geysermc/{ => geyser}/processor/PacketTranslatorProcessor.java (97%) rename ap/src/main/java/org/geysermc/{ => geyser}/processor/SoundHandlerProcessor.java (97%) diff --git a/ap/pom.xml b/ap/pom.xml deleted file mode 100644 index dce28a7d7b4..00000000000 --- a/ap/pom.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - 4.0.0 - - org.geysermc - geyser-parent - 2.0.1-SNAPSHOT - - - ap - 2.0.1-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 99% rename from ap/src/main/java/org/geysermc/processor/ClassProcessor.java rename to ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java index 0f730aabad9..cd9f24ba0d1 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; 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 index 463d1efad14..1f6475b61a8 100644 --- a/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,5 +1,5 @@ -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 +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/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 211455d31c7..2f57a97db2a 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -37,5 +37,7 @@ publishing { groupId = project.group as String artifactId = project.name version = project.version as String + + artifact(tasks["jar"]) } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 573687c7f71..6037f65d33a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.accessors.dm.ApProjectDependency + plugins { `java-library` id("geyser.build-logic") From 83c2b72008c3b320496a6ef706357733b470241f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 17:20:29 -0600 Subject: [PATCH 110/358] Try using shadow conventions for now? --- build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts | 2 -- build.gradle.kts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 2f57a97db2a..211455d31c7 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -37,7 +37,5 @@ publishing { groupId = project.group as String artifactId = project.name version = project.version as String - - artifact(tasks["jar"]) } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 6037f65d33a..9207dfd3ff9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ subprojects { when (this) { in platforms -> plugins.apply("geyser.platform-conventions") api -> plugins.apply("geyser.shadow-conventions") - else -> plugins.apply("geyser.base-conventions") + else -> plugins.apply("geyser.shadow-conventions") } } } \ No newline at end of file From 12b3bcd6b87225955a2732062ab16b4d8e7cf704 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 17:26:50 -0600 Subject: [PATCH 111/358] Use api for the time being since that worked locally --- build.gradle.kts | 2 +- core/build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9207dfd3ff9..6037f65d33a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ subprojects { when (this) { in platforms -> plugins.apply("geyser.platform-conventions") api -> plugins.apply("geyser.shadow-conventions") - else -> plugins.apply("geyser.shadow-conventions") + else -> plugins.apply("geyser.base-conventions") } } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 0a1883bc4b8..1400c405fc1 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -70,11 +70,11 @@ dependencies { testImplementation("junit", "junit", Versions.junitVersion) // Annotation Processors + api(projects.ap) + annotationProcessor(projects.ap) } -provided(projects.ap) - configure { val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" val gitVersion = "git-${branchName()}-${commitHashAbbrev()}" From 55ac39abc3e27d2d6d8cdb93860236bb5950549c Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 17:31:58 -0600 Subject: [PATCH 112/358] Exclude the right jars --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 14e0fd4e374..09bccf5d201 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/original-*.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/*-sources.jar', excludes: 'bootstrap/**/build/libs/*-unshaded.jar', fingerprint: true } } } From b20546c51563475e31727cb1a356c4409063eaf8 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 17:34:05 -0600 Subject: [PATCH 113/358] Fix syntax --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 09bccf5d201..24acee78a5c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/*-sources.jar', excludes: 'bootstrap/**/build/libs/*-unshaded.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/*-sources.jar,bootstrap/**/build/libs/*-unshaded.jar', fingerprint: true } } } From 7428998d71417f4d5592df8662d4632605221263 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 22:43:06 -0600 Subject: [PATCH 114/358] Fix variable replacement by using Indra --- build-logic/src/main/kotlin/extensions.kt | 16 ---------------- core/build.gradle.kts | 10 +++++++--- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index a2c90cd1f79..ae8d578df88 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -24,25 +24,9 @@ */ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import net.kyori.indra.git.IndraGitExtension import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.the - -fun Project.lastCommitHash(): String? = - the().commit()?.name?.substring(0, 7) - -// retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project -// some properties might be specific to Jenkins -fun Project.branchName(): String = - System.getProperty("GIT_BRANCH", "local/dev") -fun Project.commitHashAbbrev(): String = - System.getProperty("GIT_COMMIT", "0000000") -fun Project.versionName(): String = - System.getProperty("GIT_VERSION", "local/dev") -fun Project.buildNumber(): Int = - Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")) fun Project.relocate(pattern: String) { tasks.named("shadowJar") { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1400c405fc1..b163d3794e4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,7 +1,9 @@ +import net.kyori.indra.git.IndraGitExtension import net.kyori.blossom.BlossomExtension plugins { id("net.kyori.blossom") + id("net.kyori.indra.git") } dependencies { @@ -76,11 +78,13 @@ dependencies { } configure { + val indra = the() + val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${branchName()}-${commitHashAbbrev()}" + val gitVersion = "git-${indra.branchName()}-${indra.commit()?.name?.substring(0, 7)}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) - replaceToken("\${buildNumber}", buildNumber(), mainFile) - replaceToken("\${branch}", branchName(), mainFile) + replaceToken("\${buildNumber}", Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")), mainFile) + replaceToken("\${branch}", indra.branchName(), mainFile) } \ No newline at end of file From 9bcd62937d566866d90f0199531ac9eb3fc5652f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 22:49:27 -0600 Subject: [PATCH 115/358] Handle null better --- core/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index b163d3794e4..e14d69711ec 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -81,10 +81,10 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${indra.branchName()}-${indra.commit()?.name?.substring(0, 7)}" + val gitVersion = "git-${indra.branchName() ?: "DEV"}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) replaceToken("\${buildNumber}", Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")), mainFile) - replaceToken("\${branch}", indra.branchName(), mainFile) + replaceToken("\${branch}", indra.branchName() ?: "DEV", mainFile) } \ No newline at end of file From 28dca21892c7f718a6d020a47762961428a7dab1 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 22:54:56 -0600 Subject: [PATCH 116/358] Try this for branch name --- core/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e14d69711ec..e908c40dbf3 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -81,10 +81,10 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${indra.branchName() ?: "DEV"}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" + val gitVersion = "git-${indra.branch()?.name ?: "DEV"}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) replaceToken("\${buildNumber}", Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")), mainFile) - replaceToken("\${branch}", indra.branchName() ?: "DEV", mainFile) + replaceToken("\${branch}", indra.branch()?.name ?: "DEV", mainFile) } \ No newline at end of file From e1e4b5059c67f63e85e67d8c7b52728afee4fe72 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 23:01:24 -0600 Subject: [PATCH 117/358] Try command line --- core/build.gradle.kts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e908c40dbf3..91ad66cabaa 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,6 +1,8 @@ import net.kyori.indra.git.IndraGitExtension import net.kyori.blossom.BlossomExtension +import java.io.ByteArrayOutputStream + plugins { id("net.kyori.blossom") id("net.kyori.indra.git") @@ -81,10 +83,20 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${indra.branch()?.name ?: "DEV"}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" + val gitVersion = "git-${branchName()}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) replaceToken("\${buildNumber}", Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")), mainFile) - replaceToken("\${branch}", indra.branch()?.name ?: "DEV", mainFile) + replaceToken("\${branch}", branchName(), mainFile) +} + +fun Project.branchName(): String { + val out = ByteArrayOutputStream() + exec { + commandLine = listOf("git", "rev-parse", "--abbrev-ref", "HEAD") + standardOutput = out + } + + return out.toString(Charsets.UTF_8.name()).trim() } \ No newline at end of file From 2a667ed0963a599129e57c5db667c76c8f2432c2 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 27 Feb 2022 23:11:27 -0600 Subject: [PATCH 118/358] Use System.getenv instead of System.getProperty --- core/build.gradle.kts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 91ad66cabaa..ac21acd2bf4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -87,16 +87,12 @@ configure { replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) - replaceToken("\${buildNumber}", Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")), mainFile) + replaceToken("\${buildNumber}", buildNumber(), mainFile) replaceToken("\${branch}", branchName(), mainFile) } -fun Project.branchName(): String { - val out = ByteArrayOutputStream() - exec { - commandLine = listOf("git", "rev-parse", "--abbrev-ref", "HEAD") - standardOutput = out - } +fun Project.branchName(): String = + System.getenv("GIT_BRANCH") ?: "local/dev" - return out.toString(Charsets.UTF_8.name()).trim() -} \ No newline at end of file +fun Project.buildNumber(): Int = + Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") \ No newline at end of file From 5a5b2794a5120f9d290794783d05c7a46ff76bd6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:19:13 -0500 Subject: [PATCH 119/358] Bump to 2.0.2-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index cd8b811a7cc..1d09511e0e9 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:core:2.0.1-SNAPSHOT' - shadow('org.geysermc:core:2.0.1-SNAPSHOT') { + implementation 'org.geysermc:core:2.0.2-SNAPSHOT' + shadow('org.geysermc:core:2.0.2-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 14539564854..feafc93cea3 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18 yarn_mappings=1.18+build.1 loader_version=0.12.8 # Mod Properties -mod_version=2.0.1-SNAPSHOT +mod_version=2.0.2-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From 3e71e3fb0b860d1faa197927b1c7b6bdd1c4a6e7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Feb 2022 14:30:00 -0500 Subject: [PATCH 120/358] Require 1.18.2 --- bootstrap/fabric/gradle.properties | 4 ++-- .../mixin/server/MinecraftDedicatedServerMixin.java | 9 ++++----- bootstrap/fabric/src/main/resources/fabric.mod.json | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index feafc93cea3..31b3c82dd4d 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18 -yarn_mappings=1.18+build.1 +minecraft_version=1.18.2 +yarn_mappings=1.18.2+build.1 loader_version=0.12.8 # Mod Properties mod_version=2.0.2-SNAPSHOT diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java index f5e9a121d49..e30181f003d 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -29,15 +29,14 @@ import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.datafixers.DataFixer; import net.minecraft.resource.ResourcePackManager; -import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.SaveLoader; import net.minecraft.server.WorldGenerationProgressListenerFactory; import net.minecraft.server.dedicated.MinecraftDedicatedServer; import net.minecraft.util.UserCache; -import net.minecraft.util.registry.DynamicRegistryManager; -import net.minecraft.world.SaveProperties; import net.minecraft.world.level.storage.LevelStorage; import org.geysermc.platform.fabric.GeyserServerPortGetter; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import java.net.Proxy; @@ -45,8 +44,8 @@ @Mixin(MinecraftDedicatedServer.class) public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { // Constructor to compile - public MinecraftDedicatedServerMixin(Thread thread, DynamicRegistryManager.Impl impl, LevelStorage.Session session, SaveProperties saveProperties, ResourcePackManager resourcePackManager, Proxy proxy, DataFixer dataFixer, ServerResourceManager serverResourceManager, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { - super(thread, impl, session, saveProperties, resourcePackManager, proxy, dataFixer, serverResourceManager, minecraftSessionService, gameProfileRepository, userCache, worldGenerationProgressListenerFactory); + public MinecraftDedicatedServerMixin(Thread serverThread, LevelStorage.Session session, ResourcePackManager dataPackManager, SaveLoader saveLoader, Proxy proxy, DataFixer dataFixer, @Nullable MinecraftSessionService sessionService, @Nullable GameProfileRepository gameProfileRepo, @Nullable UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { + super(serverThread, session, dataPackManager, saveLoader, proxy, dataFixer, sessionService, gameProfileRepo, userCache, worldGenerationProgressListenerFactory); } @Override diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index baa4888a566..54478f15005 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": ">=1.17.1" + "minecraft": ">=1.18.2" } } From 05f7b01540a1ab5f3585d96c97098a130baa54b8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:23:58 -0500 Subject: [PATCH 121/358] Relocate Adventure --- bootstrap/fabric/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 1d09511e0e9..0c2735eb69b 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -84,6 +84,7 @@ shadowJar { 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") } jar { From d4b19616620b8b302c068bfaf0161ca651dba35e Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sat, 12 Mar 2022 16:45:25 +0100 Subject: [PATCH 122/358] Update download link to 1.18 (#47) --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index e5129111a29..ef83f6db757 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -2,7 +2,7 @@ [![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/) -Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.17/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/java-1.17/) +Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.18/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/java-1.18/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) From f53c3b71c074b20f60462be7c2d7ea66ea8bd96a Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 20:46:30 -0500 Subject: [PATCH 123/358] Update dependencies --- bootstrap/spigot/build.gradle.kts | 2 +- build-logic/src/main/kotlin/Versions.kt | 34 ++++++++++++------------- core/build.gradle.kts | 6 ++--- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index affb243b356..767a20d7187 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,6 +1,6 @@ val paperVersion = "1.17.1-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet val viaVersion = "4.0.0" -val adaptersVersion = "1.3-SNAPSHOT" +val adaptersVersion = "1.4-SNAPSHOT" dependencies { api(projects.core) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index c6348bc6546..b7b63903155 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -24,21 +24,21 @@ */ object Versions { - const val jacksonVersion= "2.12.4" - const val fastutilVersion= "8.5.2" - const val nettyVersion= "4.1.66.Final" - const val guavaVersion= "29.0-jre" - const val nbtVersion= "2.1.0" - const val websocketVersion= "1.5.1" - const val protocolVersion= "0cd24c0" - const val raknetVersion= "1.6.28-SNAPSHOT" - const val mcauthlibVersion= "6c99331" - const val mcprotocollibversion= "6a23a780" - const val packetlibVersion= "2.1-SNAPSHOT" - const val adventureVersion= "4.9.3" - const val eventVersion= "3.0.0" - const val junitVersion= "4.13.1" - const val checkerQualVersion= "3.19.0" - const val cumulusVersion = "1.0-SNAPSHOT" - const val log4jVersion = "2.17.1" + const val jacksonVersion = "2.12.4" + const val fastutilVersion = "8.5.2" + const val nettyVersion = "4.1.66.Final" + const val guavaVersion = "29.0-jre" + const val nbtVersion = "2.1.0" + const val websocketVersion = "1.5.1" + const val protocolVersion = "0cd24c0" + const val raknetVersion = "1.6.28-SNAPSHOT" + const val mcauthlibVersion = "d9d773e" + const val mcprotocollibversion = "0771504" + const val packetlibVersion = "2.1-SNAPSHOT" + const val adventureVersion = "4.9.3" + const val eventVersion = "3.0.0" + const val junitVersion = "4.13.1" + const val checkerQualVersion = "3.19.0" + const val cumulusVersion = "1.0-SNAPSHOT" + const val log4jVersion = "2.17.1" } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ac21acd2bf4..28490a3af46 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -36,10 +36,10 @@ dependencies { exclude("com.nukkitx", "nbt") } - api("com.github.RednedEpic", "MCAuthLib", Versions.mcauthlibVersion) + api("com.github.GeyserMC", "MCAuthLib", Versions.mcauthlibVersion) api("com.github.GeyserMC", "MCProtocolLib", Versions.mcprotocollibversion) { - exclude("com.github.steveice10", "packetlib") - exclude("com.github.steveice10", "mcauthlib") + exclude("com.github.GeyserMC", "packetlib") + exclude("com.github.GeyserMC", "mcauthlib") } api("com.github.steveice10", "packetlib", Versions.packetlibVersion) { From 1232c02c8e2fb810efd49ee36694da4daaa5fde2 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 21:13:37 -0500 Subject: [PATCH 124/358] Do not compile ap into jar --- core/build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 28490a3af46..da1a6cd3e16 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,8 +1,6 @@ import net.kyori.indra.git.IndraGitExtension import net.kyori.blossom.BlossomExtension -import java.io.ByteArrayOutputStream - plugins { id("net.kyori.blossom") id("net.kyori.indra.git") @@ -74,7 +72,7 @@ dependencies { testImplementation("junit", "junit", Versions.junitVersion) // Annotation Processors - api(projects.ap) + compileOnly(projects.ap) annotationProcessor(projects.ap) } From 9939a26a5bbbe6902fe4e7dd8779998946ebaefa Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 21:55:29 -0500 Subject: [PATCH 125/358] Add RemoteServer API --- .../org/geysermc/geyser/api/GeyserApi.java | 9 +++ .../geyser/api/network}/AuthType.java | 16 +---- .../geyser/api/network/RemoteServer.java | 67 +++++++++++++++++++ .../bungeecord/GeyserBungeePlugin.java | 6 +- .../spigot/GeyserSpigotPingPassthrough.java | 4 +- .../platform/spigot/GeyserSpigotPlugin.java | 10 +-- .../spigot/GeyserSpigotVersionChecker.java | 14 ++-- .../GeyserSpigotLegacyNativeWorldManager.java | 4 +- .../manager/GeyserSpigotWorldManager.java | 4 +- .../sponge/GeyserSpongePingPassthrough.java | 4 +- .../velocity/GeyserVelocityPlugin.java | 6 +- .../network/session/GeyserSession.java | 4 +- .../geysermc/geyser/FloodgateKeyLoader.java | 4 +- .../java/org/geysermc/geyser/GeyserImpl.java | 25 +++++-- .../command/defaults/VersionCommand.java | 10 ++- .../configuration/GeyserConfiguration.java | 2 +- .../GeyserJacksonConfiguration.java | 11 ++- .../org/geysermc/geyser/dump/DumpInfo.java | 12 ++-- .../network/ConnectorServerEventHandler.java | 8 +-- ...necraftProtocol.java => GameProtocol.java} | 13 +++- .../geyser/network/QueryPacketHandler.java | 2 +- .../geyser/network/RemoteServerImpl.java | 32 +++++++++ .../geyser/network/UpstreamPacketHandler.java | 10 +-- .../ping/GeyserLegacyPingPassthrough.java | 4 +- .../loader/EnchantmentRegistryLoader.java | 4 +- .../loader/PotionMixRegistryLoader.java | 4 +- .../geyser/registry/type/ItemMapping.java | 4 +- .../geyser/session/GeyserSession.java | 33 ++++----- .../org/geysermc/geyser/skin/SkinManager.java | 2 +- .../geysermc/geyser/text/MinecraftLocale.java | 4 +- .../inventory/item/BannerTranslator.java | 4 +- .../inventory/item/CompassTranslator.java | 4 +- .../inventory/item/PotionTranslator.java | 4 +- .../inventory/item/TippedArrowTranslator.java | 6 +- .../entity/CampfireBlockEntityTranslator.java | 4 +- ...SetLocalPlayerAsInitializedTranslator.java | 4 +- .../java/JavaCustomPayloadTranslator.java | 4 +- .../java/JavaGameProfileTranslator.java | 6 +- .../java/JavaLoginDisconnectTranslator.java | 4 +- .../protocol/java/JavaLoginTranslator.java | 4 +- 40 files changed, 249 insertions(+), 127 deletions(-) rename {core/src/main/java/org/geysermc/geyser/session/auth => api/geyser/src/main/java/org/geysermc/geyser/api/network}/AuthType.java (78%) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java rename core/src/main/java/org/geysermc/geyser/network/{MinecraftProtocol.java => GameProtocol.java} (93%) create mode 100644 core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java 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 b5a0c7897f4..59a254eccbb 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 @@ -33,6 +33,7 @@ import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; +import org.geysermc.geyser.api.network.RemoteServer; import java.util.List; import java.util.UUID; @@ -105,6 +106,14 @@ public interface GeyserApi extends GeyserApiBase { */ EventBus eventBus(); + /** + * Get's the default {@link RemoteServer} configured + * within the config file that is used by default. + * + * @return the default remote server used within Geyser + */ + RemoteServer getDefaultRemoteServer(); + /** * Gets the current {@link GeyserApiBase} instance. * 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 78% 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..f52a2e7baa4 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,20 +23,15 @@ * @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.io.IOException; - @Getter public enum AuthType { OFFLINE, ONLINE, - FLOODGATE; + HYBRID; public static final AuthType[] VALUES = values(); @@ -60,11 +55,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/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java new file mode 100644 index 00000000000..b13ae593055 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * Represents the Java server that Geyser is connecting to. + */ +public interface RemoteServer { + + /** + * Gets the IP address of the remote server. + * + * @return the IP address of the remote server + */ + String address(); + + /** + * Gets the port of the remote server. + * + * @return the port of the remote server + */ + int port(); + + /** + * Gets the protocol version of the remote server. + * + * @return the protocol version of the remote server + */ + int protocolVersion(); + + /** + * Gets the Minecraft version of the remote server. + * + * @return the Minecraft version of the remote server + */ + String minecraftVersion(); + + /** + * Gets the {@link AuthType} required by the remote server. + * + * @return the auth type required by the remote server + */ + AuthType authType(); +} 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 0aa82d9cdc6..79e18ad8208 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 @@ -32,7 +32,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.command.GeyserCommandManager; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -109,13 +109,13 @@ public void onEnable() { return; } - if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { + if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && 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) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + geyserConfig.getRemote().setAuthType(AuthType.HYBRID); } geyserConfig.loadFloodgate(this); 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 19153b75015..ca1d4160fcb 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 @@ -31,7 +31,7 @@ import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.util.CachedServerIcon; import org.geysermc.geyser.ping.GeyserPingInfo; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import javax.annotation.Nonnull; @@ -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 7f1c4aa0692..c7088cfa6be 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 @@ -40,7 +40,7 @@ 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.GeyserSpigotCommandExecutor; @@ -49,7 +49,7 @@ 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.api.network.AuthType; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -136,14 +136,14 @@ public void onEnable() { return; } - if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { + if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && 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."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + geyserConfig.getRemote().setAuthType(AuthType.HYBRID); } geyserConfig.loadFloodgate(this); @@ -344,7 +344,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/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/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 2e0491db887..816edb23153 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,7 +32,7 @@ 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.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; @@ -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/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index a03549444a2..b9bbba9d354 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 @@ -38,7 +38,7 @@ 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.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.level.GeyserWorldManager; @@ -57,7 +57,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { /** * The current client protocol version for ViaVersion usage. */ - protected static final int CLIENT_PROTOCOL_VERSION = MinecraftProtocol.getJavaProtocolVersion(); + protected static final int CLIENT_PROTOCOL_VERSION = GameProtocol.getJavaProtocolVersion(); private final Plugin plugin; 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..63900d61516 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 @@ -26,7 +26,7 @@ package org.geysermc.geyser.platform.sponge; import org.geysermc.geyser.ping.GeyserPingInfo; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.Sponge; @@ -73,7 +73,7 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { ), new GeyserPingInfo.Version( event.getResponse().getVersion().getName(), - MinecraftProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge + GameProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge ); event.getResponse().getPlayers().get().getProfiles().stream() .map(GameProfile::getName) 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 a79fbb30b18..f4f6985f35a 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 @@ -45,7 +45,7 @@ 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.api.network.AuthType; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.Nullable; @@ -128,14 +128,14 @@ public void onEnable() { } catch (ClassNotFoundException ignored) { } - if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { + if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } else if (geyserConfig.isAutoconfiguredRemote() && proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + geyserConfig.getRemote().setAuthType(AuthType.HYBRID); } geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); 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 aa60e25bacf..21aa35efcad 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() { diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java index 6d47c38c6b5..cb10b717d78 100644 --- a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -26,7 +26,7 @@ package org.geysermc.geyser; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.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().getAuthType() != AuthType.HYBRID) { return geyserDataFolder.resolve(config.getFloodgateKeyFile()); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 7a908cc55ff..bdf68beefa8 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -55,6 +55,7 @@ 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.RemoteServer; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; @@ -62,6 +63,8 @@ import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.ConnectorServerEventHandler; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.network.RemoteServerImpl; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -69,7 +72,7 @@ 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.api.network.AuthType; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; @@ -83,7 +86,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; @@ -142,6 +144,8 @@ public class GeyserImpl implements GeyserApi { private final EventBus eventBus; private final GeyserExtensionManager extensionManager; + private final RemoteServer remoteServer; + private Metrics metrics; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; @@ -200,6 +204,14 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { } } + this.remoteServer = new RemoteServerImpl( + config.getRemote().getAddress(), + config.getRemote().getPort(), + GameProtocol.getJavaProtocolVersion(), + GameProtocol.getJavaMinecraftVersion(), + config.getRemote().getAuthType() + ); + double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; if (isGui) { @@ -212,7 +224,7 @@ 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().getAuthType() == AuthType.HYBRID) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } } @@ -270,7 +282,7 @@ 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; - if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { + if (config.getRemote().getAuthType() == AuthType.HYBRID) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); @@ -558,6 +570,11 @@ public EventBus eventBus() { return this.eventBus; } + @Override + public RemoteServer getDefaultRemoteServer() { + return null; + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); 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 6dd2a4c4193..eabd3800c50 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 @@ -30,18 +30,16 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.network.MinecraftProtocol; +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 { @@ -56,14 +54,14 @@ public VersionCommand(GeyserImpl geyser, String name, String description, String @Override 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 { 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 7bb73a648d5..be9e3e3748f 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.GeyserLocale; 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 46335044138..f152efa531a 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -35,7 +35,7 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.GeyserLocale; @@ -208,7 +208,7 @@ public static class RemoteConfiguration implements IRemoteConfiguration { private int port = 25565; @Setter - @JsonDeserialize(using = AuthType.Deserializer.class) + @JsonDeserialize(using = AuthTypeDeserializer.class) @JsonProperty("auth-type") private AuthType authType = AuthType.ONLINE; @@ -274,4 +274,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/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 28e81d8e70a..4c2f5d651b1 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -40,7 +40,7 @@ import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.text.AsteriskSerializer; 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.util.FileUtils; import org.geysermc.geyser.util.WebUtils; @@ -216,11 +216,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(); } } 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 892ddcb643f..02f17aa4806 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -50,7 +50,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. @@ -104,8 +104,8 @@ 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.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().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { @@ -167,7 +167,7 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { @Override public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { try { - bedrockServerSession.setPacketCodec(MinecraftProtocol.DEFAULT_BEDROCK_CODEC); + bedrockServerSession.setPacketCodec(GameProtocol.DEFAULT_BEDROCK_CODEC); 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 93% 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 7ab3813751e..4164976b6cb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -37,7 +37,7 @@ /** * 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. @@ -103,6 +103,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 */ @@ -127,6 +136,6 @@ public static String getAllSupportedJavaVersions() { return joiner.toString(); } - private MinecraftProtocol() { + private GameProtocol() { } } 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 8b08098f25b..f11851c1b5a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java @@ -175,7 +175,7 @@ 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); diff --git a/core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java b/core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java new file mode 100644 index 00000000000..a0d919c3a03 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.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.network; + +import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.RemoteServer; + +public record RemoteServerImpl(String address, int port, int protocolVersion, String minecraftVersion, AuthType authType) implements RemoteServer { +} 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 f3ffaeeff41..8714dfb37fd 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.pack.ResourcePack; @@ -69,14 +69,14 @@ public boolean handle(LoginPacket loginPacket) { return true; } - BedrockPacketCodec packetCodec = MinecraftProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); + BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); if (packetCodec == null) { - String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions(); - if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + String supportedVersions = GameProtocol.getAllSupportedBedrockVersions(); + if (loginPacket.getProtocolVersion() > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); return true; - } else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + } else if (loginPacket.getProtocolVersion() < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return true; } 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 d438110d0d9..c3a242501db 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -29,7 +29,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.nukkitx.nbt.util.VarInts; 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; @@ -83,7 +83,7 @@ public void run() { ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); 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/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..5bb78e8dbb1 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,7 +26,7 @@ package org.geysermc.geyser.registry.loader; import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; -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.inventory.item.Potion; @@ -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/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index 28d41ba4612..332ab016772 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -28,7 +28,7 @@ import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; import java.util.Set; @@ -38,7 +38,7 @@ @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(), + BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), 64, null, null, null, 0, null, false); String javaIdentifier; 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 a46d9ebca79..b72fe29a25a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -78,6 +78,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import lombok.experimental.Accessors; import org.checkerframework.common.value.qual.IntRange; import org.geysermc.common.PlatformType; import org.geysermc.cumulus.Form; @@ -87,6 +88,7 @@ import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; @@ -106,7 +108,7 @@ 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.api.network.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.session.cache.*; import org.geysermc.geyser.skin.FloodgateSkinUploader; @@ -143,14 +145,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private BedrockClientData clientData; - /* 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 @@ -567,9 +564,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.getRemoteServer(); } /** @@ -666,7 +661,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.HYBRID) { validUsername = username.replace(' ', '_'); } @@ -823,17 +818,17 @@ 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.HYBRID; // 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); } else { - downstream = new TcpClientSession(this.remoteAddress, this.remotePort, this.protocol); + downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), this.protocol); disableSrvResolving(); } @@ -913,13 +908,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.HYBRID) { uuid = new UUID(0, Long.parseLong(authData.xuid())); } else { uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + protocol.getProfile().getName()).getBytes(StandardCharsets.UTF_8)); @@ -949,7 +944,7 @@ public void disconnected(DisconnectedEvent event) { String disconnectMessage; Throwable cause = event.getCause(); if (cause instanceof UnexpectedEncryptionException) { - if (remoteAuthType != AuthType.FLOODGATE) { + if (remoteServer.authType() != AuthType.HYBRID) { // Server expects online mode disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale()); // Explain that they may be looking for Floodgate. @@ -976,7 +971,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(); 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 4269110f560..7ee3cbefef5 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -34,7 +34,7 @@ import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import org.geysermc.geyser.GeyserImpl; -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.session.auth.BedrockClientData; 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 57749f29ea5..e5f3c05540b 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/inventory/item/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java index 15f7c57ce3b..b92371e7c03 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java @@ -32,7 +32,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; 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; @@ -80,7 +80,7 @@ private static CompoundTag getPatternTag(String pattern, int color) { } public BannerTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getItems() .values() .stream() 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 9637f1aa9e5..e7a8d4d6ce7 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; @@ -44,7 +44,7 @@ public class CompassTranslator extends ItemTranslator { private final List appliedItems; public CompassTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getItems() .values() .stream() 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 54a6deadb4f..3e07eb1db7e 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,7 +30,7 @@ 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.network.GameProtocol; import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; @@ -45,7 +45,7 @@ public class PotionTranslator extends ItemTranslator { private final List appliedItems; public PotionTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getItems() .values() .stream() 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 35e8baa0704..0b0571c6605 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,7 +30,7 @@ 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.network.GameProtocol; import org.geysermc.geyser.inventory.item.TippedArrowPotion; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; @@ -44,12 +44,12 @@ public class TippedArrowTranslator extends ItemTranslator { private final List appliedItems; - 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(); public TippedArrowTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getItems() .values() .stream() 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..6ec0effcafe 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 @@ -30,7 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; 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; @@ -48,7 +48,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/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index 8641a35ff2a..8b86be69b78 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,7 +41,7 @@ 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) { 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 33fb4f15c34..f084c1d8446 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 @@ -32,7 +32,7 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -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; @@ -49,7 +49,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator Date: Sat, 19 Mar 2022 22:00:04 -0500 Subject: [PATCH 126/358] Remove unecessary getter --- .../main/java/org/geysermc/geyser/api/network/AuthType.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java index f52a2e7baa4..4144f02c112 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.api.network; -import lombok.Getter; - -@Getter public enum AuthType { OFFLINE, ONLINE, From f8e966266540415578e1b15dbef0eb8f6a93b365 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 22:30:12 -0500 Subject: [PATCH 127/358] Add BedrockListener API and fix other stuffs --- .../org/geysermc/geyser/api/GeyserApi.java | 11 ++- .../geyser/api/network/BedrockListener.java | 74 +++++++++++++++++++ .../java/org/geysermc/geyser/GeyserImpl.java | 20 ++++- .../geyser/network/BedrockListenerImpl.java | 31 ++++++++ 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java 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 59a254eccbb..e103d05255d 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 @@ -33,6 +33,7 @@ import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; 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; @@ -112,7 +113,15 @@ public interface GeyserApi extends GeyserApiBase { * * @return the default remote server used within Geyser */ - RemoteServer getDefaultRemoteServer(); + RemoteServer defaultRemoteServer(); + + /** + * Gets the {@link BedrockListener} used for listening + * for Minecraft: Bedrock Edition client connections. + * + * @return the listener used for Bedrock client connectins + */ + BedrockListener bedrockListener(); /** * Gets the current {@link GeyserApiBase} instance. 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..648f83e47a7 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.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.api.network; + +/** + * 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 + */ + 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. + *

+ * 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. + *

+ * 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/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index bdf68beefa8..a4787127f01 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -55,6 +55,7 @@ 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.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -62,6 +63,7 @@ import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.network.BedrockListenerImpl; import org.geysermc.geyser.network.ConnectorServerEventHandler; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.RemoteServerImpl; @@ -145,6 +147,7 @@ public class GeyserImpl implements GeyserApi { private final GeyserExtensionManager extensionManager; private final RemoteServer remoteServer; + private final BedrockListener bedrockListener; private Metrics metrics; @@ -212,6 +215,14 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { config.getRemote().getAuthType() ); + this.bedrockListener = new BedrockListenerImpl( + config.getBedrock().getAddress(), + config.getBedrock().getPort(), + config.getBedrock().getMotd1(), + config.getBedrock().getMotd2(), + config.getBedrock().getServerName() + ); + double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; if (isGui) { @@ -571,8 +582,13 @@ public EventBus eventBus() { } @Override - public RemoteServer getDefaultRemoteServer() { - return null; + public RemoteServer defaultRemoteServer() { + return this.remoteServer; + } + + @Override + public BedrockListener bedrockListener() { + return this.bedrockListener; } public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { diff --git a/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java b/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java new file mode 100644 index 00000000000..e617be36a56 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.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.network; + +import org.geysermc.geyser.api.network.BedrockListener; + +public record BedrockListenerImpl(String address, int port, String primaryMotd, String secondaryMotd, String serverName) implements BedrockListener { +} From 95747d5649a73ce7b0bc35ffd10aa234015c15fd Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 22:35:41 -0500 Subject: [PATCH 128/358] Add maxPlayers API --- .../src/main/java/org/geysermc/geyser/api/GeyserApi.java | 9 +++++++++ core/src/main/java/org/geysermc/geyser/GeyserImpl.java | 5 +++++ 2 files changed, 14 insertions(+) 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 e103d05255d..4366e9ed691 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 @@ -123,6 +123,15 @@ public interface GeyserApi extends GeyserApiBase { */ BedrockListener bedrockListener(); + /** + * Gets the maximum number of players that + * can join this Geyser instance. + * + * @return the maximum number of players that + * can join this Geyser instance + */ + int maxPlayers(); + /** * Gets the current {@link GeyserApiBase} instance. * diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index a4787127f01..54be9f79de5 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -591,6 +591,11 @@ public BedrockListener bedrockListener() { return this.bedrockListener; } + @Override + public int maxPlayers() { + return this.getConfig().getMaxPlayers(); + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); From 6f0ff0f83d6343f0b14dce6d006d683cc43c9685 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 23:03:26 -0500 Subject: [PATCH 129/358] Update pull request workflow to use Gradle --- .github/workflows/pullrequest.yml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9621fa1d039..f5bb4c0427e 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -9,47 +9,43 @@ 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 uses: actions/setup-java@v1 with: + distribution: 'temurin' java-version: 16 + 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 Standalone) uses: actions/upload-artifact@v2 if: success() with: name: Geyser Standalone - path: bootstrap/standalone/target/Geyser.jar + path: bootstrap/standalone/build/libs/Geyser.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 From 3e8863ccf31a6c8a4207a1e6df8896fd78530b21 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 23:20:10 -0500 Subject: [PATCH 130/358] Add .gradle to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 401002e1d25..9fc9258d778 100644 --- a/.gitignore +++ b/.gitignore @@ -235,6 +235,9 @@ nbdist/ # End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode +### Gradle ### +.gradle + ### Geyser ### run/ config.yml From b26879f76c9f4d777630c206af7c1920e5a4ccdd Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 23:24:57 -0500 Subject: [PATCH 131/358] Add deprecated annotaiton so gradle stops complaining --- .../org/geysermc/connector/network/session/auth/AuthData.java | 1 + 1 file changed, 1 insertion(+) 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; From 1b1621c424e069c8590a2a9c22c3fb7c63a52b06 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 23:29:24 -0500 Subject: [PATCH 132/358] Add ViaVersion repo --- settings.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index 51c1b479df9..0a7e08a4aad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,6 +32,11 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() + // ViaVersion + maven("https://repo.viaversion.com") { + name = "viaversion" + } + maven("https://jitpack.io") { content { includeGroupByRegex("com\\.github\\..*") } } From 43ab71bdaa9bc2281ac680de5fe6aaabdce41f9e Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 19 Mar 2022 23:46:56 -0500 Subject: [PATCH 133/358] Add Sponge repository --- settings.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index 0a7e08a4aad..9f210114c1b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,9 @@ dependencyResolutionManagement { name = "viaversion" } + // Sponge + maven("https://repo.spongepowered.org/repository/maven-public/") + maven("https://jitpack.io") { content { includeGroupByRegex("com\\.github\\..*") } } From d613ac65a79781882ae196cbb1a06c7c38aced78 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 20 Mar 2022 00:05:25 -0500 Subject: [PATCH 134/358] Just catch NoSuchFileException for missing processors --- .../java/org/geysermc/geyser/processor/ClassProcessor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java index cd9f24ba0d1..e1da50f258e 100644 --- a/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/ClassProcessor.java @@ -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; @@ -167,7 +168,11 @@ private BufferedReader createReader() throws IOException { 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; } From 5ab69522782f71c422d334d47f214fc90790de6d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 19 Apr 2022 10:36:22 -0400 Subject: [PATCH 135/358] Update to 2.0.3-SNAPSHOT --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 0c2735eb69b..1127600098c 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:core:2.0.2-SNAPSHOT' - shadow('org.geysermc:core:2.0.2-SNAPSHOT') { + implementation 'org.geysermc:core:2.0.3-SNAPSHOT' + shadow('org.geysermc:core:2.0.3-SNAPSHOT') { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 31b3c82dd4d..1d9b12e678d 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18.2 yarn_mappings=1.18.2+build.1 loader_version=0.12.8 # Mod Properties -mod_version=2.0.2-SNAPSHOT +mod_version=2.0.3-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From d1cedbb823ceb626118682d51c5079217f7222a2 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 20 Apr 2022 12:36:10 -0400 Subject: [PATCH 136/358] Generate velocity-plugins.json and relocate fastutil (#2940) --- bootstrap/velocity/build.gradle.kts | 3 ++- build.gradle.kts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index f9fcafb2cfa..ab2f85b857f 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -1,11 +1,12 @@ val velocityVersion = "3.0.0" dependencies { + annotationProcessor("com.velocitypowered", "velocity-api", velocityVersion) api(projects.core) } platformRelocate("com.fasterxml.jackson") -platformRelocate("it.unimi.fastutil") +platformRelocate("it.unimi.dsi.fastutil") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") exclude("com.google.*:*") diff --git a/build.gradle.kts b/build.gradle.kts index 6037f65d33a..b10d7c35755 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,10 @@ 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( From 03b067e23e0d2690fd71fa30f1e8ae616836997e Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:37:50 -0400 Subject: [PATCH 137/358] Merge master into extensions (#2941) * Don't always store cert/client data used for skin uploaded This takes up a decent 30K of memory that we don't use after the skin is uploaded. The GameProfileTranslator cannot be run more than once per session. * Make all moon phases visible The fix to prevent integer overflows also prevented moon phases from being visible until now. Fixes #2927 * SetTimeTranslator: cast from long on the entire modulus This should fix some inaccuracies with time on older worlds. * Bump version; drop 1.17.40; support 1.18.30 * Actually bump to 2.0.3-SNAPSHOT * Fix message being sent still if a single escape character is sent * Replace instances of configs using `generateduuid` for Metrics * Fix some merge mistakes Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- README.md | 2 +- build-logic/src/main/kotlin/Versions.kt | 2 +- core/build.gradle.kts | 2 +- .../GeyserJacksonConfiguration.java | 2 +- .../entity/type/player/PlayerEntity.java | 2 + .../entity/type/player/SkullPlayerEntity.java | 2 + .../geyser/level/BedrockDimension.java | 41 + .../level/block/BlockPositionIterator.java | 2 - .../geysermc/geyser/network/GameProtocol.java | 13 +- .../geyser/network/UpstreamPacketHandler.java | 7 +- .../populator/BlockRegistryPopulator.java | 72 +- .../populator/ItemRegistryPopulator.java | 30 +- .../geyser/session/GeyserSession.java | 13 +- .../geyser/session/auth/AuthData.java | 13 +- .../session/auth/BedrockClientData.java | 6 + .../geyser/session/cache/ChunkCache.java | 5 +- .../bedrock/BedrockTextTranslator.java | 12 +- .../player/BedrockMovePlayerTranslator.java | 13 +- .../java/JavaGameProfileTranslator.java | 8 +- .../JavaLevelChunkWithLightTranslator.java | 39 +- .../java/level/JavaSetTimeTranslator.java | 5 +- .../org/geysermc/geyser/util/ChunkUtils.java | 29 +- .../geysermc/geyser/util/DimensionUtils.java | 2 +- .../geyser/util/LoginEncryptionUtils.java | 6 +- ...e.1_17_40.nbt => block_palette.1_18_0.nbt} | Bin .../bedrock/block_palette.1_18_30.nbt | Bin 0 -> 45536 bytes ...17_40.json => creative_items.1_18_30.json} | 1395 +++++++++-------- ....json => runtime_item_states.1_18_30.json} | 224 ++- 28 files changed, 1061 insertions(+), 886 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java rename core/src/main/resources/bedrock/{block_palette.1_17_40.nbt => block_palette.1_18_0.nbt} (100%) create mode 100644 core/src/main/resources/bedrock/block_palette.1_18_30.nbt rename core/src/main/resources/bedrock/{creative_items.1_17_40.json => creative_items.1_18_30.json} (84%) rename core/src/main/resources/bedrock/{runtime_item_states.1_17_40.json => runtime_item_states.1_18_30.json} (96%) diff --git a/README.md b/README.md index 23bde93d23a..bbb9532a56a 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.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.30 and Minecraft Java 1.18.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 96d98b18ba5..ce8593adcfe 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -30,7 +30,7 @@ object Versions { const val guavaVersion = "29.0-jre" const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" - const val protocolVersion = "0cd24c0" + const val protocolVersion = "29ecd7a" const val raknetVersion = "1.6.28-SNAPSHOT" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "0771504" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 31018c454fb..03260fc8f20 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) - api("com.github.CloudburstMC.Protocol", "bedrock-v486", Versions.protocolVersion) { + api("com.github.CloudburstMC.Protocol", "bedrock-v503", Versions.protocolVersion) { exclude("com.nukkitx.network", "raknet") exclude("com.nukkitx", "nbt") } 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 f152efa531a..630e2ffd644 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -36,8 +36,8 @@ import lombok.Setter; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.network.AuthType; -import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.network.CIDRMatcher; +import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 58f04a7565a..0d6c0dac19a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -37,6 +37,7 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -126,6 +127,7 @@ public void spawnEntity() { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index ce16158163e..f1a447b571b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -27,6 +27,7 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -84,6 +85,7 @@ public void spawnEntity() { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java new file mode 100644 index 00000000000..78c6b2c6a0b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.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.level; + +/** + * A data structure to represent what Bedrock believes are the height requirements for a specific dimension. + * As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash + * the client. + * + * @param minY The minimum height Bedrock Edition will accept. + * @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. + * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. + */ +public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) { + public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); + public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); + public static BedrockDimension THE_END = new BedrockDimension(0, 256, true); +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java index 425c78f1844..d22150ccfa8 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java @@ -26,8 +26,6 @@ package org.geysermc.geyser.level.block; import com.nukkitx.network.util.Preconditions; -import lombok.Getter; - public class BlockPositionIterator { private final int minX; diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 4164976b6cb..1f28a026442 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -28,11 +28,14 @@ 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.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; /** * Contains information about the supported protocols in Geyser. @@ -42,7 +45,7 @@ public final class GameProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v486.V486_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v503.V503_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -55,11 +58,11 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v486.V486_CODEC.toBuilder() .minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** 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 8714dfb37fd..66cda8f1696 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -32,16 +32,17 @@ import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.pack.ResourcePackManifest; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MathUtils; import java.io.FileInputStream; import java.io.InputStream; 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 8238bcea102..d8aa6a45692 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 @@ -28,8 +28,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -60,35 +61,50 @@ public class BlockRegistryPopulator { private static final ImmutableMap, BiFunction> BLOCK_MAPPERS; private static final BiFunction EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null; + private static final BiFunction V486_MAPPER = (bedrockIdentifier, statesBuilder) -> { + statesBuilder.remove("no_drop_bit"); // Used in skulls + if (bedrockIdentifier.equals("minecraft:glow_lichen")) { + // Moved around north, south, west + int bits = (int) statesBuilder.get("multi_face_direction_bits"); + boolean north = (bits & (1 << 2)) != 0; + boolean south = (bits & (1 << 3)) != 0; + boolean west = (bits & (1 << 4)) != 0; + if (north) { + bits |= 1 << 4; + } else { + bits &= ~(1 << 4); + } + if (south) { + bits |= 1 << 2; + } else { + bits &= ~(1 << 2); + } + if (west) { + bits |= 1 << 3; + } else { + bits &= ~(1 << 3); + } + statesBuilder.put("multi_face_direction_bits", bits); + } + return null; + }; + static { ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { - statesBuilder.remove("no_drop_bit"); // Used in skulls - if (bedrockIdentifier.equals("minecraft:glow_lichen")) { - // Moved around north, south, west - int bits = (int) statesBuilder.get("multi_face_direction_bits"); - boolean north = (bits & (1 << 2)) != 0; - boolean south = (bits & (1 << 3)) != 0; - boolean west = (bits & (1 << 4)) != 0; - if (north) { - bits |= 1 << 4; - } else { - bits &= ~(1 << 4); - } - if (south) { - bits |= 1 << 2; - } else { - bits &= ~(1 << 2); - } - if (west) { - bits |= 1 << 3; - } else { - bits &= ~(1 << 3); - } - statesBuilder.put("multi_face_direction_bits", bits); - } - return null; + .put(ObjectIntPair.of("1_18_0", Bedrock_v475.V475_CODEC.getProtocolVersion()), EMPTY_MAPPER) + .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), V486_MAPPER) + .put(ObjectIntPair.of("1_18_30", Bedrock_v503.V503_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { + // Apply these fixes too + V486_MAPPER.apply(bedrockIdentifier, statesBuilder); + return switch (bedrockIdentifier) { + case "minecraft:pistonArmCollision" -> "minecraft:piston_arm_collision"; + case "minecraft:stickyPistonArmCollision" -> "minecraft:sticky_piston_arm_collision"; + case "minecraft:movingBlock" -> "minecraft:moving_block"; + case "minecraft:tripWire" -> "minecraft:trip_wire"; + case "minecraft:seaLantern" -> "minecraft:sea_lantern"; + case "minecraft:concretePowder" -> "minecraft:concrete_powder"; + default -> null; + }; }); BLOCK_MAPPERS = stateMapperBuilder.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 0e12669e351..37b6c49f445 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 @@ -35,10 +35,12 @@ 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.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; -import it.unimi.dsi.fastutil.ints.*; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; +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.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -58,19 +60,16 @@ * Populates the item registries. */ public class ItemRegistryPopulator { - private static final Map PALETTE_VERSIONS; - - static { - PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); - PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); - } private record PaletteVersion(int protocolVersion, Map additionalTranslatedItems) { } public static void populate() { + Map paletteVersions = new Object2ObjectOpenHashMap<>(); + paletteVersions.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_30", new PaletteVersion(Bedrock_v503.V503_CODEC.getProtocolVersion(), Collections.emptyMap())); + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); TypeReference> mappingItemsType = new TypeReference<>() { }; @@ -88,7 +87,7 @@ public static void populate() { Int2IntMap dyeColors = new FixedInt2IntMap(); /* Load item palette */ - for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { + for (Map.Entry palette : paletteVersions.entrySet()) { TypeReference> paletteEntriesType = new TypeReference<>() {}; // Used to get the Bedrock namespaced ID (in instances where there are small differences) @@ -232,12 +231,15 @@ public static void populate() { } String bedrockIdentifier; - if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - bedrockIdentifier = "minecraft:music_disc_pigstep"; - } else if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { bedrockIdentifier = "minecraft:banner_pattern"; } else { bedrockIdentifier = mappingItem.getBedrockIdentifier(); + if (palette.getValue().protocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion()) { + if (bedrockIdentifier.equals("minecraft:sealantern")) { + bedrockIdentifier = "minecraft:sea_lantern"; + } + } } if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { 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 b72fe29a25a..6d8fa477b88 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.session; +import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; @@ -66,7 +67,6 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.ints.*; @@ -144,6 +144,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private AuthData authData; @Setter private BedrockClientData clientData; + /** + * Used for Floodgate skin uploading + */ + @Setter + private JsonNode certChainData; @Accessors(fluent = true) @Setter @@ -1377,7 +1382,7 @@ private void startGame() { startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); startGamePacket.setRotation(Vector2f.from(1, 1)); - startGamePacket.setSeed(-1); + startGamePacket.setSeed(-1L); startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); @@ -1426,10 +1431,6 @@ private void startGame() { settings.setServerAuthoritativeBlockBreaking(false); startGamePacket.setPlayerMovementSettings(settings); - if (upstream.getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - upstream.sendPacket(startGamePacket); } diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java index 802ee3ca093..99b7ae3afa6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java @@ -25,18 +25,7 @@ package org.geysermc.geyser.session.auth; -import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.geyser.GeyserImpl; - import java.util.UUID; -public record AuthData(String name, UUID uuid, String xuid, - JsonNode certChainData, String clientData) { - - public void upload(GeyserImpl geyser) { - // we can't upload the skin in LoginEncryptionUtil since the global server would return - // the skin too fast, that's why we upload it after we know for sure that the target server - // is ready to handle the result of the global server - geyser.getSkinUploader().uploadSkin(certChainData, clientData); - } +public record AuthData(String name, UUID uuid, String xuid) { } diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index b3601f6c3df..07dd3849185 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.session.auth; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import lombok.Setter; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.UiProfile; @@ -107,6 +109,10 @@ public final class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; + @JsonIgnore + @Setter + private String originalString = null; + public DeviceOs getDeviceOs() { return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } 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 feb1cf3a8fb..91d6b33d617 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 @@ -33,6 +33,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.chunk.GeyserChunk; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.util.MathUtils; public class ChunkCache { @@ -45,11 +46,11 @@ public class ChunkCache { private int heightY; /** - * Whether the Bedrock client believes they are in a world with a minimum of -64 and maximum of 320 + * Which dimension Bedrock understands themselves to be in. */ @Getter @Setter - private boolean isExtendedHeight = false; + private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD; public ChunkCache(GeyserSession session) { this.cache = !session.getGeyser().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing 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 1a6771cc582..91ed5aa2bb3 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 @@ -40,11 +40,8 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(GeyserSession session, TextPacket packet) { String message = packet.getMessage(); - if (message.isBlank()) { - // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! - return; - } - + // 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(); @@ -57,6 +54,11 @@ public void translate(GeyserSession session, TextPacket packet) { message = builder.toString(); } + if (message.isBlank()) { + // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! + return; + } + if (MessageTranslator.isTooLong(message, session)) { return; } 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 2fccbe48241..a63c0f3340b 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 @@ -37,14 +37,12 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; 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; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { - /* The upper and lower bounds to check for the void floor that only exists in Bedrock. These are the constants for the overworld. */ - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y = -104; - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y = BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y + 2; @Override public void translate(GeyserSession session, MovePlayerPacket packet) { @@ -124,11 +122,10 @@ public void translate(GeyserSession session, MovePlayerPacket packet) { if (notMovingUp) { int floorY = position.getFloorY(); - // If the client believes the world has extended height, then it also believes the void floor - // still exists, just at a lower spot - boolean extendedWorld = session.getChunkCache().isExtendedHeight(); - if (floorY <= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y : -38) - && floorY >= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y : -40)) { + // The void floor is offset about 40 blocks below the bottom of the world + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int voidFloorLocation = bedrockDimension.minY() - 40; + if (floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation) { // Work around there being a floor at the bottom of the world and teleport the player below it // Moving from below to above the void floor works fine entity.setPosition(entity.getPosition().sub(0, 4f, 0)); 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 3978db0df0f..6dd826bf762 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 @@ -57,7 +57,13 @@ public void translate(GeyserSession session, ClientboundGameProfilePacket packet if (remoteAuthType == AuthType.HYBRID) { // We'll send the skin upload a bit after the handshake packet (aka this packet), // because otherwise the global server returns the data too fast. - session.getAuthData().upload(session.getGeyser()); + // We upload it after we know for sure that the target server + // is ready to handle the result of the global server. + session.getGeyser().getSkinUploader().uploadSkin(session.getCertChainData(), session.getClientData().getOriginalString()); } + + // We no longer need these variables; they're just taking up space in memory now + session.setCertChainData(null); + session.getClientData().setOriginalString(null); } } 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 97b826473e0..165d90b367d 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 @@ -43,7 +43,7 @@ import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; @@ -52,26 +52,29 @@ import it.unimi.dsi.fastutil.ints.IntLists; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.entity.type.ItemFrameEntity; -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.level.BiomeTranslator; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; -import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; 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.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.translator.level.BiomeTranslator; +import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.ChunkUtils; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.*; +import java.util.BitSet; +import java.util.List; +import java.util.Map; import static org.geysermc.geyser.util.ChunkUtils.*; @@ -98,13 +101,13 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); - boolean overworld = session.getChunkCache().isExtendedHeight(); - int maxBedrockSectionY = ((overworld ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT) >> 4) - 1; + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int maxBedrockSectionY = (bedrockDimension.height() >> 4) - 1; int sectionCount; byte[] payload; ByteBuf byteBuf = null; - GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4))]; + GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + (bedrockDimension.minY() >> 4))]; try { NetInput in = new StreamNetInput(new ByteArrayInputStream(packet.getChunkData())); @@ -113,7 +116,7 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke javaChunks[sectionY] = javaSection.getChunkData(); javaBiomes[sectionY] = javaSection.getBiomeData(); - int bedrockSectionY = sectionY + (yOffset - ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4)); + int bedrockSectionY = sectionY + (yOffset - (bedrockDimension.minY() >> 4)); if (bedrockSectionY < 0 || maxBedrockSectionY < bedrockSectionY) { // Ignore this chunk section since it goes outside the bounds accepted by the Bedrock client continue; @@ -309,11 +312,11 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke } } - // As of 1.17.10, Bedrock hardcodes to always read 32 biome sections - // As of 1.18, this hardcode was lowered to 25 - boolean isNewVersion = session.getUpstream().getProtocolVersion() >= Bedrock_v475.V475_CODEC.getProtocolVersion(); - int biomeCount = isNewVersion ? 25 : 32; - int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4; + // As of 1.18.0, Bedrock hardcodes to always read 25 biome sections + // As of 1.18.30, the hardcode may now be tied to the dimension definition + boolean isNewVersion = session.getUpstream().getProtocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion(); + int biomeCount = isNewVersion ? bedrockDimension.height() >> 4 : 25; + int dimensionOffset = bedrockDimension.minY() >> 4; for (int i = 0; i < biomeCount; i++) { int biomeYOffset = dimensionOffset + i; if (biomeYOffset < yOffset) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java index 581433a5f4a..bc4e8c1ff89 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java @@ -42,7 +42,10 @@ public void translate(GeyserSession session, ClientboundSetTimePacket packet) { // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day SetTimePacket setTimePacket = new SetTimePacket(); - setTimePacket.setTime((int) Math.abs(time) % 24000); + // We use modulus to prevent an integer overflow + // 24000 is the range of ticks that a Minecraft day can be; we times by 8 so all moon phases are visible + // (Last verified behavior: Bedrock 1.18.12 / Java 1.18.2) + setTimePacket.setTime((int) (Math.abs(time) % (24000 * 8))); session.sendUpstreamPacket(setTimePacket); if (!session.isDaylightCycle() && time >= 0) { // Client thinks there is no daylight cycle but there is 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 7fdf12ec9b2..445ffb88283 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -46,23 +46,13 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; import static org.geysermc.geyser.level.block.BlockStateValues.JAVA_AIR_ID; @UtilityClass public class ChunkUtils { - /** - * The minimum height Bedrock Edition will accept. - */ - public static final int MINIMUM_ACCEPTED_HEIGHT = 0; - public static final int MINIMUM_ACCEPTED_HEIGHT_OVERWORLD = -64; - /** - * The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. - */ - public static final int MAXIMUM_ACCEPTED_HEIGHT = 256; - public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384; - /** * An empty subchunk. */ @@ -249,17 +239,20 @@ public static void loadDimensionTag(GeyserSession session, CompoundTag dimension throw new RuntimeException("Maximum Y must be a multiple of 16!"); } - int dimension = DimensionUtils.javaToBedrock(session.getDimension()); - boolean extendedHeight = dimension == 0; - session.getChunkCache().setExtendedHeight(extendedHeight); + 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); // 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 - if (minY < (extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) - || maxY > (extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT)) { + // (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ ) + if (minY < bedrockDimension.minY() || (bedrockDimension.doUpperHeightWarn() && maxY > bedrockDimension.height())) { session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds", - extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT, - extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT, + String.valueOf(bedrockDimension.minY()), + String.valueOf(bedrockDimension.height()), session.getDimension())); } 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 5af5e2c2bb9..f1aca63e868 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -109,7 +109,7 @@ public static void switchDimension(GeyserSession session, String javaDimension) // we check if the player is entering the nether and apply the nether fog to fake the fact that the client // thinks they are in the end dimension. if (BEDROCK_NETHER_ID == 2) { - if (bedrockDimension == BEDROCK_NETHER_ID) { + if (NETHER.equals(javaDimension)) { session.sendFog("minecraft:fog_hell"); } else if (previousDimension == BEDROCK_NETHER_ID) { session.removeFog("minecraft:fog_hell"); 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 3488f713cdf..d8d3e3c490c 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -155,10 +155,11 @@ private static void encryptConnectionWithCert(GeyserSession session, String clie session.setAuthenticationData(new AuthData( extraData.get("displayName").asText(), UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText(), - certChainData, clientData + extraData.get("XUID").asText() )); + session.setCertChainData(certChainData); + if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { throw new RuntimeException("Identity Public Key was not found!"); } @@ -169,6 +170,7 @@ private static void encryptConnectionWithCert(GeyserSession session, String clie JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); + data.setOriginalString(clientData); session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { diff --git a/core/src/main/resources/bedrock/block_palette.1_17_40.nbt b/core/src/main/resources/bedrock/block_palette.1_18_0.nbt similarity index 100% rename from core/src/main/resources/bedrock/block_palette.1_17_40.nbt rename to core/src/main/resources/bedrock/block_palette.1_18_0.nbt diff --git a/core/src/main/resources/bedrock/block_palette.1_18_30.nbt b/core/src/main/resources/bedrock/block_palette.1_18_30.nbt new file mode 100644 index 0000000000000000000000000000000000000000..bf7690970f792f71b62d7b7f0c9a2f80a3292a21 GIT binary patch literal 45536 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 Date: Sun, 24 Apr 2022 11:42:17 -0500 Subject: [PATCH 138/358] Rename HYBRID AuthType back to FLOODGATE --- .../java/org/geysermc/geyser/api/network/AuthType.java | 2 +- .../geyser/platform/bungeecord/GeyserBungeePlugin.java | 4 ++-- .../geyser/platform/spigot/GeyserSpigotPlugin.java | 4 ++-- .../geyser/platform/velocity/GeyserVelocityPlugin.java | 4 ++-- .../main/java/org/geysermc/geyser/FloodgateKeyLoader.java | 2 +- core/src/main/java/org/geysermc/geyser/GeyserImpl.java | 4 ++-- .../java/org/geysermc/geyser/session/GeyserSession.java | 8 ++++---- .../protocol/java/JavaCustomPayloadTranslator.java | 2 +- .../protocol/java/JavaGameProfileTranslator.java | 2 +- .../translator/protocol/java/JavaLoginTranslator.java | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java index 4144f02c112..5e1c2539db0 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java @@ -28,7 +28,7 @@ public enum AuthType { OFFLINE, ONLINE, - HYBRID; + FLOODGATE; public static final AuthType[] VALUES = values(); 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 79e18ad8208..7120a94f976 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 @@ -109,13 +109,13 @@ public void onEnable() { return; } - if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && getProxy().getPluginManager().getPlugin("floodgate") == null) { + if (geyserConfig.getRemote().getAuthType() == 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) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.HYBRID); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this); 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 100d5e1f17f..c5a8a4a259b 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 @@ -139,14 +139,14 @@ public void onEnable() { return; } - if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && Bukkit.getPluginManager().getPlugin("floodgate") == null) { + if (geyserConfig.getRemote().getAuthType() == 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."); - geyserConfig.getRemote().setAuthType(AuthType.HYBRID); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this); 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 f4f6985f35a..184d59b3ec7 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 @@ -128,14 +128,14 @@ public void onEnable() { } catch (ClassNotFoundException ignored) { } - if (geyserConfig.getRemote().getAuthType() == AuthType.HYBRID && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { + if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } else if (geyserConfig.isAutoconfiguredRemote() && proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.HYBRID); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java index cb10b717d78..fc21087ae18 100644 --- a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -34,7 +34,7 @@ public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { - if (config.getRemote().getAuthType() != AuthType.HYBRID) { + if (config.getRemote().getAuthType() != AuthType.FLOODGATE) { return geyserDataFolder.resolve(config.getFloodgateKeyFile()); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 54be9f79de5..38d567bde4e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -235,7 +235,7 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { if (platformType == PlatformType.STANDALONE) { logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); - } else if (config.getRemote().getAuthType() == AuthType.HYBRID) { + } else if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } } @@ -293,7 +293,7 @@ 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; - if (config.getRemote().getAuthType() == AuthType.HYBRID) { + if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); 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 3897fd61b7e..a4508f0b533 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -667,7 +667,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 (this.remoteServer.authType() == AuthType.HYBRID) { + if (this.remoteServer.authType() == AuthType.FLOODGATE) { validUsername = username.replace(' ', '_'); } @@ -824,7 +824,7 @@ public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.Authentic * After getting whatever credentials needed, we attempt to join the Java server. */ private void connectDownstream() { - boolean floodgate = this.remoteServer.authType() == AuthType.HYBRID; + boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE; // Start ticking tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); @@ -920,7 +920,7 @@ public void connected(ConnectedEvent event) { UUID uuid = protocol.getProfile().getId(); if (uuid == null) { // Set what our UUID *probably* is going to be - if (remoteServer.authType() == AuthType.HYBRID) { + 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)); @@ -950,7 +950,7 @@ public void disconnected(DisconnectedEvent event) { String disconnectMessage; Throwable cause = event.getCause(); if (cause instanceof UnexpectedEncryptionException) { - if (remoteServer.authType() != AuthType.HYBRID) { + if (remoteServer.authType() != AuthType.FLOODGATE) { // Server expects online mode disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale()); // Explain that they may be looking for Floodgate. 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 f084c1d8446..094cc397968 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 @@ -49,7 +49,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator Date: Sun, 24 Apr 2022 18:02:02 +0100 Subject: [PATCH 139/358] Re-arrange groups and artefacts when publishing (#2899) * Re-arrange groups and artefacts when publishing * Publish core jar --- api/geyser/build.gradle.kts | 7 +++++++ build.gradle.kts | 1 - core/build.gradle.kts | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts index f9f8e66a89b..cfd87cbee93 100644 --- a/api/geyser/build.gradle.kts +++ b/api/geyser/build.gradle.kts @@ -1,3 +1,10 @@ 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/build.gradle.kts b/build.gradle.kts index b10d7c35755..bf6c1df4d8f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,7 +36,6 @@ subprojects { val relativePath = projectDir.relativeTo(rootProject.projectDir).path if (relativePath.contains("api")) { - group = rootProject.group as String + ".api" plugins.apply("geyser.api-conventions") } else { group = rootProject.group as String + ".geyser" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 03260fc8f20..c22df20bab6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -77,6 +77,13 @@ dependencies { annotationProcessor(projects.ap) } +publishing { + publications.named("mavenJava") { + artifact(tasks["jar"]) + artifact(tasks["sourcesJar"]) + } +} + configure { val indra = the() From 9af6f948fe3b4cb6f2b0515d777161070718efa9 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 12:07:50 -0500 Subject: [PATCH 140/358] Publish extensions branch --- Jenkinsfile | 1 + .../geysermc/geyser/extension/GeyserExtensionLoader.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 24acee78a5c..bcb8ffa24d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,6 +23,7 @@ pipeline { stage ('Deploy') { when { branch "master" + branch "feature/extensions" } steps { diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 7092d1fc2dd..4c3c4bf119d 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -61,7 +61,7 @@ public class GeyserExtensionLoader extends ExtensionLoader { private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); - public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtensionException, InvalidDescriptionException { + public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException, InvalidDescriptionException { if (path == null) { throw new InvalidExtensionException("Path is null"); } @@ -70,7 +70,6 @@ public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtension throw new InvalidExtensionException(new NoSuchFileException(path.toString()) + " does not exist"); } - GeyserExtensionDescription description = this.extensionDescription(path); Path parentFile = path.getParent(); Path dataFolder = parentFile.resolve(description.name()); if (Files.exists(dataFolder) && !Files.isDirectory(dataFolder)) { @@ -136,6 +135,7 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { return; } + // noinspection ConstantConditions if (!GeyserImpl.VERSION.contains(".")) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); return; @@ -165,7 +165,7 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { } try { - ExtensionDescription description = this.extensionDescription(path); + GeyserExtensionDescription description = this.extensionDescription(path); if (description == null) { return; } @@ -201,7 +201,7 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { } extensions.put(name, path); - loadedExtensions.put(name, this.loadExtension(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); } From 4dbfd5f66e50495979f60bd7069ea872486dc17e Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 12:11:50 -0500 Subject: [PATCH 141/358] Fix conditional for deploying --- Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bcb8ffa24d9..df09fe47ed5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,8 +22,10 @@ pipeline { stage ('Deploy') { when { - branch "master" - branch "feature/extensions" + anyOf { + branch "master" + branch "feature/extensions" + } } steps { From df808b72c5c7a11873998a8092ffa94c4bbcc9a7 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 12:44:46 -0500 Subject: [PATCH 142/358] Fix gradle deployer id --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index df09fe47ed5..c49498f77f5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -30,7 +30,7 @@ pipeline { steps { rtGradleDeployer( - id: "maven-deployer", + id: "GRADLE_DEPLOYER", serverId: "opencollab-artifactory", releaseRepo: "maven-releases", snapshotRepo: "maven-snapshots" From 38891100819bf248c3f493ed42e058fa6eb434d7 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:03:21 -0500 Subject: [PATCH 143/358] Try this? --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c49498f77f5..3e485bc10e1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,10 +40,9 @@ pipeline { serverId: "opencollab-artifactory" ) rtGradleRun( - usesPlugin: true, + usesPlugin: false, tool: 'Gradle 7', rootDir: "", - useWrapper: true, buildFile: 'build.gradle.kts', tasks: 'build artifactoryPublish', deployerId: "GRADLE_DEPLOYER", From 463fc83f785eb7dfe5ba70c54fd75fa037363439 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:16:39 -0500 Subject: [PATCH 144/358] Publishing changes --- Jenkinsfile | 10 ++++-- .../kotlin/geyser.api-conventions.gradle.kts | 2 +- .../geyser.platform-conventions.gradle.kts | 2 +- .../geyser.publish-conventions.gradle.kts | 31 +++++++++++++++++++ .../geyser.shadow-conventions.gradle.kts | 23 -------------- build.gradle.kts | 2 +- core/build.gradle.kts | 1 + 7 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts diff --git a/Jenkinsfile b/Jenkinsfile index 3e485bc10e1..f92778318e9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,12 @@ pipeline { stage ('Build') { steps { sh 'git submodule update --init --recursive' - sh './gradlew clean build' + rtGradleRun( + usesPlugin: true, + tool: 'Gradle 7', + buildFile: 'build.gradle.kts', + tasks: 'clean build', + ) } post { success { @@ -40,9 +45,10 @@ pipeline { serverId: "opencollab-artifactory" ) rtGradleRun( - usesPlugin: false, + usesPlugin: true, tool: 'Gradle 7', rootDir: "", + useWrapper: true, buildFile: 'build.gradle.kts', tasks: 'build artifactoryPublish', deployerId: "GRADLE_DEPLOYER", diff --git a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts index 0781436c44e..31117f2da0c 100644 --- a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("geyser.shadow-conventions") + id("geyser.api-conventions") } tasks { diff --git a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts index 07968f231bf..81d22490655 100644 --- a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts @@ -1,4 +1,4 @@ plugins { application - id("geyser.shadow-conventions") + 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..6336e78877e --- /dev/null +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("geyser.shadow-conventions") + id("com.jfrog.artifactory") + id("maven-publish") +} + +publishing { + publications.create("mavenJava") { + groupId = project.group as String + artifactId = "Geyser-" + project.name + version = project.version as String + + artifact(tasks["shadowJar"]) + artifact(tasks["sourcesJar"]) + } +} + +artifactory { + publish { + repository { + setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases") + setMavenCompatible(true) + } + defaults { + publishConfigs("archives") + setPublishArtifacts(true) + setPublishPom(true) + setPublishIvy(false) + } + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts index ddd42789765..395beb10482 100644 --- a/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts @@ -3,7 +3,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id("geyser.base-conventions") id("com.github.johnrengelman.shadow") - id("com.jfrog.artifactory") } tasks { @@ -30,26 +29,4 @@ tasks { named("build") { dependsOn(shadowJar) } -} - -publishing { - publications.named("mavenJava") { - artifact(tasks["shadowJar"]) - artifact(tasks["sourcesJar"]) - } -} - -artifactory { - publish { - repository { - setRepoKey("maven-snapshots") - setMavenCompatible(true) - } - defaults { - publishConfigs("archives") - setPublishArtifacts(true) - setPublishPom(true) - setPublishIvy(false) - } - } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bf6c1df4d8f..aa35c93451f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,7 @@ subprojects { group = rootProject.group as String + ".geyser" when (this) { in platforms -> plugins.apply("geyser.platform-conventions") - api -> plugins.apply("geyser.shadow-conventions") + api -> plugins.apply("geyser.publish-conventions") else -> plugins.apply("geyser.base-conventions") } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index c22df20bab6..5ab2c393287 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,6 +4,7 @@ import net.kyori.blossom.BlossomExtension plugins { id("net.kyori.blossom") id("net.kyori.indra.git") + id("geyser.shadow-conventions") } dependencies { From cee945a66e5183463b367008c0adceda3efb01fb Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:19:44 -0500 Subject: [PATCH 145/358] Oops --- build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts index 31117f2da0c..7c8f9a3d7b7 100644 --- a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("geyser.api-conventions") + id("geyser.publish-conventions") } tasks { From 78f36500ca5737a0d0a64d3c9e811680b09a3fbc Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:22:58 -0500 Subject: [PATCH 146/358] Add missing isSnapshot --- build-logic/src/main/kotlin/extensions.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index ae8d578df88..1f9793ee458 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -28,6 +28,9 @@ import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency 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") From 3091d27e0e5f47518c57fc75e4f615e3dc61476f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:35:49 -0500 Subject: [PATCH 147/358] i think i fixed it? --- api/geyser/build.gradle.kts | 4 ++++ .../src/main/kotlin/geyser.base-conventions.gradle.kts | 8 -------- build.gradle.kts | 2 -- core/build.gradle.kts | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts index cfd87cbee93..dcde8533749 100644 --- a/api/geyser/build.gradle.kts +++ b/api/geyser/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("geyser.api-conventions") +} + dependencies { api(projects.api) } diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 211455d31c7..2ea5d88a479 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -30,12 +30,4 @@ java { targetCompatibility = JavaVersion.VERSION_16 withSourcesJar() -} - -publishing { - publications.create("mavenJava") { - groupId = project.group as String - artifactId = project.name - version = project.version as String - } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index aa35c93451f..7371978d391 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.accessors.dm.ApProjectDependency - plugins { `java-library` id("geyser.build-logic") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5ab2c393287..ae21e596124 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,7 +4,7 @@ import net.kyori.blossom.BlossomExtension plugins { id("net.kyori.blossom") id("net.kyori.indra.git") - id("geyser.shadow-conventions") + id("geyser.publish-conventions") } dependencies { From b565ecce0a9c2a2c7e5c2068bc815cf3adef5600 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:49:03 -0500 Subject: [PATCH 148/358] Try this perhaps --- build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7371978d391..c10ea91a3c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,8 +38,7 @@ subprojects { } else { group = rootProject.group as String + ".geyser" when (this) { - in platforms -> plugins.apply("geyser.platform-conventions") - api -> plugins.apply("geyser.publish-conventions") + in platforms -> plugins.apply("geyser.publish-conventions") else -> plugins.apply("geyser.base-conventions") } } From 14ac7d5dbf0950ecb9b7acf07af7e166735c1f02 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:52:02 -0500 Subject: [PATCH 149/358] Fix this --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index c10ea91a3c1..a51beafdc8d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ subprojects { group = rootProject.group as String + ".geyser" when (this) { in platforms -> plugins.apply("geyser.publish-conventions") + api -> plugins.apply("geyser.api-conventions") else -> plugins.apply("geyser.base-conventions") } } From cbd25c01118cc9eb57c8ce64a099faa33f5007cf Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 13:53:50 -0500 Subject: [PATCH 150/358] sigh --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a51beafdc8d..7371978d391 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,8 +38,8 @@ subprojects { } else { group = rootProject.group as String + ".geyser" when (this) { - in platforms -> plugins.apply("geyser.publish-conventions") - api -> plugins.apply("geyser.api-conventions") + in platforms -> plugins.apply("geyser.platform-conventions") + api -> plugins.apply("geyser.publish-conventions") else -> plugins.apply("geyser.base-conventions") } } From 766f28ec604cb849340d4610aa16571912d4b1de Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 14:07:39 -0500 Subject: [PATCH 151/358] Publishing seems to work locally now(?) --- .../src/main/kotlin/geyser.publish-conventions.gradle.kts | 2 +- core/build.gradle.kts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts index 6336e78877e..f1cb8b13905 100644 --- a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -7,7 +7,7 @@ plugins { publishing { publications.create("mavenJava") { groupId = project.group as String - artifactId = "Geyser-" + project.name + artifactId = project.name version = project.version as String artifact(tasks["shadowJar"]) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ae21e596124..fbdd3a116d6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -78,13 +78,6 @@ dependencies { annotationProcessor(projects.ap) } -publishing { - publications.named("mavenJava") { - artifact(tasks["jar"]) - artifact(tasks["sourcesJar"]) - } -} - configure { val indra = the() From 735697b553c11985713e8e2e3f2525435d8dcf26 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 14:17:15 -0500 Subject: [PATCH 152/358] Allow loading extensions in dev environment --- .gitignore | 1 + .../geysermc/geyser/extension/GeyserExtensionLoader.java | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 9fc9258d778..a0e5b64e4d2 100644 --- a/.gitignore +++ b/.gitignore @@ -240,6 +240,7 @@ nbdist/ ### Geyser ### run/ +extensions/ config.yml logs/ key.pem diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 4c3c4bf119d..ff38ea52aa7 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -129,12 +129,6 @@ void setClass(String name, final Class clazz) { @Override protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { - // noinspection ConstantConditions - if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); - return; - } - // noinspection ConstantConditions if (!GeyserImpl.VERSION.contains(".")) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); From 7c8bf330a96c910292a8a8bea3a9bd1902cfe61f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 14:37:44 -0500 Subject: [PATCH 153/358] Fix loading multiple extensions (Closes #2826) --- .../extension/GeyserExtensionClassLoader.java | 14 ++++++-------- .../geyser/extension/GeyserExtensionLoader.java | 13 ++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 28b9930b48e..71311b305db 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -39,12 +39,13 @@ public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; private final Map> classes = new HashMap<>(); - private final Extension extension; - public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, Path path) throws InvalidExtensionException, MalformedURLException { + 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 { @@ -57,10 +58,10 @@ public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader pare try { extensionClass = jarClass.asSubclass(Extension.class); } catch (ClassCastException ex) { - throw new InvalidExtensionException("Main class " + description.main() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); + throw new InvalidExtensionException("Main class " + description.main() + " should implement Extension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); } - this.extension = extensionClass.getConstructor().newInstance(); + return extensionClass.getConstructor().newInstance(); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { throw new InvalidExtensionException("No public constructor", ex); } catch (InstantiationException ex) { @@ -77,6 +78,7 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo 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) { @@ -94,8 +96,4 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo } return result; } - - public Extension extension() { - return this.extension; - } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index ff38ea52aa7..ac8da2679d4 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -78,13 +78,15 @@ public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescript final GeyserExtensionClassLoader loader; try { - loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, path); + loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), path); } catch (Throwable e) { throw new InvalidExtensionException(e); } this.classLoaders.put(description.name(), loader); - return this.setup(loader.extension(), description, dataFolder, new GeyserExtensionEventBus(GeyserImpl.getInstance().eventBus(), loader.extension())); + + 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) { @@ -110,10 +112,11 @@ public Class classByName(final String name) throws ClassNotFoundException{ Class clazz = this.classes.get(name); try { for (GeyserExtensionClassLoader loader : this.classLoaders.values()) { - try { - clazz = loader.findClass(name,false); - } catch(NullPointerException ignored) { + if (clazz != null) { + continue; } + + clazz = loader.findClass(name, false); } return clazz; } catch (NullPointerException s) { From 7f0e5b409f419ea378f081668eb381b9d07f1fbd Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 24 Apr 2022 14:53:47 -0500 Subject: [PATCH 154/358] Optimizations to extension loading --- core/build.gradle.kts | 1 + .../extension/GeyserExtensionClassLoader.java | 4 +- .../extension/GeyserExtensionLoader.java | 119 +++++++++--------- 3 files changed, 65 insertions(+), 59 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index fbdd3a116d6..abf703110f0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation("com.nukkitx.fastutil", "fastutil-int-boolean-maps", Versions.fastutilVersion) implementation("com.nukkitx.fastutil", "fastutil-object-int-maps", Versions.fastutilVersion) implementation("com.nukkitx.fastutil", "fastutil-object-object-maps", Versions.fastutilVersion) + implementation("com.nukkitx.fastutil", "fastutil-object-reference-maps", Versions.fastutilVersion) // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 71311b305db..19e2765e8ee 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.extension; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; @@ -38,7 +40,7 @@ public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; - private final Map> classes = new HashMap<>(); + private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path) throws MalformedURLException { super(new URL[] { path.toUri().toURL() }, parent); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index ac8da2679d4..676231c5b76 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.extension; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; @@ -51,13 +53,15 @@ import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; +import java.util.stream.Stream; @RequiredArgsConstructor public class GeyserExtensionLoader extends ExtensionLoader { private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); - private static final Pattern API_VERSION_PATTERN = Pattern.compile("^[0-9]+\\.[0-9]+\\.[0-9]+$"); + private static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); + private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; - private final Map> classes = new HashMap<>(); + private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); @@ -105,29 +109,27 @@ public GeyserExtensionDescription extensionDescription(Path path) throws Invalid } public Pattern[] extensionFilters() { - return new Pattern[] { Pattern.compile("^.+\\.jar$") }; + return EXTENSION_FILTERS; } public Class classByName(final String name) throws ClassNotFoundException{ Class clazz = this.classes.get(name); - try { - for (GeyserExtensionClassLoader loader : this.classLoaders.values()) { - if (clazz != null) { - continue; - } + if (clazz != null) { + return clazz; + } - clazz = loader.findClass(name, false); + for (GeyserExtensionClassLoader loader : this.classLoaders.values()) { + clazz = loader.findClass(name, false); + if (clazz != null) { + break; } - return clazz; - } catch (NullPointerException s) { - return null; } + + return clazz; } void setClass(String name, final Class clazz) { - if (!this.classes.containsKey(name)) { - this.classes.put(name,clazz); - } + this.classes.putIfAbsent(name, clazz); } @Override @@ -149,60 +151,61 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { Map loadedExtensions = new LinkedHashMap<>(); Pattern[] extensionFilters = this.extensionFilters(); - - Files.walk(EXTENSION_DIRECTORY).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); - if (description == null) { + try (Stream entries = Files.walk(EXTENSION_DIRECTORY)) { + entries.forEach(path -> { + if (Files.isDirectory(path)) { return; } - 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; + for (Pattern filter : extensionFilters) { + if (!filter.matcher(path.getFileName().toString()).matches()) { + return; + } } try { - // Check the format: majorVersion.minorVersion.patch - if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { - throw new IllegalArgumentException(); + GeyserExtensionDescription description = this.extensionDescription(path); + if (description == null) { + return; } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); - return; - } - String[] versionArray = description.apiVersion().split("\\."); + 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 (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - return; - } + try { + // Check the format: majorVersion.minorVersion.patch + if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { + throw new IllegalArgumentException(); + } + } catch (NullPointerException | IllegalArgumentException e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); + return; + } - // If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - return; - } + String[] versionArray = description.apiVersion().split("\\."); - 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); - } - }); + // Completely different API version + if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + // If the extension requires new API features, being backwards compatible + if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + 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); From 59d5a6469ca37e8958427cf25aae832d5a74674c Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 1 May 2022 12:25:24 -0500 Subject: [PATCH 155/358] Add support for modifying identifiers through the AvailableEntityIdentifiersPacket This is the first of many commits that address adding support for custom entities through the Geyser API. --- .../org/geysermc/geyser/api/GeyserApi.java | 8 ++ .../geysermc/geyser/api/command/Command.java | 55 ++++++++++- .../geyser/api/command/CommandManager.java | 9 -- .../geyser/api/entity/EntityIdentifier.java | 97 +++++++++++++++++++ .../api/event/entity/DefineEntitiesEvent.java | 46 +++++++++ .../geyser/api/provider/BuilderProvider.java | 47 +++++++++ .../geyser/api/provider/Provider.java | 29 ++++++ .../geyser/api/provider/ProviderManager.java | 41 ++++++++ .../java/org/geysermc/geyser/GeyserImpl.java | 7 ++ .../geyser/command/GeyserCommandManager.java | 5 - .../geyser/entity/GeyserEntityIdentifier.java | 85 ++++++++++++++++ .../geyser/registry/ProviderRegistries.java | 42 ++++++++ .../loader/ProviderRegistryLoader.java | 45 +++++++++ .../registry/provider/AbstractProvider.java | 39 ++++++++ .../provider/GeyserBuilderProvider.java | 64 ++++++++++++ .../provider/GeyserProviderManager.java | 36 +++++++ .../registry/provider/ProviderSupplier.java | 31 ++++++ .../geyser/session/GeyserSession.java | 34 ++++++- 18 files changed, 702 insertions(+), 18 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/ProviderSupplier.java 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 4366e9ed691..96c23d26775 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 @@ -35,6 +35,7 @@ import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.api.provider.ProviderManager; import java.util.List; import java.util.UUID; @@ -99,6 +100,13 @@ public interface GeyserApi extends GeyserApiBase { */ CommandManager commandManager(); + /** + * Gets the {@link ProviderManager}. + * + * @return the provider manager + */ + ProviderManager providerManager(); + /** * Gets the {@link EventBus} for handling * Geyser events. diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java index 2c48473c6dd..55e894dd7ec 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -98,27 +98,80 @@ default boolean isBedrockOnly() { } static Command.Builder builder(Class sourceType) { - return GeyserApi.api().commandManager().provideBuilder(sourceType); + return GeyserApi.api().providerManager().builderProvider().provideBuilder(Builder.class, sourceType); } interface Builder { + /** + * Sets the command name. + * + * @param name the command name + * @return the builder + */ Builder name(String name); + /** + * Sets the command description. + * + * @param description the command description + * @return the builder + */ Builder description(String description); + /** + * Sets the permission node. + * + * @param permission the permission node + * @return the builder + */ Builder permission(String permission); + /** + * Sets the aliases. + * + * @param aliases the aliases + * @return the builder + */ Builder aliases(List aliases); + /** + * 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(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(CommandExecutor executor); + /** + * Builds the command. + * + * @return the command + */ Command build(); } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java index 864ba71a4ca..9f29651ba8d 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java @@ -34,15 +34,6 @@ */ public abstract class CommandManager { - /** - * Provides a {@link Command.Builder}. - * - * @param sourceType the command source type - * @param the type - * @return a command builder - */ - protected abstract Command.Builder provideBuilder(Class sourceType); - /** * Registers the given {@link Command}. * diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java b/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java new file mode 100644 index 00000000000..1c82d3a6306 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java @@ -0,0 +1,97 @@ +/* + * 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.entity; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; + +/** + * Represents the data sent over to a client regarding + * an entity's identifier. + */ +public interface EntityIdentifier { + + /** + * Gets whether this entity has a spawn egg or not. + * + * @return whether this entity has a spawn egg or not + */ + boolean hasSpawnEgg(); + + /** + * Gets the entity's identifier that is sent to the client. + * + * @return the entity's identifier that is sent to the client. + */ + @NonNull + String identifier(); + + /** + * Gets whether the entity is summonable or not. + * + * @return whether the entity is summonable or not + */ + boolean isSummonable(); + + @NonNull + static Builder builder() { + return GeyserApi.api().providerManager().builderProvider().provideBuilder(Builder.class); + } + + interface Builder { + + /** + * Sets whether the entity has a spawn egg or not. + * + * @param spawnEgg whether the entity has a spawn egg or not + * @return the builder + */ + Builder spawnEgg(boolean spawnEgg); + + /** + * Sets the entity's identifier that is sent to the client. + * + * @param identifier the entity's identifier that is sent to the client + * @return the builder + */ + Builder identifier(String identifier); + + /** + * Sets whether the entity is summonable or not. + * + * @param summonable whether the entity is summonable or not + * @return the builder + */ + Builder summonable(boolean summonable); + + /** + * Builds the entity identifier. + * + * @return the entity identifier + */ + EntityIdentifier build(); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java new file mode 100644 index 00000000000..9bdae3dba95 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.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.api.event.entity; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.entity.EntityIdentifier; +import org.geysermc.geyser.api.event.Event; + +import java.util.List; + +/** + * Called when Geyser sends a list of available entities to the + * Bedrock client. This will typically contain all the available + * entities within vanilla, but can be modified to include any custom + * entity defined through a resource pack. + * + * @param connection the {@link GeyserConnection} that is receiving the entities + * @param identifiers a mutable list of all the {@link EntityIdentifier}s + * sent to the client + */ +public record DefineEntitiesEvent(@NonNull GeyserConnection connection, @NonNull List identifiers) implements Event { +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java new file mode 100644 index 00000000000..b05a36f8812 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.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.provider; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Allows for obtaining instances of a builder that are + * used for constructing various data. + */ +public interface BuilderProvider extends Provider { + + /** + * Provides a builder for the specified builder type. + * + * @param builderClass the builder class + * @param the resulting type + * @param the builder type + * @return the builder instance + */ + @NonNull + B provideBuilder(@NonNull Class builderClass, @Nullable Object... args); +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java new file mode 100644 index 00000000000..4463efeed97 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java @@ -0,0 +1,29 @@ +/* + * 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.provider; + +public interface Provider { +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java new file mode 100644 index 00000000000..48ec99a3f35 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.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.provider; + +/** + * Holds a record of every {@link Provider} available + * that allows for accessing various information throughout + * the API. + */ +public interface ProviderManager { + + /** + * Returns the {@link BuilderProvider}. + * + * @return the builder provider + */ + BuilderProvider builderProvider(); +} diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 38d567bde4e..188393f4cbc 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -70,6 +70,7 @@ import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.provider.GeyserProviderManager; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; @@ -145,6 +146,7 @@ public class GeyserImpl implements GeyserApi { private final EventBus eventBus; private final GeyserExtensionManager extensionManager; + private final GeyserProviderManager providerManager = new GeyserProviderManager(); private final RemoteServer remoteServer; private final BedrockListener bedrockListener; @@ -576,6 +578,11 @@ public GeyserCommandManager commandManager() { return this.bootstrap.getGeyserCommandManager(); } + @Override + public GeyserProviderManager providerManager() { + return this.providerManager; + } + @Override public EventBus eventBus() { return this.eventBus; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 0214a44fd19..c242106d88b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -148,11 +148,6 @@ public void runCommand(GeyserCommandSource sender, String command) { */ public abstract String description(String command); - @Override - protected Command.Builder provideBuilder(Class sourceType) { - return new CommandBuilder<>(sourceType); - } - @RequiredArgsConstructor public static class CommandBuilder implements Command.Builder { private final Class sourceType; diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java new file mode 100644 index 00000000000..9a4a5e5d521 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.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.entity; + +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.entity.EntityIdentifier; + +import java.util.concurrent.atomic.AtomicInteger; + +public record GeyserEntityIdentifier(NbtMap nbt) implements EntityIdentifier { + private static final AtomicInteger RUNTIME_ID_ALLOCATORS = new AtomicInteger(100000); + + @Override + public boolean hasSpawnEgg() { + return this.nbt.getBoolean("hasspawnegg"); + } + + @NonNull + @Override + public String identifier() { + return this.nbt.getString("id"); + } + + @Override + public boolean isSummonable() { + return this.nbt.getBoolean("summonable"); + } + + public static class EntityIdentifierBuilder implements EntityIdentifier.Builder { + private final NbtMapBuilder nbt = NbtMap.builder(); + + @Override + public Builder spawnEgg(boolean spawnEgg) { + this.nbt.putBoolean("hasspawnegg", spawnEgg); + return this; + } + + @Override + public Builder identifier(String identifier) { + this.nbt.putString("id", identifier); + return this; + } + + @Override + public Builder summonable(boolean summonable) { + this.nbt.putBoolean("summonable", summonable); + return this; + } + + @Override + public EntityIdentifier build() { + // Vanilla registry information + this.nbt.putString("bid", ""); + this.nbt.putInt("rid", RUNTIME_ID_ALLOCATORS.getAndIncrement()); + this.nbt.putBoolean("experimental", false); + + return new GeyserEntityIdentifier(this.nbt.build()); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java new file mode 100644 index 00000000000..46b44c8da36 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.api.provider.Provider; +import org.geysermc.geyser.registry.loader.ProviderRegistryLoader; +import org.geysermc.geyser.registry.provider.GeyserBuilderProvider; +import org.geysermc.geyser.registry.provider.ProviderSupplier; + +/** + * Holds registries for the available {@link Provider}s + */ +public class ProviderRegistries { + + /** + * A registry containing all the providers for builders. + */ + public static final SimpleMappedRegistry, ProviderSupplier> BUILDERS = SimpleMappedRegistry.create(GeyserBuilderProvider.INSTANCE, ProviderRegistryLoader::new); +} 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..290f2991bb2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.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.registry.loader; + +import org.geysermc.geyser.registry.provider.AbstractProvider; +import org.geysermc.geyser.registry.provider.ProviderSupplier; + +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Registers the provider data from the provider. + */ +public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>> { + + @Override + public Map, ProviderSupplier> load(AbstractProvider input) { + Map, ProviderSupplier> providers = new IdentityHashMap<>(); + input.registerProviders(providers); + return providers; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java new file mode 100644 index 00000000000..8c5201c23e1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java @@ -0,0 +1,39 @@ +/* + * 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; + +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.api.provider.Provider; +import org.geysermc.geyser.registry.SimpleMappedRegistry; + +import java.util.Map; + +@RequiredArgsConstructor +public abstract class AbstractProvider implements Provider { + public abstract void registerProviders(Map, ProviderSupplier> providers); + + public abstract SimpleMappedRegistry, ProviderSupplier> providerRegistry(); +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java new file mode 100644 index 00000000000..57789ae5549 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java @@ -0,0 +1,64 @@ +/* + * 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; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.entity.EntityIdentifier; +import org.geysermc.geyser.api.provider.BuilderProvider; +import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.entity.GeyserEntityIdentifier; +import org.geysermc.geyser.registry.ProviderRegistries; +import org.geysermc.geyser.registry.SimpleMappedRegistry; + +import java.util.Map; + +public class GeyserBuilderProvider extends AbstractProvider implements BuilderProvider { + public static GeyserBuilderProvider INSTANCE = new GeyserBuilderProvider(); + + private GeyserBuilderProvider() { + } + + @SuppressWarnings("unchecked") + @Override + public void registerProviders(Map, ProviderSupplier> providers) { + providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) args[0])); + providers.put(EntityIdentifier.Builder.class, args -> new GeyserEntityIdentifier.EntityIdentifierBuilder()); + } + + @Override + public SimpleMappedRegistry, ProviderSupplier> providerRegistry() { + return ProviderRegistries.BUILDERS; + } + + @SuppressWarnings("unchecked") + @Override + public @NonNull B provideBuilder(@NonNull Class builderClass, @Nullable Object... args) { + return (B) this.providerRegistry().get(builderClass).create(args); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java new file mode 100644 index 00000000000..208981d7aa0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.geysermc.geyser.api.provider.ProviderManager; + +public class GeyserProviderManager implements ProviderManager { + + @Override + public GeyserBuilderProvider builderProvider() { + return GeyserBuilderProvider.INSTANCE; + } +} 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/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index a4508f0b533..8e8bbd8fc20 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -61,6 +61,9 @@ import com.github.steveice10.packetlib.tcp.TcpSession; import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.*; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; @@ -88,9 +91,12 @@ import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.entity.EntityIdentifier; +import org.geysermc.geyser.api.event.entity.DefineEntitiesEvent; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; +import org.geysermc.geyser.entity.GeyserEntityIdentifier; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -129,6 +135,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Getter public class GeyserSession implements GeyserConnection, GeyserCommandSource { @@ -595,9 +602,7 @@ public void connect() { biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get()); upstream.sendPacket(biomeDefinitionListPacket); - AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get()); - upstream.sendPacket(entityPacket); + this.sendAvailableEntityIdentifiers(); CreativeContentPacket creativePacket = new CreativeContentPacket(); creativePacket.setContents(this.itemMappings.getCreativeItems()); @@ -627,6 +632,29 @@ public void connect() { upstream.sendPacket(gamerulePacket); } + public void sendAvailableEntityIdentifiers() { + NbtMap nbt = Registries.BEDROCK_ENTITY_IDENTIFIERS.get(); + List idlist = nbt.getList("idlist", NbtType.COMPOUND); + List identifiers = new ArrayList<>(idlist.size()); + for (NbtMap identifier : idlist) { + identifiers.add(new GeyserEntityIdentifier(identifier)); + } + + DefineEntitiesEvent event = new DefineEntitiesEvent(this, identifiers); + this.geyser.eventBus().fire(event); + + NbtMapBuilder builder = nbt.toBuilder(); + builder.putList("idlist", NbtType.COMPOUND, event.identifiers() + .stream() + .map(identifer -> ((GeyserEntityIdentifier) identifer).nbt()) + .collect(Collectors.toList()) + ); + + AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); + entityPacket.setIdentifiers(builder.build()); + upstream.sendPacket(entityPacket); + } + public void authenticate(String username) { authenticate(username, ""); } From fbaa6c1f5f5bd0752f633c722e8c316dd98e62d7 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 7 May 2022 10:11:21 -0500 Subject: [PATCH 156/358] Remove entity work from main extension branch for now --- .../geyser/api/entity/EntityIdentifier.java | 97 ------------------- .../api/event/entity/DefineEntitiesEvent.java | 46 --------- .../geyser/entity/GeyserEntityIdentifier.java | 85 ---------------- .../provider/GeyserBuilderProvider.java | 3 - .../geyser/session/GeyserSession.java | 30 +----- 5 files changed, 3 insertions(+), 258 deletions(-) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java delete mode 100644 core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java b/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java deleted file mode 100644 index 1c82d3a6306..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/entity/EntityIdentifier.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.entity; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.GeyserApi; - -/** - * Represents the data sent over to a client regarding - * an entity's identifier. - */ -public interface EntityIdentifier { - - /** - * Gets whether this entity has a spawn egg or not. - * - * @return whether this entity has a spawn egg or not - */ - boolean hasSpawnEgg(); - - /** - * Gets the entity's identifier that is sent to the client. - * - * @return the entity's identifier that is sent to the client. - */ - @NonNull - String identifier(); - - /** - * Gets whether the entity is summonable or not. - * - * @return whether the entity is summonable or not - */ - boolean isSummonable(); - - @NonNull - static Builder builder() { - return GeyserApi.api().providerManager().builderProvider().provideBuilder(Builder.class); - } - - interface Builder { - - /** - * Sets whether the entity has a spawn egg or not. - * - * @param spawnEgg whether the entity has a spawn egg or not - * @return the builder - */ - Builder spawnEgg(boolean spawnEgg); - - /** - * Sets the entity's identifier that is sent to the client. - * - * @param identifier the entity's identifier that is sent to the client - * @return the builder - */ - Builder identifier(String identifier); - - /** - * Sets whether the entity is summonable or not. - * - * @param summonable whether the entity is summonable or not - * @return the builder - */ - Builder summonable(boolean summonable); - - /** - * Builds the entity identifier. - * - * @return the entity identifier - */ - EntityIdentifier build(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java deleted file mode 100644 index 9bdae3dba95..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/entity/DefineEntitiesEvent.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.entity; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.entity.EntityIdentifier; -import org.geysermc.geyser.api.event.Event; - -import java.util.List; - -/** - * Called when Geyser sends a list of available entities to the - * Bedrock client. This will typically contain all the available - * entities within vanilla, but can be modified to include any custom - * entity defined through a resource pack. - * - * @param connection the {@link GeyserConnection} that is receiving the entities - * @param identifiers a mutable list of all the {@link EntityIdentifier}s - * sent to the client - */ -public record DefineEntitiesEvent(@NonNull GeyserConnection connection, @NonNull List identifiers) implements Event { -} diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java deleted file mode 100644 index 9a4a5e5d521..00000000000 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityIdentifier.java +++ /dev/null @@ -1,85 +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.entity; - -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.entity.EntityIdentifier; - -import java.util.concurrent.atomic.AtomicInteger; - -public record GeyserEntityIdentifier(NbtMap nbt) implements EntityIdentifier { - private static final AtomicInteger RUNTIME_ID_ALLOCATORS = new AtomicInteger(100000); - - @Override - public boolean hasSpawnEgg() { - return this.nbt.getBoolean("hasspawnegg"); - } - - @NonNull - @Override - public String identifier() { - return this.nbt.getString("id"); - } - - @Override - public boolean isSummonable() { - return this.nbt.getBoolean("summonable"); - } - - public static class EntityIdentifierBuilder implements EntityIdentifier.Builder { - private final NbtMapBuilder nbt = NbtMap.builder(); - - @Override - public Builder spawnEgg(boolean spawnEgg) { - this.nbt.putBoolean("hasspawnegg", spawnEgg); - return this; - } - - @Override - public Builder identifier(String identifier) { - this.nbt.putString("id", identifier); - return this; - } - - @Override - public Builder summonable(boolean summonable) { - this.nbt.putBoolean("summonable", summonable); - return this; - } - - @Override - public EntityIdentifier build() { - // Vanilla registry information - this.nbt.putString("bid", ""); - this.nbt.putInt("rid", RUNTIME_ID_ALLOCATORS.getAndIncrement()); - this.nbt.putBoolean("experimental", false); - - return new GeyserEntityIdentifier(this.nbt.build()); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java index 57789ae5549..e144fcd4f7f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java @@ -29,10 +29,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.api.entity.EntityIdentifier; import org.geysermc.geyser.api.provider.BuilderProvider; import org.geysermc.geyser.command.GeyserCommandManager; -import org.geysermc.geyser.entity.GeyserEntityIdentifier; import org.geysermc.geyser.registry.ProviderRegistries; import org.geysermc.geyser.registry.SimpleMappedRegistry; @@ -48,7 +46,6 @@ private GeyserBuilderProvider() { @Override public void registerProviders(Map, ProviderSupplier> providers) { providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) args[0])); - providers.put(EntityIdentifier.Builder.class, args -> new GeyserEntityIdentifier.EntityIdentifierBuilder()); } @Override 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 8e8bbd8fc20..bf0360e1db6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -91,12 +91,9 @@ import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.entity.EntityIdentifier; -import org.geysermc.geyser.api.event.entity.DefineEntitiesEvent; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; -import org.geysermc.geyser.entity.GeyserEntityIdentifier; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -602,7 +599,9 @@ public void connect() { biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get()); upstream.sendPacket(biomeDefinitionListPacket); - this.sendAvailableEntityIdentifiers(); + AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); + entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get()); + upstream.sendPacket(entityPacket); CreativeContentPacket creativePacket = new CreativeContentPacket(); creativePacket.setContents(this.itemMappings.getCreativeItems()); @@ -632,29 +631,6 @@ public void connect() { upstream.sendPacket(gamerulePacket); } - public void sendAvailableEntityIdentifiers() { - NbtMap nbt = Registries.BEDROCK_ENTITY_IDENTIFIERS.get(); - List idlist = nbt.getList("idlist", NbtType.COMPOUND); - List identifiers = new ArrayList<>(idlist.size()); - for (NbtMap identifier : idlist) { - identifiers.add(new GeyserEntityIdentifier(identifier)); - } - - DefineEntitiesEvent event = new DefineEntitiesEvent(this, identifiers); - this.geyser.eventBus().fire(event); - - NbtMapBuilder builder = nbt.toBuilder(); - builder.putList("idlist", NbtType.COMPOUND, event.identifiers() - .stream() - .map(identifer -> ((GeyserEntityIdentifier) identifer).nbt()) - .collect(Collectors.toList()) - ); - - AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setIdentifiers(builder.build()); - upstream.sendPacket(entityPacket); - } - public void authenticate(String username) { authenticate(username, ""); } From 4b4529c590aedcb6c5577aac2de673048229c072 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 16 May 2022 13:26:53 -0400 Subject: [PATCH 157/358] Prevent NPE with new language overrides --- .../java/org/geysermc/platform/fabric/GeyserFabricMod.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 3c175896f35..f6a657bba25 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -97,13 +97,15 @@ public void onInitialize() { @Override public void onEnable() { - GeyserLocale.init(this); - 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); From 2a89eab100976db1affecf78dba2d5e53e3edd10 Mon Sep 17 00:00:00 2001 From: David Choo Date: Tue, 7 Jun 2022 20:10:12 -0400 Subject: [PATCH 158/358] Update for Java 1.19 and Geyser 2.0.4-SNAPSHOT (#53) * Update for 1.19 and 2.0.4-SNAPSHOT * Use mod version in Geyser dependency --- bootstrap/fabric/build.gradle | 8 ++++---- bootstrap/fabric/gradle.properties | 12 ++++++------ .../platform/fabric/command/FabricCommandSender.java | 4 ++-- .../fabric/mixin/client/IntegratedServerMixin.java | 4 ++-- .../mixin/server/MinecraftDedicatedServerMixin.java | 10 +++------- bootstrap/fabric/src/main/resources/fabric.mod.json | 2 +- 6 files changed, 18 insertions(+), 22 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 1127600098c..e120ba1b219 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.10-SNAPSHOT' + id 'fabric-loom' version '0.12-SNAPSHOT' id 'maven-publish' id 'com.github.johnrengelman.shadow' version '7.0.0' id 'java' @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation 'org.geysermc:core:2.0.3-SNAPSHOT' - shadow('org.geysermc:core:2.0.3-SNAPSHOT') { + implementation "org.geysermc:core:${project.mod_version}" + shadow("org.geysermc:core:${project.mod_version}") { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' @@ -93,7 +93,7 @@ jar { remapJar { dependsOn(shadowJar) - input.set shadowJar.archiveFile.get() + input = tasks.shadowJar.archiveFile } // configure the maven publication diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 1d9b12e678d..8e6528e258a 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -1,14 +1,14 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18.2 -yarn_mappings=1.18.2+build.1 -loader_version=0.12.8 +minecraft_version=1.19 +yarn_mappings=1.19+build.1 +loader_version=0.14.6 # Mod Properties -mod_version=2.0.3-SNAPSHOT +mod_version=2.0.4-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.43.1+1.18 +fabric_version=0.55.2+1.19 \ No newline at end of file diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index 1fa9f55ae62..57f877637ec 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -27,7 +27,7 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.text.ChatColor; @@ -49,7 +49,7 @@ public String name() { @Override public void sendMessage(String message) { if (source.getEntity() instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) source.getEntity()).sendMessage(new LiteralText(message), false); + ((ServerPlayerEntity) source.getEntity()).sendMessage(Text.literal(message), false); } else { GeyserImpl.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java index 1fc945f2866..6a6d3e0e611 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -30,7 +30,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.server.MinecraftServer; import net.minecraft.server.integrated.IntegratedServer; -import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; import net.minecraft.world.GameMode; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.platform.fabric.GeyserFabricMod; @@ -58,7 +58,7 @@ private void onOpenToLan(GameMode gameMode, boolean cheatsAllowed, int port, Cal // Ensure player locale has been loaded, in case it's different from Java system language GeyserLocale.loadGeyserLocale(this.client.options.language); // Give indication that Geyser is loaded - this.client.player.sendMessage(new LiteralText(GeyserLocale.getPlayerLocaleString("geyser.core.start", + this.client.player.sendMessage(Text.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start", this.client.options.language, "localhost", String.valueOf(this.lanPort))), false); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java index e30181f003d..a41a083423d 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -25,27 +25,23 @@ package org.geysermc.platform.fabric.mixin.server; -import com.mojang.authlib.GameProfileRepository; -import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.datafixers.DataFixer; import net.minecraft.resource.ResourcePackManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.SaveLoader; import net.minecraft.server.WorldGenerationProgressListenerFactory; import net.minecraft.server.dedicated.MinecraftDedicatedServer; -import net.minecraft.util.UserCache; +import net.minecraft.util.ApiServices; import net.minecraft.world.level.storage.LevelStorage; import org.geysermc.platform.fabric.GeyserServerPortGetter; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import java.net.Proxy; @Mixin(MinecraftDedicatedServer.class) public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { - // Constructor to compile - public MinecraftDedicatedServerMixin(Thread serverThread, LevelStorage.Session session, ResourcePackManager dataPackManager, SaveLoader saveLoader, Proxy proxy, DataFixer dataFixer, @Nullable MinecraftSessionService sessionService, @Nullable GameProfileRepository gameProfileRepo, @Nullable UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { - super(serverThread, session, dataPackManager, saveLoader, proxy, dataFixer, sessionService, gameProfileRepo, userCache, worldGenerationProgressListenerFactory); + public MinecraftDedicatedServerMixin(Thread serverThread, LevelStorage.Session session, ResourcePackManager dataPackManager, SaveLoader saveLoader, Proxy proxy, DataFixer dataFixer, ApiServices apiServices, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { + super(serverThread, session, dataPackManager, saveLoader, proxy, dataFixer, apiServices, worldGenerationProgressListenerFactory); } @Override diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 54478f15005..fd04f8ecf69 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": ">=1.18.2" + "minecraft": ">=1.19" } } From 795bec91d74e4bc6a1b282902ff2d57f16140106 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Sat, 11 Jun 2022 10:03:19 +0200 Subject: [PATCH 159/358] Fixed plugin.yml for spigot --- .../spigot/src/main/resources/plugin.yml | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 013b1d96cd0..e28b8981d87 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -8,41 +8,4 @@ api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser -<<<<<<< HEAD -permissions: - geyser.command.help: - description: Shows help for all registered commands. - default: true - geyser.command.offhand: - description: Puts an items in your offhand. - default: true - geyser.command.advancements: - description: Shows the advancements of the player on the server. - default: true - geyser.command.tooltips: - description: Toggles showing advanced tooltips on your items. - default: true - geyser.command.statistics: - description: Shows the statistics of the player on the server. - default: true - geyser.command.settings: - description: Modify user settings - default: true - geyser.command.list: - description: List all players connected through Geyser. - default: op - geyser.command.dump: - description: Dumps Geyser debug information for bug reports. - default: op - geyser.command.reload: - description: Reloads the Geyser configurations. Kicks all players when used! - default: false - geyser.command.version: - description: Shows the current Geyser version and checks for updates. - default: op - geyser.command.extensions: - description: Shows all the loaded extensions. - default: op -======= ->>>>>>> 2595eae30070fa5d221ad505eef6a9e1606fb1d1 + usage: /geyser \ No newline at end of file From fb03885eb8564d3c82c4875cfab87e6612939644 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:33:50 -0400 Subject: [PATCH 160/358] Consistent file name; remove Netty IO_Uring --- bootstrap/fabric/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index e120ba1b219..f25af4ee8b1 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -32,6 +32,8 @@ dependencies { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' + exclude group: 'it.unimi.dsi.fastutil' + exclude group: 'io.netty.incubator' } } @@ -80,7 +82,6 @@ task sourcesJar(type: Jar, dependsOn: classes) { shadowJar { configurations = [project.configurations.shadow] - relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") 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") @@ -94,6 +95,7 @@ jar { remapJar { dependsOn(shadowJar) input = tasks.shadowJar.archiveFile + archiveName = "Geyser-Fabric.jar" } // configure the maven publication From 3b32b16bc8ea1fa436dd1e891da54fbd45c6b974 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:49:47 -0400 Subject: [PATCH 161/358] Properly exclude Fastutil --- bootstrap/fabric/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index f25af4ee8b1..96f3ee8b12a 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -32,7 +32,7 @@ dependencies { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' - exclude group: 'it.unimi.dsi.fastutil' + exclude group: 'com.nukkitx.fastutil' exclude group: 'io.netty.incubator' } } From 36c49a7256ae9a08aed82f7421c355b2d806a550 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Date: Sat, 2 Jul 2022 18:50:16 +0200 Subject: [PATCH 162/358] Custom item support for extensions (#2822) Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .gitignore | 3 +- .../org/geysermc/geyser/api/GeyserApi.java | 2 +- .../geysermc/geyser/api/event/EventBus.java | 2 +- .../GeyserDefineCustomItemsEvent.java | 85 +++++ .../GeyserLoadResourcePacksEvent.java | 40 ++ .../api/item/custom/CustomItemData.java | 109 ++++++ .../api/item/custom/CustomItemOptions.java | 83 ++++ .../api/item/custom/CustomRenderOffsets.java | 51 +++ .../item/custom/NonVanillaCustomItemData.java | 188 +++++++++ .../geysermc/geyser/api/util/TriState.java | 83 ++++ bootstrap/standalone/build.gradle.kts | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 26 +- .../geyser/entity/type/ItemFrameEntity.java | 7 +- .../extension/GeyserExtensionManager.java | 24 +- .../geyser/item/GeyserCustomItemData.java | 158 ++++++++ .../geyser/item/GeyserCustomItemOptions.java | 69 ++++ .../geyser/item/GeyserCustomMappingData.java | 32 ++ .../item/GeyserNonVanillaCustomItemData.java | 303 +++++++++++++++ .../item/components/ToolBreakSpeedsUtils.java | 174 +++++++++ .../geyser/item/components/ToolTier.java | 66 ++++ .../geyser/item/components/WearableSlot.java | 47 +++ .../InvalidCustomMappingsFileException.java | 40 ++ .../item/mappings/MappingsConfigReader.java | 98 +++++ .../mappings/versions/MappingsReader.java | 93 +++++ .../mappings/versions/MappingsReader_v1.java | 123 ++++++ .../geyser/network/UpstreamPacketHandler.java | 4 +- .../geysermc/geyser/pack/ResourcePack.java | 35 +- .../geysermc/geyser/registry/Registries.java | 5 - .../CustomItemRegistryPopulator.java | 360 ++++++++++++++++++ .../populator/ItemRegistryPopulator.java | 159 +++++++- .../provider/GeyserBuilderProvider.java | 9 + .../registry/type/GeyserMappingItem.java | 2 + .../geyser/registry/type/ItemMapping.java | 9 +- .../geyser/registry/type/ItemMappings.java | 5 +- .../type/NonVanillaItemRegistration.java | 34 ++ .../geyser/session/GeyserSession.java | 6 +- .../inventory/item/CompassTranslator.java | 3 +- .../inventory/item/ItemTranslator.java | 55 ++- .../inventory/item/PotionTranslator.java | 3 +- .../inventory/item/TippedArrowTranslator.java | 3 +- .../inventory/item/nbt/BannerTranslator.java | 3 +- .../JavaMerchantOffersTranslator.java | 3 +- core/src/main/resources/mappings | 2 +- 43 files changed, 2535 insertions(+), 73 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/components/ToolTier.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/mappings/MappingsConfigReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/mappings/versions/MappingsReader_v1.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/type/NonVanillaItemRegistration.java diff --git a/.gitignore b/.gitignore index 0566cb46254..2b7e2972c29 100644 --- a/.gitignore +++ b/.gitignore @@ -249,4 +249,5 @@ locales/ /packs/ /dump.json /saved-refresh-tokens.json -/languages/ \ No newline at end of file +/custom_mappings/ +/languages/ 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 96c23d26775..d6cb3c25a19 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 @@ -116,7 +116,7 @@ public interface GeyserApi extends GeyserApiBase { EventBus eventBus(); /** - * Get's the default {@link RemoteServer} configured + * Gets the default {@link RemoteServer} configured * within the config file that is used by default. * * @return the default remote server used within Geyser diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java index 0352dcc9e7e..b13f123002a 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java @@ -76,7 +76,7 @@ public interface EventBus { void unregisterAll(@NonNull Extension extension); /** - * Fires the given {@link Event}. + * Fires the given {@link Event} and returns the result. * * @param event the event to fire * 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..308b39d2231 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.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.lifecycle; + +import com.google.common.collect.Multimap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.event.Event; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; + +import java.util.*; + +/** + * Called on Geyser's startup when looking for custom items. Custom items must be registered through this event. + * + * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. + */ +public abstract class GeyserDefineCustomItemsEvent implements Event { + private final Multimap customItems; + private final List nonVanillaCustomItems; + + public GeyserDefineCustomItemsEvent(Multimap customItems, List nonVanillaCustomItems) { + this.customItems = customItems; + this.nonVanillaCustomItems = nonVanillaCustomItems; + } + + /** + * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. + * + * @return a multimap of all the already registered custom items + */ + public Map> getExistingCustomItems() { + return Collections.unmodifiableMap(this.customItems.asMap()); + } + + /** + * Gets the list of the already registered non-vanilla custom items. + * + * @return the list of the already registered non-vanilla custom items + */ + public List getExistingNonVanillaCustomItems() { + return Collections.unmodifiableList(this.nonVanillaCustomItems); + } + + /** + * 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); +} \ 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..0f181aedfcd --- /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.geyser.api.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/item/custom/CustomItemData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java new file mode 100644 index 00000000000..7391c068051 --- /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().providerManager().builderProvider().provideBuilder(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..037f2f05e43 --- /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().providerManager().builderProvider().provideBuilder(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..1df94f7ea09 --- /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().providerManager().builderProvider().provideBuilder(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/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/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index 088d5dc8258..d49c7c49047 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -1,7 +1,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer val terminalConsoleVersion = "1.2.0" -val jlineVersion = "3.10.0" +val jlineVersion = "3.21.0" dependencies { api(projects.core) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 9eeae4abb60..8f3156abe2e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -180,22 +180,26 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info(""); logger.info("******************************************"); - /* Initialize translators and registries */ - BlockRegistries.init(); - Registries.init(); - - EntityDefinitions.init(); - ItemTranslator.init(); - MessageTranslator.init(); - MinecraftLocale.init(); + /* Initialize event bus */ + this.eventBus = new GeyserEventBus(); /* Load Extensions */ - this.eventBus = new GeyserEventBus(); this.extensionManager = new GeyserExtensionManager(); this.extensionManager.init(); + this.extensionManager.enableExtensions(); this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); + /* Initialize registries */ + Registries.init(); + BlockRegistries.init(); + + /* Initialize translators */ + EntityDefinitions.init(); + ItemTranslator.init(); + MessageTranslator.init(); + MinecraftLocale.init(); + start(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -256,8 +260,6 @@ private void start() { ResourcePack.loadPacks(); - this.extensionManager.enableExtensions(); - if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { @@ -580,6 +582,7 @@ public void shutdown() { @Override public void reload() { shutdown(); + this.extensionManager.enableExtensions(); bootstrap.onEnable(); } @@ -615,7 +618,6 @@ public EventBus eventBus() { return this.eventBus; } - @Override public RemoteServer defaultRemoteServer() { return this.remoteServer; } 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/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 79cf1febcf1..7d80c2cf60a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.extension; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -32,7 +33,6 @@ 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.registry.Registries; import org.geysermc.geyser.text.GeyserLocale; import java.util.Collection; @@ -44,13 +44,15 @@ public class GeyserExtensionManager extends ExtensionManager { private static final Key BASE_EXTENSION_LOADER_KEY = Key.key("geysermc", "base"); + private final Map extensionLoaderTypes = new Object2ObjectOpenHashMap<>(); + private final Map extensions = new LinkedHashMap<>(); private final Map extensionsLoaders = new LinkedHashMap<>(); public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - Registries.EXTENSION_LOADERS.register(BASE_EXTENSION_LOADER_KEY, new GeyserExtensionLoader()); + extensionLoaderTypes.put(BASE_EXTENSION_LOADER_KEY, new GeyserExtensionLoader()); for (ExtensionLoader loader : this.extensionLoaders().values()) { this.loadAllExtensions(loader); } @@ -98,6 +100,12 @@ public void enableExtension(Extension extension) { } } + 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); @@ -107,12 +115,6 @@ private void disableExtension(@NonNull Extension extension) { } } - public void enableExtensions() { - for (Extension extension : this.extensions()) { - this.enable(extension); - } - } - public void disableExtensions() { for (Extension extension : this.extensions()) { this.disable(extension); @@ -133,18 +135,18 @@ public Collection extensions() { @Nullable @Override public ExtensionLoader extensionLoader(@NonNull String identifier) { - return Registries.EXTENSION_LOADERS.get(Key.key(identifier)); + return this.extensionLoaderTypes.get(Key.key(identifier)); } @Override public void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader) { - Registries.EXTENSION_LOADERS.register(Key.key(identifier), extensionLoader); + this.extensionLoaderTypes.put(Key.key(identifier), extensionLoader); } @NonNull @Override public Map extensionLoaders() { - return Registries.EXTENSION_LOADERS.get().entrySet().stream().collect(Collectors.toMap(key -> key.getKey().asString(), Map.Entry::getValue)); + return this.extensionLoaderTypes.entrySet().stream().collect(Collectors.toMap(key -> key.getKey().asString(), Map.Entry::getValue)); } @Override 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/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java b/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java new file mode 100644 index 00000000000..a4479f871b3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.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.item.components; + +import com.nukkitx.nbt.NbtMap; + +import java.util.Locale; + +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(); + } + + 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/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 6030b6ebfd6..33eca67a970 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.pack.ResourcePack; @@ -38,7 +39,6 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.MathUtils; @@ -160,7 +160,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)); } 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 08d6b57384b..bef5c741896 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -26,12 +26,16 @@ package org.geysermc.geyser.pack; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; import java.io.File; -import java.util.HashMap; -import java.util.Map; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -59,16 +63,33 @@ public class ResourcePack { * 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(); 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 9c370bc1cfb..f1dd054f5b9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -113,11 +113,6 @@ public final class Registries { */ public static final SimpleMappedRegistry> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(() -> new EnumMap<>(EntityType.class))); - /** - * A map containing all the extension loaders. - */ - public static final SimpleMappedRegistry EXTENSION_LOADERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); - /** * A map containing all Java entity identifiers and their respective Geyser definitions */ 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..64543272e9a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -0,0 +1,360 @@ +/* + * 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 it.unimi.dsi.fastutil.objects.Object2IntMaps; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.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.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"); + } + 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(Object2IntMaps.emptyMap()) + .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); + } + + 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); + if (maxDamage > 0) { + 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 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 f3d936b2ef8..22669fd7943 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,14 +37,22 @@ 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.*; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; 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.event.lifecycle.GeyserDefineCustomItemsEvent; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.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.*; @@ -78,6 +88,58 @@ public static void populate() { throw new AssertionError("Unable to load Java runtime item IDs", e); } + boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + + Multimap customItems = MultimapBuilder.hashKeys().hashSetValues().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 GeyserDefineCustomItemsEvent(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(); @@ -99,11 +161,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<>(); @@ -203,18 +274,20 @@ 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:trader_llama_spawn_egg", "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; @@ -226,7 +299,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 @@ -380,12 +453,46 @@ 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 + Object2IntMap customItemOptions; + Collection customItemsToLoad = customItems.get(javaIdentifier); + if (customItemsAllowed && !customItemsToLoad.isEmpty()) { + customItemOptions = new Object2IntOpenHashMap<>(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.put(customItem.customItemOptions(), customProtocolId); + + customIdMappings.put(customMapping.integerId(), customMapping.stringId()); + } + } else { + customItemOptions = Object2IntMaps.emptyMap(); + } + mappingBuilder.customItemOptions(customItemOptions); + ItemMapping mapping = mappingBuilder.build(); if (javaIdentifier.contains("boat")) { @@ -436,12 +543,12 @@ public static void populate() { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) + .customItemOptions(Object2IntMaps.emptyMap()) .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)); @@ -456,7 +563,7 @@ public static void populate() { .build()); creativeItems.add(ItemData.builder() - .netId(netId) + .netId(netId++) .id(furnaceMinecartId) .count(1).build()); @@ -492,7 +599,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() @@ -506,8 +642,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/provider/GeyserBuilderProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java index e144fcd4f7f..af289bcda02 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java +++ b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java @@ -29,8 +29,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandSource; +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.api.provider.BuilderProvider; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.item.GeyserCustomItemData; +import org.geysermc.geyser.item.GeyserCustomItemOptions; +import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; import org.geysermc.geyser.registry.ProviderRegistries; import org.geysermc.geyser.registry.SimpleMappedRegistry; @@ -46,6 +52,9 @@ private GeyserBuilderProvider() { @Override public void registerProviders(Map, ProviderSupplier> providers) { providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) 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()); } @Override 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..6c65f1c3476 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,6 +42,8 @@ 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; 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 332ab016772..12ba7d2085b 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,9 +25,12 @@ package org.geysermc.geyser.registry.type; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; @@ -39,7 +42,7 @@ public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, 0, null, false); + 64, null, null, null, Object2IntMaps.emptyMap(), 0, null, false); String javaIdentifier; String bedrockIdentifier; @@ -59,6 +62,8 @@ public class ItemMapping { String translationString; + Object2IntMap customItemOptions; + int maxDamage; Set repairMaterials; @@ -91,4 +96,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 3072568f392..c4e967dffde 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/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 49261500be2..99e29dd2161 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -594,9 +594,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); } @@ -1465,7 +1465,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? 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 b5778e681ed..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 @@ -79,8 +79,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.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/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index b9bfdd57652..0a2ab57dfb4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -34,9 +34,12 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; @@ -122,7 +125,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); } } @@ -170,6 +173,8 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } + translateCustomItem(nbt, builder, bedrockItem); + if (nbt != null) { // Translate the canDestroy and canPlaceOn Java NBT ListTag canDestroy = nbt.get("CanDestroy"); @@ -292,6 +297,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; } @@ -416,7 +425,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); } @@ -524,6 +533,48 @@ public static CompoundTag translateDisplayProperties(GeyserSession session, Comp return tag; } + /** + * Translates the custom model data of an item + */ + private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) { + int bedrockId = getCustomItem(nbt, mapping); + if (bedrockId != -1) { + builder.id(bedrockId); + } + } + + private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { + if (nbt == null) { + return -1; + } + Object2IntMap customMappings = mapping.getCustomItemOptions(); + if (customMappings.isEmpty()) { + return -1; + } + int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; + TriState unbreakable = TriState.fromBoolean(nbt.get("Unbreakable") instanceof ByteTag unbreakableTag && unbreakableTag.getValue() == 1); + int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; + for (Object2IntMap.Entry mappingTypes : customMappings.object2IntEntrySet()) { + CustomItemOptions options = mappingTypes.getKey(); + + TriState unbreakableOption = options.unbreakable(); + if (unbreakableOption == unbreakable) { // Implementation note: if the option is NOT_SET then this comparison will always be false because of how the item unbreaking TriState is created + return mappingTypes.getIntValue(); + } + + OptionalInt customModelDataOption = options.customModelData(); + if (customModelDataOption.isPresent() && customModelDataOption.getAsInt() == customModelData) { + return mappingTypes.getIntValue(); + } + + OptionalInt damagePredicate = options.damagePredicate(); + if (damagePredicate.isPresent() && damagePredicate.getAsInt() == damage) { + return mappingTypes.getIntValue(); + } + } + return -1; + } + /** * Checks if an {@link ItemStack} is equal to another item stack * 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 f12355ce62e..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 @@ -74,8 +74,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.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 5dc525e56b9..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 @@ -81,8 +81,7 @@ public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMap @Override public List getAppliedItems() { - return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.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/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index 0d1538eb86c..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 @@ -76,8 +76,7 @@ private static CompoundTag getPatternTag(String pattern, int color) { } public BannerTranslator() { - appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.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/protocol/java/inventory/JavaMerchantOffersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index 1c9ded0c17d..58b59d82e6a 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 @@ -168,11 +168,12 @@ private static NbtMap getItemTag(GeyserSession session, ItemStack stack, int spe private static NbtMap getItemTag(GeyserSession session, ItemStack stack, ItemMapping mapping, int count) { ItemData itemData = ItemTranslator.translateToBedrock(session, stack); + String customIdentifier = session.getItemMappings().getCustomIdMappings().get(itemData.getId()); NbtMapBuilder builder = NbtMap.builder(); builder.putByte("Count", (byte) count); builder.putShort("Damage", (short) itemData.getDamage()); - builder.putString("Name", mapping.getBedrockIdentifier()); + builder.putString("Name", customIdentifier != null ? customIdentifier : mapping.getBedrockIdentifier()); if (itemData.getTag() != null) { NbtMap tag = itemData.getTag().toBuilder().build(); builder.put("tag", tag); diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 99a1f8070e8..919908f4825 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 99a1f8070e844d059454dacbb6e8b203521eed23 +Subproject commit 919908f4825e9fa1bb7b5a2f5e09218f0a3f72f3 From b5eb27693fdeefad782fa9b73b12e8958515f965 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 2 Jul 2022 12:30:23 -0500 Subject: [PATCH 163/358] Use an immutable view in GeyserDefineCommandsEvent Methods to properly register/unregister commands are provided in the command manager --- .../geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java | 3 +-- .../java/org/geysermc/geyser/command/GeyserCommandManager.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) 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 index 1a2c7b4d418..e506c0ca053 100644 --- 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 @@ -36,8 +36,7 @@ * Called when commands are defined within Geyser. * * @param commandManager the command manager - * @param commands a mutable list of the currently - * registered default commands + * @param commands an immutable view of the default commands */ public record GeyserDefineCommandsEvent(@NonNull CommandManager commandManager, @NonNull Map commands) implements Event { } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index fea57efa149..c6b9cbdd24d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -70,7 +70,7 @@ public void init() { register(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } - this.geyser.eventBus().fire(new GeyserDefineCommandsEvent(this, this.commands)); + this.geyser.eventBus().fire(new GeyserDefineCommandsEvent(this, this.commands())); } @Override From f9fd7cb831fc909ed4291a7ab9b652987952481f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 2 Jul 2022 12:42:31 -0500 Subject: [PATCH 164/358] Fix Geyser not working in IDE --- .../java/org/geysermc/api/GeyserApiBase.java | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 12 ++++++++-- .../command/defaults/VersionCommand.java | 2 +- .../extension/GeyserExtensionLoader.java | 22 ++++++++----------- 4 files changed, 21 insertions(+), 17 deletions(-) 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 bf38f58b99d..e5105b1be43 100644 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java @@ -78,7 +78,7 @@ public interface GeyserApiBase { * @return the major API version. Bumped whenever a significant breaking change or feature addition is added. */ default int majorApiVersion() { - return 0; + return 1; } /** diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 8f3156abe2e..4c3cf6fca45 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -116,7 +116,7 @@ public class GeyserImpl implements GeyserApi { 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 int BUILD_NUMBER = Integer.parseInt("${buildNumber}"); + public static final String BUILD_NUMBER = "${buildNumber}"; public static final String BRANCH = "${branch}"; /** @@ -318,7 +318,7 @@ private void start() { pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); - this.newsHandler = new NewsHandler(BRANCH, BUILD_NUMBER); + this.newsHandler = new NewsHandler(BRANCH, this.buildNumber()); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether @@ -632,6 +632,14 @@ public int maxPlayers() { return this.getConfig().getMaxPlayers(); } + public int buildNumber() { + if (!this.isProductionEnvironment()) { + return 0; + } + + return Integer.parseInt(BUILD_NUMBER); + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); 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 9006ca959fe..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 @@ -79,7 +79,7 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] 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 = GeyserImpl.BUILD_NUMBER; + int buildNum = this.geyser.buildNumber(); if (latestBuildNum == buildNum) { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); } else { diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 137a2005ed8..55f018ddb2a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; 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.*; @@ -125,14 +126,6 @@ void setClass(String name, final Class clazz) { @Override protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { - // noinspection ConstantConditions - if (!GeyserImpl.VERSION.contains(".")) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); - return; - } - - String[] apiVersion = GeyserImpl.VERSION.split("\\."); - try { if (Files.notExists(EXTENSION_DIRECTORY)) { Files.createDirectory(EXTENSION_DIRECTORY); @@ -166,27 +159,30 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { return; } + int majorVersion = Geyser.api().majorApiVersion(); + int minorVersion = Geyser.api().minorApiVersion(); + try { // Check the format: majorVersion.minorVersion.patch if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { throw new IllegalArgumentException(); } } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, majorVersion + "." + minorVersion)); return; } String[] versionArray = description.apiVersion().split("\\."); // Completely different API version - if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + if (Integer.parseInt(versionArray[0]) != majorVersion) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); return; } // If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + if (Integer.parseInt(versionArray[1]) > minorVersion) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); return; } From 897c4dcfecda8a416028c8eda2a1f8997ef93a49 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 9 Jul 2022 18:39:02 -0400 Subject: [PATCH 165/358] Changes --- .../org/geysermc/geyser/api/GeyserApi.java | 38 ++------- .../geysermc/geyser/api/command/Command.java | 2 +- .../downstream/ServerDefineCommandsEvent.java | 6 +- .../api/item/custom/CustomItemData.java | 2 +- .../api/item/custom/CustomItemOptions.java | 2 +- .../item/custom/NonVanillaCustomItemData.java | 2 +- .../geysermc/geyser/api/network/AuthType.java | 18 ++-- .../geyser/api/network/BedrockListener.java | 4 +- .../geyser/api/provider/BuilderProvider.java | 47 ----------- .../geyser/api/provider/Provider.java | 29 ------- .../geyser/api/provider/ProviderManager.java | 41 --------- .../standalone/GeyserStandaloneBootstrap.java | 2 +- .../geysermc/geyser/FloodgateKeyLoader.java | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 84 ++++--------------- .../configuration/GeyserConfiguration.java | 32 +++---- .../GeyserJacksonConfiguration.java | 49 ++++++++++- .../geyser/network/BedrockListenerImpl.java | 31 ------- .../network/ConnectorServerEventHandler.java | 6 +- .../geyser/network/QueryPacketHandler.java | 6 +- .../geyser/network/RemoteServerImpl.java | 32 ------- .../geyser/network/UpstreamPacketHandler.java | 4 +- .../ping/GeyserLegacyPingPassthrough.java | 4 +- .../geyser/registry/ProviderRegistries.java | 42 ---------- .../geysermc/geyser/registry/Registries.java | 13 +-- .../loader/ProviderRegistryLoader.java | 23 +++-- .../registry/provider/AbstractProvider.java | 39 --------- .../provider/GeyserBuilderProvider.java | 70 ---------------- .../provider/GeyserProviderManager.java | 36 -------- .../geyser/session/GeyserSession.java | 4 +- .../org/geysermc/geyser/skin/SkinManager.java | 2 +- 30 files changed, 144 insertions(+), 528 deletions(-) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java delete mode 100644 core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java delete mode 100644 core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java 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 d6cb3c25a19..16bfe7070a0 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 @@ -35,7 +35,6 @@ import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; -import org.geysermc.geyser.api.provider.ProviderManager; import java.util.List; import java.util.UUID; @@ -44,24 +43,6 @@ * Represents the API used in Geyser. */ public interface GeyserApi extends GeyserApiBase { - /** - * Shuts down the current Geyser instance. - */ - void shutdown(); - - /** - * Reloads the current Geyser instance. - */ - void reload(); - - /** - * 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. - * - * @return if we are in a production environment - */ - boolean isProductionEnvironment(); - /** * {@inheritDoc} */ @@ -101,11 +82,15 @@ public interface GeyserApi extends GeyserApiBase { CommandManager commandManager(); /** - * Gets the {@link ProviderManager}. + * Provides an implementation for the specified API type. * - * @return the provider manager + * @param apiClass the builder class + * @param the implementation type + * @param the API type + * @return the builder instance */ - ProviderManager providerManager(); + @NonNull + R provider(@NonNull Class apiClass, @Nullable Object... args); /** * Gets the {@link EventBus} for handling @@ -131,15 +116,6 @@ public interface GeyserApi extends GeyserApiBase { */ BedrockListener bedrockListener(); - /** - * Gets the maximum number of players that - * can join this Geyser instance. - * - * @return the maximum number of players that - * can join this Geyser instance - */ - int maxPlayers(); - /** * Gets the current {@link GeyserApiBase} instance. * diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java index f9ae68ab324..0ad2966692d 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -105,7 +105,7 @@ default boolean isBedrockOnly() { } static Command.Builder builder(Class sourceType) { - return GeyserApi.api().providerManager().builderProvider().provideBuilder(Builder.class, sourceType); + return GeyserApi.api().provider(Builder.class, sourceType); } interface Builder { diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java index ba7254c9433..06412eb4c0f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -45,9 +45,10 @@ public ServerDefineCommandsEvent(@NonNull GeyserConnection connection, @NonNull } /** - * A mutable collection of the commands sent over. + * A collection of commands sent from the server. Any element in this collection can be removed, but no element can + * be added. * - * @return a mutable collection of the commands sent over + * @return a collection of the commands sent over */ @NonNull public Set commands() { @@ -65,7 +66,6 @@ public void setCancelled(boolean cancelled) { } public interface CommandInfo { - /** * Gets the name of the command. * 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 index 7391c068051..17763fb77d8 100644 --- 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 @@ -83,7 +83,7 @@ public interface CustomItemData { @Nullable CustomRenderOffsets renderOffsets(); static CustomItemData.Builder builder() { - return GeyserApi.api().providerManager().builderProvider().provideBuilder(CustomItemData.Builder.class); + return GeyserApi.api().provider(CustomItemData.Builder.class); } interface Builder { 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 index 037f2f05e43..ec26a6e375c 100644 --- 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 @@ -68,7 +68,7 @@ default boolean hasCustomItemOptions() { } static CustomItemOptions.Builder builder() { - return GeyserApi.api().providerManager().builderProvider().provideBuilder(CustomItemOptions.Builder.class); + return GeyserApi.api().provider(CustomItemOptions.Builder.class); } interface Builder { 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 index 1df94f7ea09..d2cef637a9c 100644 --- 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 @@ -137,7 +137,7 @@ public interface NonVanillaCustomItemData extends CustomItemData { boolean isTool(); static NonVanillaCustomItemData.Builder builder() { - return GeyserApi.api().providerManager().builderProvider().provideBuilder(NonVanillaCustomItemData.Builder.class); + return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class); } interface Builder extends CustomItemData.Builder { diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java index 5e1c2539db0..3176f338422 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java @@ -25,16 +25,22 @@ package org.geysermc.geyser.api.network; +import java.util.Locale; + +/** + * 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 @@ -44,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; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java index 648f83e47a7..58a597eb63e 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java @@ -48,7 +48,7 @@ public interface BedrockListener { int port(); /** - * Gets the primary MOTD shown to Bedrock players. + * 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. * @@ -57,7 +57,7 @@ public interface BedrockListener { String primaryMotd(); /** - * Gets the secondary MOTD shown to Bedrock players. + * 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. * diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java deleted file mode 100644 index b05a36f8812..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/BuilderProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.provider; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * Allows for obtaining instances of a builder that are - * used for constructing various data. - */ -public interface BuilderProvider extends Provider { - - /** - * Provides a builder for the specified builder type. - * - * @param builderClass the builder class - * @param the resulting type - * @param the builder type - * @return the builder instance - */ - @NonNull - B provideBuilder(@NonNull Class builderClass, @Nullable Object... args); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java deleted file mode 100644 index 4463efeed97..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/Provider.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.provider; - -public interface Provider { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java deleted file mode 100644 index 48ec99a3f35..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/provider/ProviderManager.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.provider; - -/** - * Holds a record of every {@link Provider} available - * that allows for accessing various information throughout - * the API. - */ -public interface ProviderManager { - - /** - * Returns the {@link BuilderProvider}. - * - * @return the builder provider - */ - BuilderProvider builderProvider(); -} 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 a89b18d1e29..44194d75ce1 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 @@ -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"); } diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java index aaf45ce35cf..8b51228c845 100644 --- a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -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/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 4c3cf6fca45..146cb985f69 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -64,14 +64,10 @@ import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.level.WorldManager; -import org.geysermc.geyser.network.BedrockListenerImpl; import org.geysermc.geyser.network.ConnectorServerEventHandler; -import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.network.RemoteServerImpl; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.provider.GeyserProviderManager; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; @@ -90,7 +86,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; @@ -148,10 +143,6 @@ public class GeyserImpl implements GeyserApi { private final EventBus eventBus; private final GeyserExtensionManager extensionManager; - private final GeyserProviderManager providerManager = new GeyserProviderManager(); - - private final RemoteServer remoteServer; - private final BedrockListener bedrockListener; private Metrics metrics; @@ -215,22 +206,6 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { } } - this.remoteServer = new RemoteServerImpl( - config.getRemote().getAddress(), - config.getRemote().getPort(), - GameProtocol.getJavaProtocolVersion(), - GameProtocol.getJavaMinecraftVersion(), - config.getRemote().getAuthType() - ); - - this.bedrockListener = new BedrockListenerImpl( - config.getBedrock().getAddress(), - config.getBedrock().getPort(), - config.getBedrock().getMotd1(), - config.getBedrock().getMotd2(), - config.getBedrock().getServerName() - ); - double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; if (isGui) { @@ -243,7 +218,7 @@ 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); } } @@ -260,7 +235,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()); @@ -272,7 +247,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")) { int remotePort; @@ -298,24 +273,6 @@ 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.isProductionEnvironment()) { - 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, this.buildNumber()); @@ -335,7 +292,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 @@ -358,11 +315,11 @@ 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"); @@ -372,7 +329,7 @@ private void start() { }).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()); @@ -390,7 +347,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)); @@ -474,7 +431,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: " + @@ -552,7 +509,6 @@ private void start() { return null; } - @Override public void shutdown() { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown")); shuttingDown = true; @@ -579,7 +535,6 @@ public void shutdown() { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } - @Override public void reload() { shutdown(); this.extensionManager.enableExtensions(); @@ -592,10 +547,10 @@ public void reload() { * * @return true if the version number is not 'DEV'. */ - @Override 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); + return !("git-local/dev-0000000".equals(GeyserImpl.GIT_VERSION) || "${gitVersion}".equals(GeyserImpl.GIT_VERSION)); } @Override @@ -609,8 +564,8 @@ public GeyserCommandManager commandManager() { } @Override - public GeyserProviderManager providerManager() { - return this.providerManager; + public @NonNull R provider(@NonNull Class apiClass, @Nullable Object... args) { + return (R) Registries.PROVIDERS.get(apiClass).create(args); } @Override @@ -619,17 +574,12 @@ public EventBus eventBus() { } public RemoteServer defaultRemoteServer() { - return this.remoteServer; + return getConfig().getRemote(); } @Override public BedrockListener bedrockListener() { - return this.bedrockListener; - } - - @Override - public int maxPlayers() { - return this.getConfig().getMaxPlayers(); + return getConfig().getBedrock(); } public int buildNumber() { 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 d74d804dff0..7439b22f291 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.api.network.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; @@ -109,20 +111,10 @@ public interface GeyserConfiguration { int getPendingAuthenticationTimeout(); - interface IBedrockConfiguration { - - String getAddress(); - - int getPort(); + interface IBedrockConfiguration extends BedrockListener { boolean isCloneRemotePort(); - String getMotd1(); - - String getMotd2(); - - String getServerName(); - int getCompressionLevel(); boolean isEnableProxyProtocol(); @@ -135,23 +127,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 77b35151847..b80e60e496c 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -153,24 +153,50 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("pending-authentication-timeout") private int pendingAuthenticationTimeout = 120; - @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(isIp = true) private String address = "0.0.0.0"; + @Override + public String address() { + return address; + } + @Setter private int port = 19132; + @Override + public int port() { + return port; + } + + @Getter @JsonProperty("clone-remote-port") private boolean cloneRemotePort = false; private String motd1 = "GeyserMC"; + + @Override + public String primaryMotd() { + return motd1; + } + 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 +204,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 +230,45 @@ public List getWhitelistedIPsMatchers() { } } - @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(isIp = true) private String address = "auto"; + @Override + public String address() { + return address; + } + @JsonDeserialize(using = PortDeserializer.class) @Setter private int port = 25565; + @Override + public int port() { + return port; + } + @Setter @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; } diff --git a/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java b/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java deleted file mode 100644 index e617be36a56..00000000000 --- a/core/src/main/java/org/geysermc/geyser/network/BedrockListenerImpl.java +++ /dev/null @@ -1,31 +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.network; - -import org.geysermc.geyser.api.network.BedrockListener; - -public record BedrockListenerImpl(String address, int port, String primaryMotd, String secondaryMotd, String serverName) implements BedrockListener { -} 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 e231cd08a53..3bfbf118d76 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -108,7 +108,7 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { pong.setNintendoLimited(false); 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().getPort()); + pong.setIpv4Port(config.getBedrock().port()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); @@ -118,8 +118,8 @@ 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) { 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 f11851c1b5a..4a78d0bdb74 100644 --- a/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java @@ -151,7 +151,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 @@ -180,8 +180,8 @@ private byte[] getGameData() { 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/RemoteServerImpl.java b/core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java deleted file mode 100644 index a0d919c3a03..00000000000 --- a/core/src/main/java/org/geysermc/geyser/network/RemoteServerImpl.java +++ /dev/null @@ -1,32 +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.network; - -import org.geysermc.geyser.api.network.AuthType; -import org.geysermc.geyser.api.network.RemoteServer; - -public record RemoteServerImpl(String address, int port, int protocolVersion, String minecraftVersion, AuthType authType) implements RemoteServer { -} 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 33eca67a970..5fc254363ac 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -31,7 +31,6 @@ import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.pack.ResourcePack; @@ -39,6 +38,7 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.MathUtils; @@ -119,7 +119,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 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 c3a242501db..74501c8ae00 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -76,8 +76,8 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { 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(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java deleted file mode 100644 index 46b44c8da36..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/ProviderRegistries.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry; - -import org.geysermc.geyser.api.provider.Provider; -import org.geysermc.geyser.registry.loader.ProviderRegistryLoader; -import org.geysermc.geyser.registry.provider.GeyserBuilderProvider; -import org.geysermc.geyser.registry.provider.ProviderSupplier; - -/** - * Holds registries for the available {@link Provider}s - */ -public class ProviderRegistries { - - /** - * A registry containing all the providers for builders. - */ - public static final SimpleMappedRegistry, ProviderSupplier> BUILDERS = SimpleMappedRegistry.create(GeyserBuilderProvider.INSTANCE, ProviderRegistryLoader::new); -} 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 f1dd054f5b9..4b361ba4faa 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -40,8 +40,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.kyori.adventure.key.Key; -import org.geysermc.geyser.api.extension.ExtensionLoader; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; @@ -49,6 +47,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; @@ -59,10 +58,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. @@ -140,6 +136,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. */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 290f2991bb2..449f6574e6e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -25,21 +25,32 @@ package org.geysermc.geyser.registry.loader; -import org.geysermc.geyser.registry.provider.AbstractProvider; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandSource; +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.item.GeyserCustomItemData; +import org.geysermc.geyser.item.GeyserCustomItemOptions; +import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; import org.geysermc.geyser.registry.provider.ProviderSupplier; -import java.util.IdentityHashMap; import java.util.Map; /** * Registers the provider data from the provider. */ -public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>> { +public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>, Map, ProviderSupplier>> { + @SuppressWarnings("unchecked") @Override - public Map, ProviderSupplier> load(AbstractProvider input) { - Map, ProviderSupplier> providers = new IdentityHashMap<>(); - input.registerProviders(providers); + public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { + providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) 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()); + return providers; } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java deleted file mode 100644 index 8c5201c23e1..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/provider/AbstractProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry.provider; - -import lombok.RequiredArgsConstructor; -import org.geysermc.geyser.api.provider.Provider; -import org.geysermc.geyser.registry.SimpleMappedRegistry; - -import java.util.Map; - -@RequiredArgsConstructor -public abstract class AbstractProvider implements Provider { - public abstract void registerProviders(Map, ProviderSupplier> providers); - - public abstract SimpleMappedRegistry, ProviderSupplier> providerRegistry(); -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java deleted file mode 100644 index af289bcda02..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserBuilderProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry.provider; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandSource; -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.api.provider.BuilderProvider; -import org.geysermc.geyser.command.GeyserCommandManager; -import org.geysermc.geyser.item.GeyserCustomItemData; -import org.geysermc.geyser.item.GeyserCustomItemOptions; -import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; -import org.geysermc.geyser.registry.ProviderRegistries; -import org.geysermc.geyser.registry.SimpleMappedRegistry; - -import java.util.Map; - -public class GeyserBuilderProvider extends AbstractProvider implements BuilderProvider { - public static GeyserBuilderProvider INSTANCE = new GeyserBuilderProvider(); - - private GeyserBuilderProvider() { - } - - @SuppressWarnings("unchecked") - @Override - public void registerProviders(Map, ProviderSupplier> providers) { - providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) 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()); - } - - @Override - public SimpleMappedRegistry, ProviderSupplier> providerRegistry() { - return ProviderRegistries.BUILDERS; - } - - @SuppressWarnings("unchecked") - @Override - public @NonNull B provideBuilder(@NonNull Class builderClass, @Nullable Object... args) { - return (B) this.providerRegistry().get(builderClass).create(args); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java b/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java deleted file mode 100644 index 208981d7aa0..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/provider/GeyserProviderManager.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry.provider; - -import org.geysermc.geyser.api.provider.ProviderManager; - -public class GeyserProviderManager implements ProviderManager { - - @Override - public GeyserBuilderProvider builderProvider() { - return GeyserBuilderProvider.INSTANCE; - } -} 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 99e29dd2161..53d22edbe8b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -581,7 +581,7 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio disconnect(message); }); - this.remoteServer = geyser.getRemoteServer(); + this.remoteServer = geyser.defaultRemoteServer(); } /** @@ -1457,7 +1457,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); 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 38d57dc0127..992835a2bbd 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -286,7 +286,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) { From 83ba6b5ab53f869bd3e15739a33f61db8c5dc65f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 10 Jul 2022 20:58:48 -0400 Subject: [PATCH 166/358] Extensions have their own command --- .../downstream/ServerDefineCommandsEvent.java | 4 +- .../lifecycle/GeyserDefineCommandsEvent.java | 42 ----------- .../bungeecord/GeyserBungeePlugin.java | 7 +- .../command/GeyserBungeeCommandExecutor.java | 7 +- .../platform/spigot/GeyserSpigotInjector.java | 4 +- .../platform/spigot/GeyserSpigotPlugin.java | 13 ++-- .../command/GeyserSpigotCommandExecutor.java | 5 +- .../spigot/src/main/resources/plugin.yml | 5 +- .../platform/sponge/GeyserSpongePlugin.java | 7 +- .../command/GeyserSpongeCommandExecutor.java | 10 +-- .../velocity/GeyserVelocityPlugin.java | 7 +- .../GeyserVelocityCommandExecutor.java | 6 +- .../geyser/command/GeyserCommandExecutor.java | 6 +- .../geyser/command/GeyserCommandManager.java | 75 +++++++++++-------- .../geyser/command/defaults/HelpCommand.java | 12 ++- .../BedrockCommandRequestTranslator.java | 7 +- 16 files changed, 101 insertions(+), 116 deletions(-) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java index 06412eb4c0f..2ab1b9611aa 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -33,7 +33,9 @@ import java.util.Set; /** - * Called when the downstream server defines the commands available on the server. + * 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; 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 deleted file mode 100644 index e506c0ca053..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandManager; -import org.geysermc.geyser.api.event.Event; - -import java.util.Map; - -/** - * Called when commands are defined within Geyser. - * - * @param commandManager the command manager - * @param commands an immutable view of the default commands - */ -public record GeyserDefineCommandsEvent(@NonNull CommandManager commandManager, @NonNull Map commands) implements Event { -} 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 d98349eac37..7b937ac6ba3 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 @@ -86,7 +86,7 @@ public void onEnable() { 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("")) { @@ -109,7 +109,7 @@ public void onEnable() { return; } - 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) { @@ -134,7 +134,8 @@ public void onEnable() { this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); } - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(geyser)); + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", geyser, geyserCommandManager.getCommands())); + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyserext", geyser, geyserCommandManager.commands())); } @Override diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index d022074fd1e..6575f047c6c 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 @@ -37,14 +37,15 @@ import java.util.Arrays; import java.util.Collections; +import java.util.Map; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { private final GeyserCommandExecutor commandExecutor; - public GeyserBungeeCommandExecutor(GeyserImpl geyser) { - super("geyser"); + public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) { + super(name); - this.commandExecutor = new GeyserCommandExecutor(geyser); + this.commandExecutor = new GeyserCommandExecutor(geyser, commands); } @Override 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/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index fed5dd6b9ef..6e36b817d28 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 @@ -43,7 +43,6 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters; 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; @@ -125,7 +124,7 @@ public void onEnable() { } // 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("")) { @@ -148,7 +147,7 @@ public void onEnable() { 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; @@ -249,8 +248,10 @@ public void onEnable() { geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } - PluginCommand pluginCommand = this.getCommand("geyser"); - pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); + PluginCommand geyserCommand = this.getCommand("geyser"); + geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); + PluginCommand geyserExtCommand = this.getCommand("geyserext"); + geyserExtCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI @@ -277,7 +278,7 @@ public void onEnable() { 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 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 b5a6ee887fe..52779db23b3 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 @@ -38,11 +38,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor { - public GeyserSpigotCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index e28b8981d87..0f6398b491c 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -8,4 +8,7 @@ api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser \ No newline at end of file + usage: /geyser + geyserext: + description: The command any extensions can register to. + usage: /geyserext \ No newline at end of file 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 4e088161dc0..312dfb0879a 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 @@ -99,14 +99,14 @@ public void onEnable() { // 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); 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()); @@ -121,7 +121,8 @@ public void onEnable() { this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); this.geyserCommandManager.init(); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser), "geyser"); + Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); + Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.commands()), "geyserext"); } @Override 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 485c06c41ba..3598ea8c2fc 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 @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.sponge.command; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommandSource; @@ -40,15 +41,12 @@ import org.spongepowered.api.world.World; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements CommandCallable { - public GeyserSpongeCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override 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 0b228941bc6..739e99d438d 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 @@ -102,7 +102,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("")) { @@ -128,7 +128,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; @@ -148,7 +148,8 @@ public void onEnable() { this.geyserCommandManager = new GeyserVelocityCommandManager(geyser); this.geyserCommandManager.init(); - this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser)); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); + this.commandManager.register("geyserext", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.commands())); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { 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 b3c4221df54..c77a3daef92 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,6 +27,7 @@ import com.velocitypowered.api.command.SimpleCommand; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommandSource; @@ -37,11 +38,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand { - public GeyserVelocityCommandExecutor(GeyserImpl geyser) { - super(geyser); + public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map commands) { + super(geyser, commands); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java index 3d08600d10f..a9b1c734fab 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java @@ -37,15 +37,16 @@ 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 GeyserCommandExecutor { protected final GeyserImpl geyser; + private final Map commands; public GeyserCommand getCommand(String label) { - return (GeyserCommand) geyser.commandManager().commands().get(label); + return (GeyserCommand) commands.get(label); } @Nullable @@ -78,7 +79,6 @@ public List tabComplete(GeyserCommandSource sender) { } List availableCommands = new ArrayList<>(); - Map commands = geyser.commandManager().getCommands(); // Only show commands they have permission to use for (Map.Entry entry : commands.entrySet()) { diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index c6b9cbdd24d..4fd5ba4111f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -25,6 +25,7 @@ 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; @@ -34,7 +35,6 @@ import org.geysermc.geyser.api.command.CommandExecutor; import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; import org.geysermc.geyser.command.defaults.*; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -42,91 +42,104 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; -import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; @RequiredArgsConstructor public abstract class GeyserCommandManager extends CommandManager { @Getter - private final Map commands = new HashMap<>(); + private final Map commands = new Object2ObjectOpenHashMap<>(12); + private final Map extensionCommands = new Object2ObjectOpenHashMap<>(0); private final GeyserImpl geyser; public void init() { - register(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help")); - register(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); - register(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); - register(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); - register(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); - register(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); - register(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); - register(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - register(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); - register(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); - register(new ExtensionsCommand(geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); + registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", 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")); if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { - register(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } - this.geyser.eventBus().fire(new GeyserDefineCommandsEvent(this, this.commands())); + register(new HelpCommand(geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", "geyserext", extensionCommands)); + } + + /** + * For internal Geyser commands + */ + public void registerBuiltInCommand(GeyserCommand command) { + register(command, this.commands); } @Override public void register(@NonNull Command command) { - this.commands.put(command.name(), command); - this.geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); + register(command, this.extensionCommands); + } + + 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()) { - this.commands.put(alias, command); + commands.put(alias, command); } } @Override public void unregister(@NonNull Command command) { - this.commands.remove(command.name(), command); + this.extensionCommands.remove(command.name(), command); if (command.aliases().isEmpty()) { return; } for (String alias : command.aliases()) { - this.commands.remove(alias, command); + this.extensionCommands.remove(alias, command); } } @NotNull @Override public Map commands() { - return Collections.unmodifiableMap(this.commands); + return Collections.unmodifiableMap(this.extensionCommands); } - public void runCommand(GeyserCommandSource sender, String command) { - if (!command.startsWith("geyser ")) - return; + public boolean runCommand(GeyserCommandSource sender, String command) { + boolean extensionCommand = command.startsWith("geyserext "); + if (!command.startsWith("geyser ") && !extensionCommand) { + return false; + } - command = command.trim().replace("geyser ", ""); + command = command.trim().replace(extensionCommand ? "geyserext " : "geyser ", ""); String label; String[] args; if (!command.contains(" ")) { - label = command.toLowerCase(); + label = command.toLowerCase(Locale.ROOT); args = new String[0]; } else { - label = command.substring(0, command.indexOf(" ")).toLowerCase(); + 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 = commands.get(label); + Command cmd = (extensionCommand ? this.extensionCommands : this.commands).get(label); if (cmd == null) { geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); - return; + return false; } if (cmd instanceof GeyserCommand) { @@ -140,6 +153,8 @@ public void runCommand(GeyserCommandSource sender, String command) { } } } + + return true; } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 84a0730b856..81f34b75965 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 @@ -39,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("?")); } @@ -61,8 +66,7 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); sender.sendMessage(header); - Map cmds = geyser.commandManager().getCommands(); - for (Map.Entry entry : cmds.entrySet()) { + for (Map.Entry entry : commands.entrySet()) { Command cmd = entry.getValue(); // Standalone hack-in since it doesn't have a concept of permissions @@ -72,7 +76,7 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] continue; } - sender.sendMessage(ChatColor.YELLOW + "/geyser " + entry.getKey() + ChatColor.WHITE + ": " + + 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/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index e5840ba0d28..3301f7b9f87 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,7 +28,6 @@ import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -40,10 +39,8 @@ public class BedrockCommandRequestTranslator extends PacketTranslator Date: Tue, 12 Jul 2022 05:29:39 +0000 Subject: [PATCH 167/358] 1.19.10 fallout --- bootstrap/spigot/build.gradle.kts | 2 +- .../geyser/platform/spigot/GeyserPaperPingPassthrough.java | 4 ++-- build-logic/build.gradle.kts | 2 +- build-logic/src/main/kotlin/Versions.kt | 4 ++-- .../src/main/kotlin/geyser.base-conventions.gradle.kts | 4 ++-- core/build.gradle.kts | 2 +- .../geysermc/geyser/ping/GeyserLegacyPingPassthrough.java | 3 +-- .../java/org/geysermc/geyser/session/GeyserSession.java | 2 +- .../translator/inventory/item/GoatHornTranslator.java | 4 ++-- .../java/entity/player/JavaPlayerCombatKillTranslator.java | 6 +++--- 10 files changed, 16 insertions(+), 17 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 8e2b73cd1f5..d755f323ce2 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,4 +1,4 @@ -val paperVersion = "1.17.1-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet +val paperVersion = "1.19-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet val viaVersion = "4.0.0" val adaptersVersion = "1.4-SNAPSHOT" val commodoreVersion = "1.13" 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 64c18558519..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 @@ -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()) { diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 25cbfe9de90..dc8d6d620e4 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -16,6 +16,6 @@ dependencies { tasks.withType { kotlinOptions { - jvmTarget = "16" + jvmTarget = "17" } } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 779065bc53e..919935409be 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -30,10 +30,10 @@ object Versions { const val guavaVersion = "29.0-jre" const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" - const val protocolVersion = "977a9a1" + const val protocolVersion = "a78a64b" const val raknetVersion = "1.6.28-SNAPSHOT" const val mcauthlibVersion = "d9d773e" - const val mcprotocollibversion = "bb2b414" + const val mcprotocollibversion = "54fc9f0" const val packetlibVersion = "3.0" const val adventureVersion = "4.9.3" const val eventVersion = "3.0.0" diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 2ea5d88a479..b4cc63debe8 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -26,8 +26,8 @@ tasks { } java { - sourceCompatibility = JavaVersion.VERSION_16 - targetCompatibility = JavaVersion.VERSION_16 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 withSourcesJar() } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e82af56877b..561c2f554e8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) - api("com.github.CloudburstMC.Protocol", "bedrock-v527", Versions.protocolVersion) { + api("com.github.CloudburstMC.Protocol", "bedrock-v534", Versions.protocolVersion) { exclude("com.nukkitx.network", "raknet") exclude("com.nukkitx", "nbt") } 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 817ee9e815a..199e139184b 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; -import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.nukkitx.nbt.util.VarInts; import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; @@ -85,7 +84,7 @@ public void run() { 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/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 4639a952c43..cf94d299d11 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1643,7 +1643,7 @@ public void sendAdventureSettings() { boolean spectator = gameMode == GameMode.SPECTATOR; boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; - if (org.geysermc.geyser.network.MinecraftProtocol.supports1_19_10(this)) { + if (org.geysermc.geyser.network.GameProtocol.supports1_19_10(this)) { UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); adventureSettingsPacket.setNoMvP(false); adventureSettingsPacket.setNoPvM(false); 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/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerCombatKillTranslator.java index 0f1fd4d1b34..89be26e4a0e 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 Date: Tue, 12 Jul 2022 14:24:53 +0000 Subject: [PATCH 168/358] Build on Java 17 --- .github/workflows/pullrequest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index f5bb4c0427e..9d925c4dc43 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -9,11 +9,11 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 16 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: distribution: 'temurin' - java-version: 16 + java-version: 17 cache: 'gradle' - name: submodules-init uses: snickerbockers/submodules-init@v4 From 8b9f41d4a6dc409c3acd98f64ad2813d8529f93a Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Tue, 12 Jul 2022 07:38:57 -0700 Subject: [PATCH 169/358] Java 17 (Jenkinsfile) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f92778318e9..b3df4bc95c2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any tools { gradle 'Gradle 7' - jdk 'Java 16' + jdk 'Java 17' } options { buildDiscarder(logRotator(artifactNumToKeepStr: '20')) From 2dbd39c5a437ee14c589acb1ab2576c9779725b9 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:33:57 +0000 Subject: [PATCH 170/358] Per review by @Camotoy --- bootstrap/spigot/build.gradle.kts | 2 +- .../main/java/org/geysermc/geyser/session/GeyserSession.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index d755f323ce2..123ef06f2b9 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,4 +1,4 @@ -val paperVersion = "1.19-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet +val paperVersion = "1.19-R0.1-SNAPSHOT" val viaVersion = "4.0.0" val adaptersVersion = "1.4-SNAPSHOT" val commodoreVersion = "1.13" 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 cf94d299d11..edaed9893fc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -116,6 +116,7 @@ import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; @@ -1643,7 +1644,7 @@ public void sendAdventureSettings() { boolean spectator = gameMode == GameMode.SPECTATOR; boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; - if (org.geysermc.geyser.network.GameProtocol.supports1_19_10(this)) { + if (GameProtocol.supports1_19_10(this)) { UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); adventureSettingsPacket.setNoMvP(false); adventureSettingsPacket.setNoPvM(false); From d1fbb909a54ac4114bee7034b2d2df1300ac3d97 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:09:48 +0000 Subject: [PATCH 171/358] fixed java 16 thanks to @davchoo --- bootstrap/spigot/build.gradle.kts | 14 ++++++++++++-- build-logic/build.gradle.kts | 2 +- .../main/kotlin/geyser.base-conventions.gradle.kts | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 123ef06f2b9..5a12ded0146 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -9,6 +9,18 @@ dependencies { implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion) implementation("me.lucko", "commodore", commodoreVersion) + + // Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 + compileOnly("io.papermc.paper", "paper-api", paperVersion) { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) + } + } + compileOnly("io.papermc.paper", "paper-mojangapi", paperVersion) { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) + } + } } platformRelocate("it.unimi.dsi.fastutil") @@ -19,8 +31,6 @@ platformRelocate("me.lucko.commodore") platformRelocate("io.netty.channel.kqueue") // These dependencies are already present on the platform -provided("io.papermc.paper", "paper-api", paperVersion) -provided("io.papermc.paper", "paper-mojangapi", paperVersion) provided("com.viaversion", "viaversion", viaVersion) application { diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index dc8d6d620e4..25cbfe9de90 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -16,6 +16,6 @@ dependencies { tasks.withType { kotlinOptions { - jvmTarget = "17" + jvmTarget = "16" } } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index b4cc63debe8..2ea5d88a479 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -26,8 +26,8 @@ tasks { } java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_16 + targetCompatibility = JavaVersion.VERSION_16 withSourcesJar() } \ No newline at end of file From 0335c8263c21e30a28baa0f34a9eb97dbe808de3 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:20:42 +0000 Subject: [PATCH 172/358] Address @Camotoy's review --- bootstrap/spigot/build.gradle.kts | 2 +- build-logic/src/main/kotlin/Versions.kt | 2 ++ settings.gradle.kts | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 5a12ded0146..02883999da0 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,6 +1,6 @@ val paperVersion = "1.19-R0.1-SNAPSHOT" val viaVersion = "4.0.0" -val adaptersVersion = "1.4-SNAPSHOT" +val adaptersVersion = "1.5-SNAPSHOT" val commodoreVersion = "1.13" dependencies { diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 919935409be..27f7bcaf57c 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -31,6 +31,8 @@ object Versions { const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" const val protocolVersion = "a78a64b" + // Not pinned to specific version due to possible gradle bug + // See comment in settings.gradle.kts const val raknetVersion = "1.6.28-SNAPSHOT" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "54fc9f0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 0bc694b435d..dd08f3922ad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,7 +8,12 @@ dependencyResolutionManagement { mavenContent { releasesOnly() } } maven("https://repo.opencollab.dev/maven-snapshots") { - mavenContent { snapshotsOnly() } + mavenContent { + // This has the unintended side effect of not allowing snapshot version pinning. + // Likely a bug in Gradle's implementation of snapshot pinning + // See https://github.com/gradle/gradle/pull/406 + snapshotsOnly() + } } // Paper, Velocity From 2f1fa6d4177d44d462400df3f9c65d5451d78ac7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 14 Jul 2022 16:52:58 -0400 Subject: [PATCH 173/358] Update to 2.0.5-SNAPSHOT --- bootstrap/fabric/gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 8e6528e258a..7817ad24f29 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,9 +6,9 @@ minecraft_version=1.19 yarn_mappings=1.19+build.1 loader_version=0.14.6 # Mod Properties -mod_version=2.0.4-SNAPSHOT +mod_version=2.0.5-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.55.2+1.19 \ No newline at end of file +fabric_version=0.55.2+1.19 From bd34d65580a898c52a723b5bde6a7addb33be0a1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 14 Jul 2022 17:01:09 -0400 Subject: [PATCH 174/358] Update README.md --- bootstrap/fabric/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md index ef83f6db757..883d032895d 100644 --- a/bootstrap/fabric/README.md +++ b/bootstrap/fabric/README.md @@ -2,7 +2,7 @@ [![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/) -Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/java-1.18/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/java-1.18/) +Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/master/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/master/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) From 616c088b66830c8640a2b6e9f39f36af9ef14eb5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Jul 2022 18:35:40 -0400 Subject: [PATCH 175/358] Fix custom items with ItemTranslator#getBedrockItemMapping --- .../geysermc/geyser/inventory/Inventory.java | 7 +++---- .../inventory/item/ItemTranslator.java | 17 ++++++++++++----- .../BedrockInventoryTransactionTranslator.java | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) 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..58d55148997 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; @@ -136,9 +135,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/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 0a2ab57dfb4..b36833cb165 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 @@ -266,16 +266,23 @@ private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBe } /** - * 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 = getCustomItem(itemStack.getNbt(), mapping); + if (customItemId == -1) { + // No custom item + return mapping.getBedrockId(); + } else { + return customItemId; + } } private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 24c046ef296..815456132b2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -542,7 +542,7 @@ private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, Inven private boolean isIncorrectHeldItem(GeyserSession session, InventoryTransactionPacket packet) { int javaSlot = session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot()); - int expectedItemId = ItemTranslator.getBedrockItemMapping(session, session.getPlayerInventory().getItem(javaSlot)).getBedrockId(); + int expectedItemId = ItemTranslator.getBedrockItemId(session, session.getPlayerInventory().getItem(javaSlot)); int heldItemId = packet.getItemInHand() == null ? ItemData.AIR.getId() : packet.getItemInHand().getId(); if (expectedItemId != heldItemId) { From 8b300426dc9d8cb7b77a8ea55e67af236d0cc5a2 Mon Sep 17 00:00:00 2001 From: Carlos Ramos <57056947+CarlosRamosDev@users.noreply.github.com> Date: Thu, 28 Jul 2022 17:56:13 -0600 Subject: [PATCH 176/358] Update for Java 1.19.1 and Geyser 2.0.6-SNAPSHOT (#62) * Updated to Minecraft 1.19.1 * Update fabric.mod.json --- bootstrap/fabric/gradle.properties | 10 +++++----- bootstrap/fabric/src/main/resources/fabric.mod.json | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 7817ad24f29..c42b9c71304 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.19 -yarn_mappings=1.19+build.1 -loader_version=0.14.6 +minecraft_version=1.19.1 +yarn_mappings=1.19.1+build.1 +loader_version=0.14.8 # Mod Properties -mod_version=2.0.5-SNAPSHOT +mod_version=2.0.6-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.55.2+1.19 +fabric_version=0.58.5+1.19.1 diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index fd04f8ecf69..c02f07c3f65 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -23,8 +23,8 @@ "geyser-fabric.mixins.json" ], "depends": { - "fabricloader": ">=0.11.3", + "fabricloader": ">=0.14.8", "fabric": "*", - "minecraft": ">=1.19" + "minecraft": ">=1.19.1" } } From 7121051d9aa46df8ce8533c71e0e126c4f189fd3 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 2 Aug 2022 00:25:07 -0400 Subject: [PATCH 177/358] Merge mistake fixes --- .../standalone/GeyserStandaloneBootstrap.java | 2 -- build-logic/src/main/kotlin/Versions.kt | 2 +- .../geysermc/geyser/command/CommandManager.java | 0 .../geyser/command/GeyserCommandManager.java | 1 + .../command/defaults/ConnectionTestCommand.java | 14 +++++++------- .../protocol/java/JavaLoginTranslator.java | 2 -- pom.xml | 0 7 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/command/CommandManager.java delete mode 100644 pom.xml 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 523917ab3cc..052a41439e3 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 @@ -52,8 +52,6 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.LoopbackUtil; -import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; -import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import java.io.File; import java.io.IOException; diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 27f7bcaf57c..b02f3b02c89 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -35,7 +35,7 @@ object Versions { // See comment in settings.gradle.kts const val raknetVersion = "1.6.28-SNAPSHOT" const val mcauthlibVersion = "d9d773e" - const val mcprotocollibversion = "54fc9f0" + const val mcprotocollibversion = "9f78bd5" const val packetlibVersion = "3.0" const val adventureVersion = "4.9.3" const val eventVersion = "3.0.0" 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 e69de29bb2d..00000000000 diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 4fd5ba4111f..cb3cf6eee1a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -66,6 +66,7 @@ public void init() { 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 (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } 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/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 0adb46c4597..6aa613b2486 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 @@ -40,8 +40,6 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatTypeEntry; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; diff --git a/pom.xml b/pom.xml deleted file mode 100644 index e69de29bb2d..00000000000 From 17f3deb8df94f4e22403e176096c112791f06874 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 2 Aug 2022 01:11:17 -0400 Subject: [PATCH 178/358] try to reset languages --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 615694055b1..d9290402706 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 615694055b1bc7ffba5771dd931ef76b453d16a2 +Subproject commit d92904027061856248ece8382face369e9cc5d67 From a5dc70a3b56bd84974f6bb0cca0fcca438d089c9 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 2 Aug 2022 23:22:08 -0400 Subject: [PATCH 179/358] Refactor extension description --- .../api/extension/ExtensionDescription.java | 26 ++++++- .../extension/GeyserExtensionDescription.java | 74 ++++++++++++------- .../extension/GeyserExtensionLoader.java | 35 +++------ 3 files changed, 81 insertions(+), 54 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index e7741114498..3969bb65ffb 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -50,13 +50,35 @@ public interface ExtensionDescription { @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 */ - @NonNull - String apiVersion(); + default String apiVersion() { + return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); + } /** * Gets the extension's description diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index eaf29a819f8..797b43a8107 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,53 +25,75 @@ package org.geysermc.geyser.extension; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; -import org.yaml.snakeyaml.DumperOptions; +import org.geysermc.geyser.text.GeyserLocale; import org.yaml.snakeyaml.Yaml; import java.io.Reader; import java.util.*; +import java.util.regex.Pattern; -public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { - @SuppressWarnings("unchecked") - public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { - DumperOptions dumperOptions = new DumperOptions(); - dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); +public record GeyserExtensionDescription(@NonNull String name, + @NonNull String main, + int majorApiVersion, + int minorApiVersion, + int patchApiVersion, + @NonNull String version, + @NonNull List authors) implements ExtensionDescription { - Yaml yaml = new Yaml(dumperOptions); - Map yamlMap = yaml.loadAs(reader, LinkedHashMap.class); + private static final Yaml YAML = new Yaml(); + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); + public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); - String name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (name.isBlank()) { - throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); + @NonNull + @SuppressWarnings("unchecked") + public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { + Map map; + try { + map = YAML.loadAs(reader, HashMap.class); + } catch (Exception e) { + throw new InvalidDescriptionException(e); } - name = name.replace(" ", "_"); - String version = String.valueOf(yamlMap.get("version")); - String main = (String) yamlMap.get("main"); - String apiVersion; + String name = require(map, "name"); + if (!NAME_PATTERN.matcher(name).matches()) { + throw new InvalidDescriptionException("Invalid extension name, must match: " + NAME_PATTERN.pattern()); + } + String version = String.valueOf(map.get("version")); + String main = require(map, "main"); - Object api = yamlMap.get("api"); - if (api instanceof String) { - apiVersion = (String) api; - } else { - throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); + String apiVersion = require(map, "api"); + if (!API_VERSION_PATTERN.matcher(apiVersion).matches()) { + throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion)); } + String[] api = apiVersion.split("\\."); + int majorApi = Integer.parseUnsignedInt(api[0]); + int minorApi = Integer.parseUnsignedInt(api[1]); + int patchApi = Integer.parseUnsignedInt(api[2]); List authors = new ArrayList<>(); - if (yamlMap.containsKey("author")) { - authors.add((String) yamlMap.get("author")); + if (map.containsKey("author")) { + authors.add(String.valueOf(map.get("author"))); } - - if (yamlMap.containsKey("authors")) { + if (map.containsKey("authors")) { try { - authors.addAll((Collection) yamlMap.get("authors")); + authors.addAll((Collection) map.get("authors")); } catch (Exception e) { throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); } } - return new GeyserExtensionDescription(name, main, apiVersion, version, authors); + return new GeyserExtensionDescription(name, main, majorApi, minorApi, patchApi, version, authors); + } + + @NonNull + private static String require(Map desc, String key) throws InvalidDescriptionException { + Object value = desc.get(key); + if (value instanceof String) { + return (String) value; + } + throw new InvalidDescriptionException("Extension description is missing string property '" + key + "'"); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 55f018ddb2a..fcb718dec92 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -39,25 +39,24 @@ import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; +import java.io.Reader; import java.nio.file.*; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Stream; @RequiredArgsConstructor public class GeyserExtensionLoader extends ExtensionLoader { private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); - private static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); - public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException, InvalidDescriptionException { + public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException { if (path == null) { throw new InvalidExtensionException("Path is null"); } @@ -94,7 +93,9 @@ public GeyserExtensionDescription extensionDescription(Path path) throws Invalid Map environment = new HashMap<>(); try (FileSystem fileSystem = FileSystems.newFileSystem(path, environment, null)) { Path extensionYml = fileSystem.getPath("extension.yml"); - return GeyserExtensionDescription.fromYaml(Files.newBufferedReader(extensionYml)); + try (Reader reader = Files.newBufferedReader(extensionYml)) { + return GeyserExtensionDescription.fromYaml(reader); + } } catch (IOException ex) { throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); } @@ -149,9 +150,6 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { try { GeyserExtensionDescription description = this.extensionDescription(path); - if (description == null) { - return; - } String name = description.name(); if (extensions.containsKey(name) || extensionManager.extension(name) != null) { @@ -159,30 +157,15 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { return; } - int majorVersion = Geyser.api().majorApiVersion(); - int minorVersion = Geyser.api().minorApiVersion(); - - try { - // Check the format: majorVersion.minorVersion.patch - if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, majorVersion + "." + minorVersion)); - return; - } - - String[] versionArray = description.apiVersion().split("\\."); - // Completely different API version - if (Integer.parseInt(versionArray[0]) != majorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.majorApiVersion() != Geyser.api().majorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } // If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > minorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.minorApiVersion() > Geyser.api().minorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } From aa7d0f4a57b2b93acc69aab0e2f1704c2e0e188f Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 3 Aug 2022 00:20:27 -0400 Subject: [PATCH 180/358] Use class for reading extension.yml --- .../extension/GeyserExtensionDescription.java | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 797b43a8107..8fab4e338f3 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,14 +25,18 @@ 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 name, @@ -43,28 +47,27 @@ public record GeyserExtensionDescription(@NonNull String name, @NonNull String version, @NonNull List authors) implements ExtensionDescription { - private static final Yaml YAML = new Yaml(); + private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader())); public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); @NonNull - @SuppressWarnings("unchecked") public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { - Map map; + Source source; try { - map = YAML.loadAs(reader, HashMap.class); + source = YAML.loadAs(reader, Source.class); } catch (Exception e) { throw new InvalidDescriptionException(e); } - String name = require(map, "name"); + 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(map.get("version")); - String main = require(map, "main"); + String version = String.valueOf(source.version); + String main = require(source::getMain, "main"); - String apiVersion = require(map, "api"); + 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)); } @@ -74,26 +77,33 @@ public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidD int patchApi = Integer.parseUnsignedInt(api[2]); List authors = new ArrayList<>(); - if (map.containsKey("author")) { - authors.add(String.valueOf(map.get("author"))); + if (source.author != null) { + authors.add(source.author); } - if (map.containsKey("authors")) { - try { - authors.addAll((Collection) map.get("authors")); - } catch (Exception e) { - throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); - } + if (source.authors != null) { + authors.addAll(source.authors); } return new GeyserExtensionDescription(name, main, majorApi, minorApi, patchApi, version, authors); } @NonNull - private static String require(Map desc, String key) throws InvalidDescriptionException { - Object value = desc.get(key); - if (value instanceof String) { - return (String) value; + 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 + "'"); } - throw new InvalidDescriptionException("Extension description is missing string property '" + key + "'"); + return value; + } + + @Getter + @Setter + public static class Source { + String name; + String main; + String api; + String version; + String author; + List authors; } } From 36ef23b24e50590fef2b63b52478a86ea2008763 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 3 Aug 2022 00:30:22 -0400 Subject: [PATCH 181/358] Don't allow empty extension name --- .../geysermc/geyser/extension/GeyserExtensionDescription.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 8fab4e338f3..010c1430c30 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -48,7 +48,7 @@ public record GeyserExtensionDescription(@NonNull String name, @NonNull List authors) implements ExtensionDescription { private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader())); - public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]+$"); public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); @NonNull From 67e3bf1f8dd864fa68c0cd672212145e03a030ba Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 4 Aug 2022 17:55:12 -0400 Subject: [PATCH 182/358] Move extensions folder to Geyser's config folder (#3202) * Move extensions folder to Geyser's config folder * Move directory field --- .../geysermc/geyser/extension/GeyserExtensionLoader.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 55f018ddb2a..d317bc013d7 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -43,19 +43,18 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Stream; @RequiredArgsConstructor public class GeyserExtensionLoader extends ExtensionLoader { - private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); private static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); + private final Path extensionsDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("extensions"); public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException, InvalidDescriptionException { if (path == null) { @@ -127,15 +126,15 @@ void setClass(String name, final Class clazz) { @Override protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { try { - if (Files.notExists(EXTENSION_DIRECTORY)) { - Files.createDirectory(EXTENSION_DIRECTORY); + if (Files.notExists(extensionsDirectory)) { + Files.createDirectory(extensionsDirectory); } Map extensions = new LinkedHashMap<>(); Map loadedExtensions = new LinkedHashMap<>(); Pattern[] extensionFilters = this.extensionFilters(); - try (Stream entries = Files.walk(EXTENSION_DIRECTORY)) { + try (Stream entries = Files.walk(extensionsDirectory)) { entries.forEach(path -> { if (Files.isDirectory(path)) { return; From 3c27273eac3a13b051523cb004f4ca521885576e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 5 Aug 2022 11:29:47 -0400 Subject: [PATCH 183/358] Indicate support for Java 1.19.2 --- README.md | 2 +- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62db2d60a19..49aba79ac34 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.0 - 1.19.10/1.19.11 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 391bff65f41..3d5bc01abd9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -33,7 +33,6 @@ import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.StringJoiner; @@ -101,7 +100,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(), "1.19.2"); } /** From f74713c0edca0f94eb90c26c7dfd177ff8623381 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 7 Aug 2022 12:09:54 -0400 Subject: [PATCH 184/358] Initial support for 1.19.20 Bedrock --- core/pom.xml | 4 +-- .../geyser/network/MinecraftProtocol.java | 2 ++ .../populator/BlockRegistryPopulator.java | 31 ++++++++++-------- .../geyser/session/GeyserSession.java | 2 ++ .../bedrock/block_palette.1_19_20.nbt | Bin 0 -> 55132 bytes core/src/main/resources/mappings | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 core/src/main/resources/bedrock/block_palette.1_19_20.nbt diff --git a/core/pom.xml b/core/pom.xml index 8977854cb7f..88bde33600b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -128,8 +128,8 @@ com.github.CloudburstMC.Protocol - bedrock-v534 - a78a64b + bedrock-v544 + 92d9854 compile diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 3d5bc01abd9..e0a06b5e38e 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -30,6 +30,7 @@ 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 org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; @@ -63,6 +64,7 @@ public final class MinecraftProtocol { SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() .minecraftVersion("1.19.10/1.19.11") .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); } /** 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..ce8f05ed8a1 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,7 @@ 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 it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -56,17 +57,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 +71,17 @@ 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_0", Bedrock_v527.V527_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { + if (bedrockIdentifier.equals("minecraft:muddy_mangrove_roots")) { + statesBuilder.remove("pillar_axis"); + } + return null; + }) + .put(ObjectIntPair.of("1_19_20", Bedrock_v544.V544_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 +96,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 +114,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()]; 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..f7c990ac3e1 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1491,6 +1491,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); 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 0000000000000000000000000000000000000000..75d84b6a767aed381f7afd63fe0853b6e9c2a379 GIT binary patch literal 55132 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

N!K?VWkIdC++V5CX^Dc;QTi}}9T=lerttP4d#b zb@t-=s{f05Iog>R1Xnsx(kBjuLM1(5y> z0`qrk9IcJ3+X^bauWCFyv*Q|9W3$obUsIWO6GmRVc&~n)wYRmd-o(d};}KuUSvr_< zo?aev3X z@?O();jB+SQW0T>*lBV|#ERayqI6_69C>_wlrJ2B*~T_zyVA)LGk_H(QOpU&q^aPc zgIaaCmZOZhK#J7-|J%$y!aAdFvA{53{F3{;m8ORSjLa;Yd0HZz1AiZcz$hmDM`Awa zn{-Jc@GLf5N>k!DrZJRc<1Di;v;f9x^n#gwRmlZFpeT_!p?pUzZBF&mka*oEPSx^3 z%4?=6**e=DBe_)h1LL%53-I%rtiTCBzA~3~2^a{$*?Ucsrhl{dEK;Yra)wkk6D;f` zTGJY90D-s%PIKUCK4_XQuZ!y-86EpCGadf7nMeO+rrdvH=H-8HrUaJBX=-j)FB5`v zQIKt9Qv^8%>R&&^`QnSxu&Fp5}1Li)>l8YTv9@r0ZJG;P5AP^?cZ9k;c1oTm@6?}pJ zIzgZEZyyB>sSSb4mB%**{(%8@LZt)k{ASHq_V=ML1%3ZzX666fOlaPgjik=}JWRvd z?+W4I@6-2dtjAl;)4`RdKUK2&n$d0piAM?yg}^}O7o^ZzRG4s>o2^<>lpu37+`n>F zuqyr-85+24$wO9Q)v)eIJ?^yZpk^agqM~c~^5}C*Mt3)pwabX8U4Nd__3uP7Z->8O zKwPFCId^Pi0`xyHR#^k9M&`)F7Hs@kBQ5%K94Lx^#YN~G4y%nvc$ur@K>|$EG zAKNdmu)?UQPP2YS zU=&B@Ra9zNLe2ROO+n^5G1=p$??b{bYTsXUWu8g(qoWFqbDVo8GMsqN@FsYKVTl z8lK9|?b-#)Zwz9VH%lp}R{E-1_D8c(moRQtfWn;P2kME5d;EbqxtsOQ1YUdN?gTlp vX!0f2XX*s~rfjqL4L=}#m+!XCdF;?V!=^KE4f37S^XE`*=^ZZ(bcp{2Ge*tJ literal 0 HcmV?d00001 diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 01278912327..2c68dab9d75 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 0127891232742209b8470298dfd997249c506320 +Subproject commit 2c68dab9d751f78b2f5b0298da5e338ad6bc07ca From ef81bdeb6bb6a70a644055b9e32b3bef4dc6cd46 Mon Sep 17 00:00:00 2001 From: 7man7LMYT <67489949+7man7LMYT@users.noreply.github.com> Date: Tue, 9 Aug 2022 13:52:53 -0700 Subject: [PATCH 185/358] Update items to 1.19.20 (#3215) * Update to 1.19.20 * Add 1.19.20 mapping * Revert biome changes --- .../populator/ItemRegistryPopulator.java | 2 + .../bedrock/creative_items.1_19_20.json | 5440 +++++++++++++++++ .../resources/bedrock/entity_identifiers.dat | Bin 7762 -> 7762 bytes .../bedrock/runtime_item_states.1_19_20.json | 4530 ++++++++++++++ 4 files changed, 9972 insertions(+) create mode 100644 core/src/main/resources/bedrock/creative_items.1_19_20.json create mode 100644 core/src/main/resources/bedrock/runtime_item_states.1_19_20.json 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..ad1020e9bda 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -37,6 +37,7 @@ import com.nukkitx.protocol.bedrock.packet.StartGamePacket; 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; @@ -68,6 +69,7 @@ public static void populate() { 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())); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); diff --git a/core/src/main/resources/bedrock/creative_items.1_19_20.json b/core/src/main/resources/bedrock/creative_items.1_19_20.json new file mode 100644 index 00000000000..98d9e007a2e --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_19_20.json @@ -0,0 +1,5440 @@ +{ + "items" : [ + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6073 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6074 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6075 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6076 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6077 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6078 + }, + { + "id" : "minecraft:mangrove_planks", + "blockRuntimeId" : 949 + }, + { + "id" : "minecraft:crimson_planks", + "blockRuntimeId" : 4852 + }, + { + "id" : "minecraft:warped_planks", + "blockRuntimeId" : 922 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1184 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1185 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1186 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1187 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1188 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1189 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1196 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1191 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1192 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1190 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1193 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1197 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1194 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1195 + }, + { + "id" : "minecraft:blackstone_wall", + "blockRuntimeId" : 3932 + }, + { + "id" : "minecraft:polished_blackstone_wall", + "blockRuntimeId" : 6726 + }, + { + "id" : "minecraft:polished_blackstone_brick_wall", + "blockRuntimeId" : 973 + }, + { + "id" : "minecraft:cobbled_deepslate_wall", + "blockRuntimeId" : 8084 + }, + { + "id" : "minecraft:deepslate_tile_wall", + "blockRuntimeId" : 5073 + }, + { + "id" : "minecraft:polished_deepslate_wall", + "blockRuntimeId" : 7819 + }, + { + "id" : "minecraft:deepslate_brick_wall", + "blockRuntimeId" : 431 + }, + { + "id" : "minecraft:mud_brick_wall", + "blockRuntimeId" : 732 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7366 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7367 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7368 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7369 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7370 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7371 + }, + { + "id" : "minecraft:mangrove_fence", + "blockRuntimeId" : 6635 + }, + { + "id" : "minecraft:nether_brick_fence", + "blockRuntimeId" : 4292 + }, + { + "id" : "minecraft:crimson_fence", + "blockRuntimeId" : 7998 + }, + { + "id" : "minecraft:warped_fence", + "blockRuntimeId" : 5855 + }, + { + "id" : "minecraft:fence_gate", + "blockRuntimeId" : 76 + }, + { + "id" : "minecraft:spruce_fence_gate", + "blockRuntimeId" : 6586 + }, + { + "id" : "minecraft:birch_fence_gate", + "blockRuntimeId" : 3779 + }, + { + "id" : "minecraft:jungle_fence_gate", + "blockRuntimeId" : 5367 + }, + { + "id" : "minecraft:acacia_fence_gate", + "blockRuntimeId" : 7588 + }, + { + "id" : "minecraft:dark_oak_fence_gate", + "blockRuntimeId" : 4175 + }, + { + "id" : "minecraft:mangrove_fence_gate", + "blockRuntimeId" : 4627 + }, + { + "id" : "minecraft:crimson_fence_gate", + "blockRuntimeId" : 4663 + }, + { + "id" : "minecraft:warped_fence_gate", + "blockRuntimeId" : 5401 + }, + { + "id" : "minecraft:normal_stone_stairs", + "blockRuntimeId" : 635 + }, + { + "id" : "minecraft:stone_stairs", + "blockRuntimeId" : 3710 + }, + { + "id" : "minecraft:mossy_cobblestone_stairs", + "blockRuntimeId" : 4094 + }, + { + "id" : "minecraft:oak_stairs", + "blockRuntimeId" : 273 + }, + { + "id" : "minecraft:spruce_stairs", + "blockRuntimeId" : 128 + }, + { + "id" : "minecraft:birch_stairs", + "blockRuntimeId" : 7005 + }, + { + "id" : "minecraft:jungle_stairs", + "blockRuntimeId" : 6969 + }, + { + "id" : "minecraft:acacia_stairs", + "blockRuntimeId" : 6202 + }, + { + "id" : "minecraft:dark_oak_stairs", + "blockRuntimeId" : 5065 + }, + { + "id" : "minecraft:mangrove_stairs", + "blockRuntimeId" : 4597 + }, + { + "id" : "minecraft:stone_brick_stairs", + "blockRuntimeId" : 933 + }, + { + "id" : "minecraft:mossy_stone_brick_stairs", + "blockRuntimeId" : 5885 + }, + { + "id" : "minecraft:sandstone_stairs", + "blockRuntimeId" : 3589 + }, + { + "id" : "minecraft:smooth_sandstone_stairs", + "blockRuntimeId" : 3629 + }, + { + "id" : "minecraft:red_sandstone_stairs", + "blockRuntimeId" : 5352 + }, + { + "id" : "minecraft:smooth_red_sandstone_stairs", + "blockRuntimeId" : 5548 + }, + { + "id" : "minecraft:granite_stairs", + "blockRuntimeId" : 3539 + }, + { + "id" : "minecraft:polished_granite_stairs", + "blockRuntimeId" : 4152 + }, + { + "id" : "minecraft:diorite_stairs", + "blockRuntimeId" : 4393 + }, + { + "id" : "minecraft:polished_diorite_stairs", + "blockRuntimeId" : 6716 + }, + { + "id" : "minecraft:andesite_stairs", + "blockRuntimeId" : 5310 + }, + { + "id" : "minecraft:polished_andesite_stairs", + "blockRuntimeId" : 7030 + }, + { + "id" : "minecraft:brick_stairs", + "blockRuntimeId" : 6532 + }, + { + "id" : "minecraft:nether_brick_stairs", + "blockRuntimeId" : 106 + }, + { + "id" : "minecraft:red_nether_brick_stairs", + "blockRuntimeId" : 6604 + }, + { + "id" : "minecraft:end_brick_stairs", + "blockRuntimeId" : 6384 + }, + { + "id" : "minecraft:quartz_stairs", + "blockRuntimeId" : 4769 + }, + { + "id" : "minecraft:smooth_quartz_stairs", + "blockRuntimeId" : 7702 + }, + { + "id" : "minecraft:purpur_stairs", + "blockRuntimeId" : 7757 + }, + { + "id" : "minecraft:prismarine_stairs", + "blockRuntimeId" : 7265 + }, + { + "id" : "minecraft:dark_prismarine_stairs", + "blockRuntimeId" : 7432 + }, + { + "id" : "minecraft:prismarine_bricks_stairs", + "blockRuntimeId" : 206 + }, + { + "id" : "minecraft:crimson_stairs", + "blockRuntimeId" : 6282 + }, + { + "id" : "minecraft:warped_stairs", + "blockRuntimeId" : 3720 + }, + { + "id" : "minecraft:blackstone_stairs", + "blockRuntimeId" : 7021 + }, + { + "id" : "minecraft:polished_blackstone_stairs", + "blockRuntimeId" : 4299 + }, + { + "id" : "minecraft:polished_blackstone_brick_stairs", + "blockRuntimeId" : 4479 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 4606 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 4589 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 4307 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 353 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 395 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 3904 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 6169 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_stairs", + "blockRuntimeId" : 5842 + }, + { + "id" : "minecraft:cobbled_deepslate_stairs", + "blockRuntimeId" : 147 + }, + { + "id" : "minecraft:deepslate_tile_stairs", + "blockRuntimeId" : 4655 + }, + { + "id" : "minecraft:polished_deepslate_stairs", + "blockRuntimeId" : 294 + }, + { + "id" : "minecraft:deepslate_brick_stairs", + "blockRuntimeId" : 7424 + }, + { + "id" : "minecraft:mud_brick_stairs", + "blockRuntimeId" : 5524 + }, + { + "id" : "minecraft:wooden_door" + }, + { + "id" : "minecraft:spruce_door" + }, + { + "id" : "minecraft:birch_door" + }, + { + "id" : "minecraft:jungle_door" + }, + { + "id" : "minecraft:acacia_door" + }, + { + "id" : "minecraft:dark_oak_door" + }, + { + "id" : "minecraft:mangrove_door" + }, + { + "id" : "minecraft:iron_door" + }, + { + "id" : "minecraft:crimson_door" + }, + { + "id" : "minecraft:warped_door" + }, + { + "id" : "minecraft:trapdoor", + "blockRuntimeId" : 229 + }, + { + "id" : "minecraft:spruce_trapdoor", + "blockRuntimeId" : 6554 + }, + { + "id" : "minecraft:birch_trapdoor", + "blockRuntimeId" : 6652 + }, + { + "id" : "minecraft:jungle_trapdoor", + "blockRuntimeId" : 5383 + }, + { + "id" : "minecraft:acacia_trapdoor", + "blockRuntimeId" : 5591 + }, + { + "id" : "minecraft:dark_oak_trapdoor", + "blockRuntimeId" : 7504 + }, + { + "id" : "minecraft:mangrove_trapdoor", + "blockRuntimeId" : 4487 + }, + { + "id" : "minecraft:iron_trapdoor", + "blockRuntimeId" : 321 + }, + { + "id" : "minecraft:crimson_trapdoor", + "blockRuntimeId" : 4335 + }, + { + "id" : "minecraft:warped_trapdoor", + "blockRuntimeId" : 4735 + }, + { + "id" : "minecraft:iron_bars", + "blockRuntimeId" : 4803 + }, + { + "id" : "minecraft:glass", + "blockRuntimeId" : 6166 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1135 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1143 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1142 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1150 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1147 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1149 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1136 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1139 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1140 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1148 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1144 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1138 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1146 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1145 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1137 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1141 + }, + { + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 5977 + }, + { + "id" : "minecraft:glass_pane", + "blockRuntimeId" : 5235 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4854 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4862 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4861 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4869 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4866 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4868 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4855 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4858 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4859 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4867 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4863 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4857 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4865 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4864 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4856 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4860 + }, + { + "id" : "minecraft:ladder", + "blockRuntimeId" : 8264 + }, + { + "id" : "minecraft:scaffolding", + "blockRuntimeId" : 3573 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4272 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5824 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4275 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5795 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5272 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5273 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5274 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5275 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5276 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5277 + }, + { + "id" : "minecraft:mangrove_slab", + "blockRuntimeId" : 1151 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4277 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5822 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4273 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5825 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5796 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5790 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5826 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5807 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5812 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5813 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5810 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5811 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5809 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5808 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4276 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4279 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5797 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5806 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4278 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5823 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5791 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5792 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5793 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5794 + }, + { + "id" : "minecraft:crimson_slab", + "blockRuntimeId" : 5902 + }, + { + "id" : "minecraft:warped_slab", + "blockRuntimeId" : 6486 + }, + { + "id" : "minecraft:blackstone_slab", + "blockRuntimeId" : 912 + }, + { + "id" : "minecraft:polished_blackstone_slab", + "blockRuntimeId" : 6020 + }, + { + "id" : "minecraft:polished_blackstone_brick_slab", + "blockRuntimeId" : 4194 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 5237 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 6602 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 6055 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 5284 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 7817 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 249 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 6547 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 710 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 7312 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 288 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 4293 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 3718 + }, + { + "id" : "minecraft:mud_brick_slab", + "blockRuntimeId" : 3912 + }, + { + "id" : "minecraft:brick_block", + "blockRuntimeId" : 4767 + }, + { + "id" : "minecraft:chiseled_nether_bricks", + "blockRuntimeId" : 7251 + }, + { + "id" : "minecraft:cracked_nether_bricks", + "blockRuntimeId" : 4554 + }, + { + "id" : "minecraft:quartz_bricks", + "blockRuntimeId" : 6353 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6549 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6550 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6551 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6552 + }, + { + "id" : "minecraft:end_bricks", + "blockRuntimeId" : 281 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6089 + }, + { + "id" : "minecraft:polished_blackstone_bricks", + "blockRuntimeId" : 4682 + }, + { + "id" : "minecraft:cracked_polished_blackstone_bricks", + "blockRuntimeId" : 7216 + }, + { + "id" : "minecraft:gilded_blackstone", + "blockRuntimeId" : 4588 + }, + { + "id" : "minecraft:chiseled_polished_blackstone", + "blockRuntimeId" : 5064 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 4583 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 4162 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 5466 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 5366 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 5236 + }, + { + "id" : "minecraft:cobblestone", + "blockRuntimeId" : 3617 + }, + { + "id" : "minecraft:mossy_cobblestone", + "blockRuntimeId" : 252 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 6672 + }, + { + "id" : "minecraft:smooth_stone", + "blockRuntimeId" : 4584 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3655 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3656 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3657 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3658 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6582 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6583 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6584 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6585 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 5400 + }, + { + "id" : "minecraft:dried_kelp_block", + "blockRuntimeId" : 7981 + }, + { + "id" : "minecraft:gold_block", + "blockRuntimeId" : 291 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 8263 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 4653 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 595 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 8248 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 3555 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 7736 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 696 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 709 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 7544 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 4691 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 6168 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 7199 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 5480 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 7295 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 3811 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 4853 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 214 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 1161 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 272 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 4288 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 8262 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 5271 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 363 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3698 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3700 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3699 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3701 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6087 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6088 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 4235 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 894 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 4478 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 697 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 4236 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 7274 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 146 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 3777 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 8261 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3460 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3468 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3467 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3475 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3472 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3474 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3461 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3464 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3465 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3473 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3469 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3463 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3471 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3470 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3462 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3466 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 951 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 959 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 958 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 966 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 963 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 965 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 952 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 955 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 956 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 964 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 960 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 954 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 962 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 961 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 953 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 957 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6266 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6274 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6273 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6281 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6278 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6280 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6267 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6270 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6271 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6279 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6275 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6269 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6277 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6276 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6268 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6272 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 662 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 670 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 669 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 677 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 674 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 676 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 663 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 666 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 667 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 675 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 671 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 665 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 673 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 672 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 664 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 668 + }, + { + "id" : "minecraft:clay", + "blockRuntimeId" : 7126 + }, + { + "id" : "minecraft:hardened_clay", + "blockRuntimeId" : 643 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6178 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6186 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6185 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6193 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6190 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6192 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6179 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6182 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6183 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6191 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6187 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6181 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6189 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6188 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6180 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6184 + }, + { + "id" : "minecraft:white_glazed_terracotta", + "blockRuntimeId" : 5575 + }, + { + "id" : "minecraft:silver_glazed_terracotta", + "blockRuntimeId" : 3533 + }, + { + "id" : "minecraft:gray_glazed_terracotta", + "blockRuntimeId" : 8255 + }, + { + "id" : "minecraft:black_glazed_terracotta", + "blockRuntimeId" : 5836 + }, + { + "id" : "minecraft:brown_glazed_terracotta", + "blockRuntimeId" : 3549 + }, + { + "id" : "minecraft:red_glazed_terracotta", + "blockRuntimeId" : 4169 + }, + { + "id" : "minecraft:orange_glazed_terracotta", + "blockRuntimeId" : 1153 + }, + { + "id" : "minecraft:yellow_glazed_terracotta", + "blockRuntimeId" : 915 + }, + { + "id" : "minecraft:lime_glazed_terracotta", + "blockRuntimeId" : 223 + }, + { + "id" : "minecraft:green_glazed_terracotta", + "blockRuntimeId" : 6612 + }, + { + "id" : "minecraft:cyan_glazed_terracotta", + "blockRuntimeId" : 5360 + }, + { + "id" : "minecraft:light_blue_glazed_terracotta", + "blockRuntimeId" : 5473 + }, + { + "id" : "minecraft:blue_glazed_terracotta", + "blockRuntimeId" : 5467 + }, + { + "id" : "minecraft:purple_glazed_terracotta", + "blockRuntimeId" : 7013 + }, + { + "id" : "minecraft:magenta_glazed_terracotta", + "blockRuntimeId" : 967 + }, + { + "id" : "minecraft:pink_glazed_terracotta", + "blockRuntimeId" : 6541 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 7716 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 7718 + }, + { + "id" : "minecraft:packed_mud", + "blockRuntimeId" : 283 + }, + { + "id" : "minecraft:mud_bricks", + "blockRuntimeId" : 6891 + }, + { + "id" : "minecraft:nether_wart_block", + "blockRuntimeId" : 4295 + }, + { + "id" : "minecraft:warped_wart_block", + "blockRuntimeId" : 5907 + }, + { + "id" : "minecraft:shroomlight", + "blockRuntimeId" : 5063 + }, + { + "id" : "minecraft:crimson_nylium", + "blockRuntimeId" : 4191 + }, + { + "id" : "minecraft:warped_nylium", + "blockRuntimeId" : 6351 + }, + { + "id" : "minecraft:basalt", + "blockRuntimeId" : 4351 + }, + { + "id" : "minecraft:polished_basalt", + "blockRuntimeId" : 24 + }, + { + "id" : "minecraft:smooth_basalt", + "blockRuntimeId" : 1159 + }, + { + "id" : "minecraft:soul_soil", + "blockRuntimeId" : 5832 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 5753 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 5754 + }, + { + "id" : "minecraft:farmland", + "blockRuntimeId" : 3914 + }, + { + "id" : "minecraft:grass", + "blockRuntimeId" : 6977 + }, + { + "id" : "minecraft:grass_path", + "blockRuntimeId" : 8083 + }, + { + "id" : "minecraft:podzol", + "blockRuntimeId" : 4652 + }, + { + "id" : "minecraft:mycelium", + "blockRuntimeId" : 3685 + }, + { + "id" : "minecraft:mud", + "blockRuntimeId" : 6686 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 655 + }, + { + "id" : "minecraft:iron_ore", + "blockRuntimeId" : 4692 + }, + { + "id" : "minecraft:gold_ore", + "blockRuntimeId" : 914 + }, + { + "id" : "minecraft:diamond_ore", + "blockRuntimeId" : 4363 + }, + { + "id" : "minecraft:lapis_ore", + "blockRuntimeId" : 7701 + }, + { + "id" : "minecraft:redstone_ore", + "blockRuntimeId" : 4291 + }, + { + "id" : "minecraft:coal_ore", + "blockRuntimeId" : 4289 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 3556 + }, + { + "id" : "minecraft:emerald_ore", + "blockRuntimeId" : 7349 + }, + { + "id" : "minecraft:quartz_ore", + "blockRuntimeId" : 4503 + }, + { + "id" : "minecraft:nether_gold_ore", + "blockRuntimeId" : 27 + }, + { + "id" : "minecraft:ancient_debris", + "blockRuntimeId" : 6109 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 7275 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 6108 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 8040 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 7264 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 6618 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 6352 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 7198 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 105 + }, + { + "id" : "minecraft:gravel", + "blockRuntimeId" : 8289 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 656 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 658 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 660 + }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 7587 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 253 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 657 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 659 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 661 + }, + { + "id" : "minecraft:polished_blackstone", + "blockRuntimeId" : 3684 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 7756 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4197 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4198 + }, + { + "id" : "minecraft:cactus", + "blockRuntimeId" : 6988 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6674 + }, + { + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 7545 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6675 + }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 6290 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6676 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 5974 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6677 + }, + { + "id" : "minecraft:stripped_jungle_log", + "blockRuntimeId" : 644 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 3832 + }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 5850 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 3833 + }, + { + "id" : "minecraft:stripped_dark_oak_log", + "blockRuntimeId" : 216 + }, + { + "id" : "minecraft:mangrove_log", + "blockRuntimeId" : 350 + }, + { + "id" : "minecraft:stripped_mangrove_log", + "blockRuntimeId" : 8286 + }, + { + "id" : "minecraft:crimson_stem", + "blockRuntimeId" : 5899 + }, + { + "id" : "minecraft:stripped_crimson_stem", + "blockRuntimeId" : 6950 + }, + { + "id" : "minecraft:warped_stem", + "blockRuntimeId" : 6488 + }, + { + "id" : "minecraft:stripped_warped_stem", + "blockRuntimeId" : 7402 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3476 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3482 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3477 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3483 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3478 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3484 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3479 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3485 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3480 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3486 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3481 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3487 + }, + { + "id" : "minecraft:mangrove_wood", + "blockRuntimeId" : 4163 + }, + { + "id" : "minecraft:stripped_mangrove_wood", + "blockRuntimeId" : 4231 + }, + { + "id" : "minecraft:crimson_hyphae", + "blockRuntimeId" : 4296 + }, + { + "id" : "minecraft:stripped_crimson_hyphae", + "blockRuntimeId" : 6501 + }, + { + "id" : "minecraft:warped_hyphae", + "blockRuntimeId" : 5904 + }, + { + "id" : "minecraft:stripped_warped_hyphae", + "blockRuntimeId" : 5581 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6092 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6093 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6094 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6095 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 4355 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 4356 + }, + { + "id" : "minecraft:mangrove_leaves", + "blockRuntimeId" : 6668 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 7712 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 6341 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 714 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 715 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 716 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 717 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 718 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 719 + }, + { + "id" : "minecraft:mangrove_propagule", + "blockRuntimeId" : 6978 + }, + { + "id" : "minecraft:bee_nest", + "blockRuntimeId" : 5756 + }, + { + "id" : "minecraft:wheat_seeds" + }, + { + "id" : "minecraft:pumpkin_seeds" + }, + { + "id" : "minecraft:melon_seeds" + }, + { + "id" : "minecraft:beetroot_seeds" + }, + { + "id" : "minecraft:wheat" + }, + { + "id" : "minecraft:beetroot" + }, + { + "id" : "minecraft:potato" + }, + { + "id" : "minecraft:poisonous_potato" + }, + { + "id" : "minecraft:carrot" + }, + { + "id" : "minecraft:golden_carrot" + }, + { + "id" : "minecraft:apple" + }, + { + "id" : "minecraft:golden_apple" + }, + { + "id" : "minecraft:enchanted_golden_apple" + }, + { + "id" : "minecraft:melon_block", + "blockRuntimeId" : 394 + }, + { + "id" : "minecraft:melon_slice" + }, + { + "id" : "minecraft:glistering_melon_slice" + }, + { + "id" : "minecraft:sweet_berries" + }, + { + "id" : "minecraft:glow_berries" + }, + { + "id" : "minecraft:pumpkin", + "blockRuntimeId" : 4579 + }, + { + "id" : "minecraft:carved_pumpkin", + "blockRuntimeId" : 7380 + }, + { + "id" : "minecraft:lit_pumpkin", + "blockRuntimeId" : 6687 + }, + { + "id" : "minecraft:honeycomb" + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 931 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5457 + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 930 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5456 + }, + { + "id" : "minecraft:nether_sprouts" + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6494 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6492 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6493 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6491 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6495 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6499 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6497 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6498 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6496 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6500 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4618 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4616 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4617 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4615 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4619 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 69 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 67 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 68 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 66 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 70 + }, + { + "id" : "minecraft:kelp" + }, + { + "id" : "minecraft:seagrass", + "blockRuntimeId" : 246 + }, + { + "id" : "minecraft:crimson_roots", + "blockRuntimeId" : 7575 + }, + { + "id" : "minecraft:warped_roots", + "blockRuntimeId" : 4364 + }, + { + "id" : "minecraft:yellow_flower", + "blockRuntimeId" : 302 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3618 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3619 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3620 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3621 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3622 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3623 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3624 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3625 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3626 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3627 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3628 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5454 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5455 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5458 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5459 + }, + { + "id" : "minecraft:wither_rose", + "blockRuntimeId" : 6167 + }, + { + "id" : "minecraft:white_dye" + }, + { + "id" : "minecraft:light_gray_dye" + }, + { + "id" : "minecraft:gray_dye" + }, + { + "id" : "minecraft:black_dye" + }, + { + "id" : "minecraft:brown_dye" + }, + { + "id" : "minecraft:red_dye" + }, + { + "id" : "minecraft:orange_dye" + }, + { + "id" : "minecraft:yellow_dye" + }, + { + "id" : "minecraft:lime_dye" + }, + { + "id" : "minecraft:green_dye" + }, + { + "id" : "minecraft:cyan_dye" + }, + { + "id" : "minecraft:light_blue_dye" + }, + { + "id" : "minecraft:blue_dye" + }, + { + "id" : "minecraft:purple_dye" + }, + { + "id" : "minecraft:magenta_dye" + }, + { + "id" : "minecraft:pink_dye" + }, + { + "id" : "minecraft:ink_sac" + }, + { + "id" : "minecraft:glow_ink_sac" + }, + { + "id" : "minecraft:cocoa_beans" + }, + { + "id" : "minecraft:lapis_lazuli" + }, + { + "id" : "minecraft:bone_meal" + }, + { + "id" : "minecraft:vine", + "blockRuntimeId" : 896 + }, + { + "id" : "minecraft:weeping_vines", + "blockRuntimeId" : 5481 + }, + { + "id" : "minecraft:twisting_vines", + "blockRuntimeId" : 5693 + }, + { + "id" : "minecraft:waterlily", + "blockRuntimeId" : 1160 + }, + { + "id" : "minecraft:deadbush", + "blockRuntimeId" : 4679 + }, + { + "id" : "minecraft:bamboo", + "blockRuntimeId" : 3686 + }, + { + "id" : "minecraft:snow", + "blockRuntimeId" : 4196 + }, + { + "id" : "minecraft:ice", + "blockRuntimeId" : 6691 + }, + { + "id" : "minecraft:packed_ice", + "blockRuntimeId" : 282 + }, + { + "id" : "minecraft:blue_ice", + "blockRuntimeId" : 7029 + }, + { + "id" : "minecraft:snow_layer", + "blockRuntimeId" : 155 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 7418 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 895 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 286 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 6540 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 5399 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 205 + }, + { + "id" : "minecraft:mangrove_roots", + "blockRuntimeId" : 6177 + }, + { + "id" : "minecraft:muddy_mangrove_roots", + "blockRuntimeId" : 345 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 5982 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 4322 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 7314 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 6890 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 5479 + }, + { + "id" : "minecraft:glow_lichen", + "blockRuntimeId" : 5686 + }, + { + "id" : "minecraft:amethyst_block", + "blockRuntimeId" : 290 + }, + { + "id" : "minecraft:budding_amethyst", + "blockRuntimeId" : 7004 + }, + { + "id" : "minecraft:amethyst_cluster", + "blockRuntimeId" : 7812 + }, + { + "id" : "minecraft:large_amethyst_bud", + "blockRuntimeId" : 4730 + }, + { + "id" : "minecraft:medium_amethyst_bud", + "blockRuntimeId" : 4378 + }, + { + "id" : "minecraft:small_amethyst_bud", + "blockRuntimeId" : 304 + }, + { + "id" : "minecraft:tuff", + "blockRuntimeId" : 349 + }, + { + "id" : "minecraft:calcite", + "blockRuntimeId" : 215 + }, + { + "id" : "minecraft:chicken" + }, + { + "id" : "minecraft:porkchop" + }, + { + "id" : "minecraft:beef" + }, + { + "id" : "minecraft:mutton" + }, + { + "id" : "minecraft:rabbit" + }, + { + "id" : "minecraft:cod" + }, + { + "id" : "minecraft:salmon" + }, + { + "id" : "minecraft:tropical_fish" + }, + { + "id" : "minecraft:pufferfish" + }, + { + "id" : "minecraft:brown_mushroom", + "blockRuntimeId" : 3548 + }, + { + "id" : "minecraft:red_mushroom", + "blockRuntimeId" : 4587 + }, + { + "id" : "minecraft:crimson_fungus", + "blockRuntimeId" : 7755 + }, + { + "id" : "minecraft:warped_fungus", + "blockRuntimeId" : 287 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7364 + }, + { + "id" : "minecraft:red_mushroom_block", + "blockRuntimeId" : 3613 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7365 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7350 + }, + { + "id" : "minecraft:egg" + }, + { + "id" : "minecraft:sugar_cane" + }, + { + "id" : "minecraft:sugar" + }, + { + "id" : "minecraft:rotten_flesh" + }, + { + "id" : "minecraft:bone" + }, + { + "id" : "minecraft:web", + "blockRuntimeId" : 6715 + }, + { + "id" : "minecraft:spider_eye" + }, + { + "id" : "minecraft:mob_spawner", + "blockRuntimeId" : 403 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4146 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4147 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4148 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4149 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4150 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4151 + }, + { + "id" : "minecraft:infested_deepslate", + "blockRuntimeId" : 4643 + }, + { + "id" : "minecraft:dragon_egg", + "blockRuntimeId" : 7273 + }, + { + "id" : "minecraft:turtle_egg", + "blockRuntimeId" : 7999 + }, + { + "id" : "minecraft:frog_spawn", + "blockRuntimeId" : 4401 + }, + { + "id" : "minecraft:pearlescent_froglight", + "blockRuntimeId" : 6437 + }, + { + "id" : "minecraft:verdant_froglight", + "blockRuntimeId" : 6483 + }, + { + "id" : "minecraft:ochre_froglight", + "blockRuntimeId" : 3512 + }, + { + "id" : "minecraft:chicken_spawn_egg" + }, + { + "id" : "minecraft:bee_spawn_egg" + }, + { + "id" : "minecraft:cow_spawn_egg" + }, + { + "id" : "minecraft:pig_spawn_egg" + }, + { + "id" : "minecraft:sheep_spawn_egg" + }, + { + "id" : "minecraft:wolf_spawn_egg" + }, + { + "id" : "minecraft:polar_bear_spawn_egg" + }, + { + "id" : "minecraft:ocelot_spawn_egg" + }, + { + "id" : "minecraft:cat_spawn_egg" + }, + { + "id" : "minecraft:mooshroom_spawn_egg" + }, + { + "id" : "minecraft:bat_spawn_egg" + }, + { + "id" : "minecraft:parrot_spawn_egg" + }, + { + "id" : "minecraft:rabbit_spawn_egg" + }, + { + "id" : "minecraft:llama_spawn_egg" + }, + { + "id" : "minecraft:horse_spawn_egg" + }, + { + "id" : "minecraft:donkey_spawn_egg" + }, + { + "id" : "minecraft:mule_spawn_egg" + }, + { + "id" : "minecraft:skeleton_horse_spawn_egg" + }, + { + "id" : "minecraft:zombie_horse_spawn_egg" + }, + { + "id" : "minecraft:tropical_fish_spawn_egg" + }, + { + "id" : "minecraft:cod_spawn_egg" + }, + { + "id" : "minecraft:pufferfish_spawn_egg" + }, + { + "id" : "minecraft:salmon_spawn_egg" + }, + { + "id" : "minecraft:dolphin_spawn_egg" + }, + { + "id" : "minecraft:turtle_spawn_egg" + }, + { + "id" : "minecraft:panda_spawn_egg" + }, + { + "id" : "minecraft:fox_spawn_egg" + }, + { + "id" : "minecraft:creeper_spawn_egg" + }, + { + "id" : "minecraft:enderman_spawn_egg" + }, + { + "id" : "minecraft:silverfish_spawn_egg" + }, + { + "id" : "minecraft:skeleton_spawn_egg" + }, + { + "id" : "minecraft:wither_skeleton_spawn_egg" + }, + { + "id" : "minecraft:stray_spawn_egg" + }, + { + "id" : "minecraft:slime_spawn_egg" + }, + { + "id" : "minecraft:spider_spawn_egg" + }, + { + "id" : "minecraft:zombie_spawn_egg" + }, + { + "id" : "minecraft:zombie_pigman_spawn_egg" + }, + { + "id" : "minecraft:husk_spawn_egg" + }, + { + "id" : "minecraft:drowned_spawn_egg" + }, + { + "id" : "minecraft:squid_spawn_egg" + }, + { + "id" : "minecraft:glow_squid_spawn_egg" + }, + { + "id" : "minecraft:cave_spider_spawn_egg" + }, + { + "id" : "minecraft:witch_spawn_egg" + }, + { + "id" : "minecraft:guardian_spawn_egg" + }, + { + "id" : "minecraft:elder_guardian_spawn_egg" + }, + { + "id" : "minecraft:endermite_spawn_egg" + }, + { + "id" : "minecraft:magma_cube_spawn_egg" + }, + { + "id" : "minecraft:strider_spawn_egg" + }, + { + "id" : "minecraft:hoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_spawn_egg" + }, + { + "id" : "minecraft:zoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_brute_spawn_egg" + }, + { + "id" : "minecraft:goat_spawn_egg" + }, + { + "id" : "minecraft:axolotl_spawn_egg" + }, + { + "id" : "minecraft:warden_spawn_egg" + }, + { + "id" : "minecraft:allay_spawn_egg" + }, + { + "id" : "minecraft:frog_spawn_egg" + }, + { + "id" : "minecraft:tadpole_spawn_egg" + }, + { + "id" : "minecraft:trader_llama_spawn_egg" + }, + { + "id" : "minecraft:ghast_spawn_egg" + }, + { + "id" : "minecraft:blaze_spawn_egg" + }, + { + "id" : "minecraft:shulker_spawn_egg" + }, + { + "id" : "minecraft:vindicator_spawn_egg" + }, + { + "id" : "minecraft:evoker_spawn_egg" + }, + { + "id" : "minecraft:vex_spawn_egg" + }, + { + "id" : "minecraft:villager_spawn_egg" + }, + { + "id" : "minecraft:wandering_trader_spawn_egg" + }, + { + "id" : "minecraft:zombie_villager_spawn_egg" + }, + { + "id" : "minecraft:phantom_spawn_egg" + }, + { + "id" : "minecraft:pillager_spawn_egg" + }, + { + "id" : "minecraft:ravager_spawn_egg" + }, + { + "id" : "minecraft:obsidian", + "blockRuntimeId" : 430 + }, + { + "id" : "minecraft:crying_obsidian", + "blockRuntimeId" : 6724 + }, + { + "id" : "minecraft:bedrock", + "blockRuntimeId" : 7019 + }, + { + "id" : "minecraft:soul_sand", + "blockRuntimeId" : 5833 + }, + { + "id" : "minecraft:netherrack", + "blockRuntimeId" : 7039 + }, + { + "id" : "minecraft:magma", + "blockRuntimeId" : 8011 + }, + { + "id" : "minecraft:nether_wart" + }, + { + "id" : "minecraft:end_stone", + "blockRuntimeId" : 3838 + }, + { + "id" : "minecraft:chorus_flower", + "blockRuntimeId" : 4532 + }, + { + "id" : "minecraft:chorus_plant", + "blockRuntimeId" : 5507 + }, + { + "id" : "minecraft:chorus_fruit" + }, + { + "id" : "minecraft:popped_chorus_fruit" + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 631 + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 632 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5239 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5240 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5241 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5242 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5243 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5244 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5245 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5246 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5247 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5248 + }, + { + "id" : "minecraft:sculk", + "blockRuntimeId" : 7038 + }, + { + "id" : "minecraft:sculk_vein", + "blockRuntimeId" : 7134 + }, + { + "id" : "minecraft:sculk_catalyst", + "blockRuntimeId" : 3615 + }, + { + "id" : "minecraft:sculk_shrieker", + "blockRuntimeId" : 219 + }, + { + "id" : "minecraft:sculk_sensor", + "blockRuntimeId" : 4391 + }, + { + "id" : "minecraft:reinforced_deepslate", + "blockRuntimeId" : 5834 + }, + { + "id" : "minecraft:leather_helmet" + }, + { + "id" : "minecraft:chainmail_helmet" + }, + { + "id" : "minecraft:iron_helmet" + }, + { + "id" : "minecraft:golden_helmet" + }, + { + "id" : "minecraft:diamond_helmet" + }, + { + "id" : "minecraft:netherite_helmet" + }, + { + "id" : "minecraft:leather_chestplate" + }, + { + "id" : "minecraft:chainmail_chestplate" + }, + { + "id" : "minecraft:iron_chestplate" + }, + { + "id" : "minecraft:golden_chestplate" + }, + { + "id" : "minecraft:diamond_chestplate" + }, + { + "id" : "minecraft:netherite_chestplate" + }, + { + "id" : "minecraft:leather_leggings" + }, + { + "id" : "minecraft:chainmail_leggings" + }, + { + "id" : "minecraft:iron_leggings" + }, + { + "id" : "minecraft:golden_leggings" + }, + { + "id" : "minecraft:diamond_leggings" + }, + { + "id" : "minecraft:netherite_leggings" + }, + { + "id" : "minecraft:leather_boots" + }, + { + "id" : "minecraft:chainmail_boots" + }, + { + "id" : "minecraft:iron_boots" + }, + { + "id" : "minecraft:golden_boots" + }, + { + "id" : "minecraft:diamond_boots" + }, + { + "id" : "minecraft:netherite_boots" + }, + { + "id" : "minecraft:wooden_sword" + }, + { + "id" : "minecraft:stone_sword" + }, + { + "id" : "minecraft:iron_sword" + }, + { + "id" : "minecraft:golden_sword" + }, + { + "id" : "minecraft:diamond_sword" + }, + { + "id" : "minecraft:netherite_sword" + }, + { + "id" : "minecraft:wooden_axe" + }, + { + "id" : "minecraft:stone_axe" + }, + { + "id" : "minecraft:iron_axe" + }, + { + "id" : "minecraft:golden_axe" + }, + { + "id" : "minecraft:diamond_axe" + }, + { + "id" : "minecraft:netherite_axe" + }, + { + "id" : "minecraft:wooden_pickaxe" + }, + { + "id" : "minecraft:stone_pickaxe" + }, + { + "id" : "minecraft:iron_pickaxe" + }, + { + "id" : "minecraft:golden_pickaxe" + }, + { + "id" : "minecraft:diamond_pickaxe" + }, + { + "id" : "minecraft:netherite_pickaxe" + }, + { + "id" : "minecraft:wooden_shovel" + }, + { + "id" : "minecraft:stone_shovel" + }, + { + "id" : "minecraft:iron_shovel" + }, + { + "id" : "minecraft:golden_shovel" + }, + { + "id" : "minecraft:diamond_shovel" + }, + { + "id" : "minecraft:netherite_shovel" + }, + { + "id" : "minecraft:wooden_hoe" + }, + { + "id" : "minecraft:stone_hoe" + }, + { + "id" : "minecraft:iron_hoe" + }, + { + "id" : "minecraft:golden_hoe" + }, + { + "id" : "minecraft:diamond_hoe" + }, + { + "id" : "minecraft:netherite_hoe" + }, + { + "id" : "minecraft:bow" + }, + { + "id" : "minecraft:crossbow" + }, + { + "id" : "minecraft:arrow" + }, + { + "id" : "minecraft:arrow", + "damage" : 6 + }, + { + "id" : "minecraft:arrow", + "damage" : 7 + }, + { + "id" : "minecraft:arrow", + "damage" : 8 + }, + { + "id" : "minecraft:arrow", + "damage" : 9 + }, + { + "id" : "minecraft:arrow", + "damage" : 10 + }, + { + "id" : "minecraft:arrow", + "damage" : 11 + }, + { + "id" : "minecraft:arrow", + "damage" : 12 + }, + { + "id" : "minecraft:arrow", + "damage" : 13 + }, + { + "id" : "minecraft:arrow", + "damage" : 14 + }, + { + "id" : "minecraft:arrow", + "damage" : 15 + }, + { + "id" : "minecraft:arrow", + "damage" : 16 + }, + { + "id" : "minecraft:arrow", + "damage" : 17 + }, + { + "id" : "minecraft:arrow", + "damage" : 18 + }, + { + "id" : "minecraft:arrow", + "damage" : 19 + }, + { + "id" : "minecraft:arrow", + "damage" : 20 + }, + { + "id" : "minecraft:arrow", + "damage" : 21 + }, + { + "id" : "minecraft:arrow", + "damage" : 22 + }, + { + "id" : "minecraft:arrow", + "damage" : 23 + }, + { + "id" : "minecraft:arrow", + "damage" : 24 + }, + { + "id" : "minecraft:arrow", + "damage" : 25 + }, + { + "id" : "minecraft:arrow", + "damage" : 26 + }, + { + "id" : "minecraft:arrow", + "damage" : 27 + }, + { + "id" : "minecraft:arrow", + "damage" : 28 + }, + { + "id" : "minecraft:arrow", + "damage" : 29 + }, + { + "id" : "minecraft:arrow", + "damage" : 30 + }, + { + "id" : "minecraft:arrow", + "damage" : 31 + }, + { + "id" : "minecraft:arrow", + "damage" : 32 + }, + { + "id" : "minecraft:arrow", + "damage" : 33 + }, + { + "id" : "minecraft:arrow", + "damage" : 34 + }, + { + "id" : "minecraft:arrow", + "damage" : 35 + }, + { + "id" : "minecraft:arrow", + "damage" : 36 + }, + { + "id" : "minecraft:arrow", + "damage" : 37 + }, + { + "id" : "minecraft:arrow", + "damage" : 38 + }, + { + "id" : "minecraft:arrow", + "damage" : 39 + }, + { + "id" : "minecraft:arrow", + "damage" : 40 + }, + { + "id" : "minecraft:arrow", + "damage" : 41 + }, + { + "id" : "minecraft:arrow", + "damage" : 42 + }, + { + "id" : "minecraft:arrow", + "damage" : 43 + }, + { + "id" : "minecraft:shield" + }, + { + "id" : "minecraft:cooked_chicken" + }, + { + "id" : "minecraft:cooked_porkchop" + }, + { + "id" : "minecraft:cooked_beef" + }, + { + "id" : "minecraft:cooked_mutton" + }, + { + "id" : "minecraft:cooked_rabbit" + }, + { + "id" : "minecraft:cooked_cod" + }, + { + "id" : "minecraft:cooked_salmon" + }, + { + "id" : "minecraft:bread" + }, + { + "id" : "minecraft:mushroom_stew" + }, + { + "id" : "minecraft:beetroot_soup" + }, + { + "id" : "minecraft:rabbit_stew" + }, + { + "id" : "minecraft:baked_potato" + }, + { + "id" : "minecraft:cookie" + }, + { + "id" : "minecraft:pumpkin_pie" + }, + { + "id" : "minecraft:cake" + }, + { + "id" : "minecraft:dried_kelp" + }, + { + "id" : "minecraft:fishing_rod" + }, + { + "id" : "minecraft:carrot_on_a_stick" + }, + { + "id" : "minecraft:warped_fungus_on_a_stick" + }, + { + "id" : "minecraft:snowball" + }, + { + "id" : "minecraft:shears" + }, + { + "id" : "minecraft:flint_and_steel" + }, + { + "id" : "minecraft:lead" + }, + { + "id" : "minecraft:clock" + }, + { + "id" : "minecraft:compass" + }, + { + "id" : "minecraft:recovery_compass" + }, + { + "id" : "minecraft:goat_horn" + }, + { + "id" : "minecraft:goat_horn", + "damage" : 1 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 2 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 3 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 4 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 5 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 6 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 7 + }, + { + "id" : "minecraft:empty_map" + }, + { + "id" : "minecraft:empty_map", + "damage" : 2 + }, + { + "id" : "minecraft:saddle" + }, + { + "id" : "minecraft:leather_horse_armor" + }, + { + "id" : "minecraft:iron_horse_armor" + }, + { + "id" : "minecraft:golden_horse_armor" + }, + { + "id" : "minecraft:diamond_horse_armor" + }, + { + "id" : "minecraft:trident" + }, + { + "id" : "minecraft:turtle_helmet" + }, + { + "id" : "minecraft:elytra" + }, + { + "id" : "minecraft:totem_of_undying" + }, + { + "id" : "minecraft:glass_bottle" + }, + { + "id" : "minecraft:experience_bottle" + }, + { + "id" : "minecraft:potion" + }, + { + "id" : "minecraft:potion", + "damage" : 1 + }, + { + "id" : "minecraft:potion", + "damage" : 2 + }, + { + "id" : "minecraft:potion", + "damage" : 3 + }, + { + "id" : "minecraft:potion", + "damage" : 4 + }, + { + "id" : "minecraft:potion", + "damage" : 5 + }, + { + "id" : "minecraft:potion", + "damage" : 6 + }, + { + "id" : "minecraft:potion", + "damage" : 7 + }, + { + "id" : "minecraft:potion", + "damage" : 8 + }, + { + "id" : "minecraft:potion", + "damage" : 9 + }, + { + "id" : "minecraft:potion", + "damage" : 10 + }, + { + "id" : "minecraft:potion", + "damage" : 11 + }, + { + "id" : "minecraft:potion", + "damage" : 12 + }, + { + "id" : "minecraft:potion", + "damage" : 13 + }, + { + "id" : "minecraft:potion", + "damage" : 14 + }, + { + "id" : "minecraft:potion", + "damage" : 15 + }, + { + "id" : "minecraft:potion", + "damage" : 16 + }, + { + "id" : "minecraft:potion", + "damage" : 17 + }, + { + "id" : "minecraft:potion", + "damage" : 18 + }, + { + "id" : "minecraft:potion", + "damage" : 19 + }, + { + "id" : "minecraft:potion", + "damage" : 20 + }, + { + "id" : "minecraft:potion", + "damage" : 21 + }, + { + "id" : "minecraft:potion", + "damage" : 22 + }, + { + "id" : "minecraft:potion", + "damage" : 23 + }, + { + "id" : "minecraft:potion", + "damage" : 24 + }, + { + "id" : "minecraft:potion", + "damage" : 25 + }, + { + "id" : "minecraft:potion", + "damage" : 26 + }, + { + "id" : "minecraft:potion", + "damage" : 27 + }, + { + "id" : "minecraft:potion", + "damage" : 28 + }, + { + "id" : "minecraft:potion", + "damage" : 29 + }, + { + "id" : "minecraft:potion", + "damage" : 30 + }, + { + "id" : "minecraft:potion", + "damage" : 31 + }, + { + "id" : "minecraft:potion", + "damage" : 32 + }, + { + "id" : "minecraft:potion", + "damage" : 33 + }, + { + "id" : "minecraft:potion", + "damage" : 34 + }, + { + "id" : "minecraft:potion", + "damage" : 35 + }, + { + "id" : "minecraft:potion", + "damage" : 36 + }, + { + "id" : "minecraft:potion", + "damage" : 37 + }, + { + "id" : "minecraft:potion", + "damage" : 38 + }, + { + "id" : "minecraft:potion", + "damage" : 39 + }, + { + "id" : "minecraft:potion", + "damage" : 40 + }, + { + "id" : "minecraft:potion", + "damage" : 41 + }, + { + "id" : "minecraft:potion", + "damage" : 42 + }, + { + "id" : "minecraft:splash_potion" + }, + { + "id" : "minecraft:splash_potion", + "damage" : 1 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 2 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 3 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 4 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 5 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 6 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 7 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 8 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 9 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 10 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 11 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 12 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 13 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 14 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 15 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 16 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 17 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 18 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 19 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 20 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 21 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 22 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 23 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 24 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 25 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 26 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 27 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 28 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 29 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 30 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 31 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 32 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 33 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 34 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 35 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 36 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 37 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 38 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 39 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 40 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 41 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 42 + }, + { + "id" : "minecraft:lingering_potion" + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 1 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 2 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 3 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 4 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 5 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 6 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 7 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 8 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 9 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 10 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 11 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 12 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 13 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 14 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 15 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 16 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 17 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 18 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 19 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 20 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 21 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 22 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 23 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 24 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 25 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 26 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 27 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 28 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 29 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 30 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 31 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 32 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 33 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 34 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 35 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 36 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 37 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 38 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 39 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 40 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 41 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 42 + }, + { + "id" : "minecraft:spyglass" + }, + { + "id" : "minecraft:stick" + }, + { + "id" : "minecraft:bed" + }, + { + "id" : "minecraft:bed", + "damage" : 8 + }, + { + "id" : "minecraft:bed", + "damage" : 7 + }, + { + "id" : "minecraft:bed", + "damage" : 15 + }, + { + "id" : "minecraft:bed", + "damage" : 12 + }, + { + "id" : "minecraft:bed", + "damage" : 14 + }, + { + "id" : "minecraft:bed", + "damage" : 1 + }, + { + "id" : "minecraft:bed", + "damage" : 4 + }, + { + "id" : "minecraft:bed", + "damage" : 5 + }, + { + "id" : "minecraft:bed", + "damage" : 13 + }, + { + "id" : "minecraft:bed", + "damage" : 9 + }, + { + "id" : "minecraft:bed", + "damage" : 3 + }, + { + "id" : "minecraft:bed", + "damage" : 11 + }, + { + "id" : "minecraft:bed", + "damage" : 10 + }, + { + "id" : "minecraft:bed", + "damage" : 2 + }, + { + "id" : "minecraft:bed", + "damage" : 6 + }, + { + "id" : "minecraft:torch", + "blockRuntimeId" : 726 + }, + { + "id" : "minecraft:soul_torch", + "blockRuntimeId" : 4646 + }, + { + "id" : "minecraft:sea_pickle", + "blockRuntimeId" : 5857 + }, + { + "id" : "minecraft:lantern", + "blockRuntimeId" : 7076 + }, + { + "id" : "minecraft:soul_lantern", + "blockRuntimeId" : 5751 + }, + { + "id" : "minecraft:candle", + "blockRuntimeId" : 7405 + }, + { + "id" : "minecraft:white_candle", + "blockRuntimeId" : 5302 + }, + { + "id" : "minecraft:orange_candle", + "blockRuntimeId" : 364 + }, + { + "id" : "minecraft:magenta_candle", + "blockRuntimeId" : 420 + }, + { + "id" : "minecraft:light_blue_candle", + "blockRuntimeId" : 4571 + }, + { + "id" : "minecraft:yellow_candle", + "blockRuntimeId" : 6194 + }, + { + "id" : "minecraft:lime_candle", + "blockRuntimeId" : 6370 + }, + { + "id" : "minecraft:pink_candle", + "blockRuntimeId" : 7372 + }, + { + "id" : "minecraft:gray_candle", + "blockRuntimeId" : 941 + }, + { + "id" : "minecraft:light_gray_candle", + "blockRuntimeId" : 6226 + }, + { + "id" : "minecraft:cyan_candle", + "blockRuntimeId" : 7728 + }, + { + "id" : "minecraft:purple_candle", + "blockRuntimeId" : 7040 + }, + { + "id" : "minecraft:blue_candle" + }, + { + "id" : "minecraft:brown_candle", + "blockRuntimeId" : 5877 + }, + { + "id" : "minecraft:green_candle", + "blockRuntimeId" : 688 + }, + { + "id" : "minecraft:red_candle", + "blockRuntimeId" : 4683 + }, + { + "id" : "minecraft:black_candle", + "blockRuntimeId" : 171 + }, + { + "id" : "minecraft:crafting_table", + "blockRuntimeId" : 5856 + }, + { + "id" : "minecraft:cartography_table", + "blockRuntimeId" : 8290 + }, + { + "id" : "minecraft:fletching_table", + "blockRuntimeId" : 5835 + }, + { + "id" : "minecraft:smithing_table", + "blockRuntimeId" : 3728 + }, + { + "id" : "minecraft:beehive", + "blockRuntimeId" : 6110 + }, + { + "id" : "minecraft:campfire" + }, + { + "id" : "minecraft:soul_campfire" + }, + { + "id" : "minecraft:furnace", + "blockRuntimeId" : 7804 + }, + { + "id" : "minecraft:blast_furnace", + "blockRuntimeId" : 7569 + }, + { + "id" : "minecraft:smoker", + "blockRuntimeId" : 649 + }, + { + "id" : "minecraft:respawn_anchor", + "blockRuntimeId" : 683 + }, + { + "id" : "minecraft:brewing_stand" + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6636 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6640 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6644 + }, + { + "id" : "minecraft:grindstone", + "blockRuntimeId" : 8041 + }, + { + "id" : "minecraft:enchanting_table", + "blockRuntimeId" : 6725 + }, + { + "id" : "minecraft:bookshelf", + "blockRuntimeId" : 6673 + }, + { + "id" : "minecraft:lectern", + "blockRuntimeId" : 6942 + }, + { + "id" : "minecraft:cauldron" + }, + { + "id" : "minecraft:composter", + "blockRuntimeId" : 5417 + }, + { + "id" : "minecraft:chest", + "blockRuntimeId" : 7117 + }, + { + "id" : "minecraft:trapped_chest", + "blockRuntimeId" : 5585 + }, + { + "id" : "minecraft:ender_chest", + "blockRuntimeId" : 4371 + }, + { + "id" : "minecraft:barrel", + "blockRuntimeId" : 4520 + }, + { + "id" : "minecraft:undyed_shulker_box", + "blockRuntimeId" : 3683 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5318 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5326 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5325 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5333 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5330 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5332 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5319 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5322 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5323 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5331 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5327 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5321 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5329 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5328 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5320 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5324 + }, + { + "id" : "minecraft:armor_stand" + }, + { + "id" : "minecraft:noteblock", + "blockRuntimeId" : 348 + }, + { + "id" : "minecraft:jukebox", + "blockRuntimeId" : 4876 + }, + { + "id" : "minecraft:music_disc_13" + }, + { + "id" : "minecraft:music_disc_cat" + }, + { + "id" : "minecraft:music_disc_blocks" + }, + { + "id" : "minecraft:music_disc_chirp" + }, + { + "id" : "minecraft:music_disc_far" + }, + { + "id" : "minecraft:music_disc_mall" + }, + { + "id" : "minecraft:music_disc_mellohi" + }, + { + "id" : "minecraft:music_disc_stal" + }, + { + "id" : "minecraft:music_disc_strad" + }, + { + "id" : "minecraft:music_disc_ward" + }, + { + "id" : "minecraft:music_disc_11" + }, + { + "id" : "minecraft:music_disc_wait" + }, + { + "id" : "minecraft:music_disc_otherside" + }, + { + "id" : "minecraft:music_disc_5" + }, + { + "id" : "minecraft:music_disc_pigstep" + }, + { + "id" : "minecraft:disc_fragment_5" + }, + { + "id" : "minecraft:glowstone_dust" + }, + { + "id" : "minecraft:glowstone", + "blockRuntimeId" : 3887 + }, + { + "id" : "minecraft:redstone_lamp", + "blockRuntimeId" : 251 + }, + { + "id" : "minecraft:sea_lantern", + "blockRuntimeId" : 7548 + }, + { + "id" : "minecraft:oak_sign" + }, + { + "id" : "minecraft:spruce_sign" + }, + { + "id" : "minecraft:birch_sign" + }, + { + "id" : "minecraft:jungle_sign" + }, + { + "id" : "minecraft:acacia_sign" + }, + { + "id" : "minecraft:dark_oak_sign" + }, + { + "id" : "minecraft:mangrove_sign" + }, + { + "id" : "minecraft:crimson_sign" + }, + { + "id" : "minecraft:warped_sign" + }, + { + "id" : "minecraft:painting" + }, + { + "id" : "minecraft:frame" + }, + { + "id" : "minecraft:glow_frame" + }, + { + "id" : "minecraft:honey_bottle" + }, + { + "id" : "minecraft:flower_pot" + }, + { + "id" : "minecraft:bowl" + }, + { + "id" : "minecraft:bucket" + }, + { + "id" : "minecraft:milk_bucket" + }, + { + "id" : "minecraft:water_bucket" + }, + { + "id" : "minecraft:lava_bucket" + }, + { + "id" : "minecraft:cod_bucket" + }, + { + "id" : "minecraft:salmon_bucket" + }, + { + "id" : "minecraft:tropical_fish_bucket" + }, + { + "id" : "minecraft:pufferfish_bucket" + }, + { + "id" : "minecraft:powder_snow_bucket" + }, + { + "id" : "minecraft:axolotl_bucket" + }, + { + "id" : "minecraft:tadpole_bucket" + }, + { + "id" : "minecraft:skull", + "damage" : 3 + }, + { + "id" : "minecraft:skull", + "damage" : 2 + }, + { + "id" : "minecraft:skull", + "damage" : 4 + }, + { + "id" : "minecraft:skull", + "damage" : 5 + }, + { + "id" : "minecraft:skull" + }, + { + "id" : "minecraft:skull", + "damage" : 1 + }, + { + "id" : "minecraft:beacon", + "blockRuntimeId" : 145 + }, + { + "id" : "minecraft:bell", + "blockRuntimeId" : 6910 + }, + { + "id" : "minecraft:conduit", + "blockRuntimeId" : 4234 + }, + { + "id" : "minecraft:stonecutter_block", + "blockRuntimeId" : 7576 + }, + { + "id" : "minecraft:end_portal_frame", + "blockRuntimeId" : 6079 + }, + { + "id" : "minecraft:coal" + }, + { + "id" : "minecraft:charcoal" + }, + { + "id" : "minecraft:diamond" + }, + { + "id" : "minecraft:iron_nugget" + }, + { + "id" : "minecraft:raw_iron" + }, + { + "id" : "minecraft:raw_gold" + }, + { + "id" : "minecraft:raw_copper" + }, + { + "id" : "minecraft:copper_ingot" + }, + { + "id" : "minecraft:iron_ingot" + }, + { + "id" : "minecraft:netherite_scrap" + }, + { + "id" : "minecraft:netherite_ingot" + }, + { + "id" : "minecraft:gold_nugget" + }, + { + "id" : "minecraft:gold_ingot" + }, + { + "id" : "minecraft:emerald" + }, + { + "id" : "minecraft:quartz" + }, + { + "id" : "minecraft:clay_ball" + }, + { + "id" : "minecraft:brick" + }, + { + "id" : "minecraft:netherbrick" + }, + { + "id" : "minecraft:prismarine_shard" + }, + { + "id" : "minecraft:amethyst_shard" + }, + { + "id" : "minecraft:prismarine_crystals" + }, + { + "id" : "minecraft:nautilus_shell" + }, + { + "id" : "minecraft:heart_of_the_sea" + }, + { + "id" : "minecraft:scute" + }, + { + "id" : "minecraft:phantom_membrane" + }, + { + "id" : "minecraft:string" + }, + { + "id" : "minecraft:feather" + }, + { + "id" : "minecraft:flint" + }, + { + "id" : "minecraft:gunpowder" + }, + { + "id" : "minecraft:leather" + }, + { + "id" : "minecraft:rabbit_hide" + }, + { + "id" : "minecraft:rabbit_foot" + }, + { + "id" : "minecraft:fire_charge" + }, + { + "id" : "minecraft:blaze_rod" + }, + { + "id" : "minecraft:blaze_powder" + }, + { + "id" : "minecraft:magma_cream" + }, + { + "id" : "minecraft:fermented_spider_eye" + }, + { + "id" : "minecraft:echo_shard" + }, + { + "id" : "minecraft:dragon_breath" + }, + { + "id" : "minecraft:shulker_shell" + }, + { + "id" : "minecraft:ghast_tear" + }, + { + "id" : "minecraft:slime_ball" + }, + { + "id" : "minecraft:ender_pearl" + }, + { + "id" : "minecraft:ender_eye" + }, + { + "id" : "minecraft:nether_star" + }, + { + "id" : "minecraft:end_rod", + "blockRuntimeId" : 5893 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 1178 + }, + { + "id" : "minecraft:end_crystal" + }, + { + "id" : "minecraft:paper" + }, + { + "id" : "minecraft:book" + }, + { + "id" : "minecraft:writable_book" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:oak_boat" + }, + { + "id" : "minecraft:spruce_boat" + }, + { + "id" : "minecraft:birch_boat" + }, + { + "id" : "minecraft:jungle_boat" + }, + { + "id" : "minecraft:acacia_boat" + }, + { + "id" : "minecraft:dark_oak_boat" + }, + { + "id" : "minecraft:mangrove_boat" + }, + { + "id" : "minecraft:oak_chest_boat" + }, + { + "id" : "minecraft:spruce_chest_boat" + }, + { + "id" : "minecraft:birch_chest_boat" + }, + { + "id" : "minecraft:jungle_chest_boat" + }, + { + "id" : "minecraft:acacia_chest_boat" + }, + { + "id" : "minecraft:dark_oak_chest_boat" + }, + { + "id" : "minecraft:mangrove_chest_boat" + }, + { + "id" : "minecraft:rail", + "blockRuntimeId" : 3922 + }, + { + "id" : "minecraft:golden_rail", + "blockRuntimeId" : 5334 + }, + { + "id" : "minecraft:detector_rail", + "blockRuntimeId" : 4134 + }, + { + "id" : "minecraft:activator_rail", + "blockRuntimeId" : 309 + }, + { + "id" : "minecraft:minecart" + }, + { + "id" : "minecraft:chest_minecart" + }, + { + "id" : "minecraft:hopper_minecart" + }, + { + "id" : "minecraft:tnt_minecart" + }, + { + "id" : "minecraft:redstone" + }, + { + "id" : "minecraft:redstone_block", + "blockRuntimeId" : 3778 + }, + { + "id" : "minecraft:redstone_torch", + "blockRuntimeId" : 3527 + }, + { + "id" : "minecraft:lever", + "blockRuntimeId" : 6516 + }, + { + "id" : "minecraft:wooden_button", + "blockRuntimeId" : 6393 + }, + { + "id" : "minecraft:spruce_button", + "blockRuntimeId" : 4323 + }, + { + "id" : "minecraft:birch_button", + "blockRuntimeId" : 7768 + }, + { + "id" : "minecraft:jungle_button", + "blockRuntimeId" : 116 + }, + { + "id" : "minecraft:acacia_button", + "blockRuntimeId" : 7233 + }, + { + "id" : "minecraft:dark_oak_button", + "blockRuntimeId" : 93 + }, + { + "id" : "minecraft:mangrove_button", + "blockRuntimeId" : 7064 + }, + { + "id" : "minecraft:stone_button", + "blockRuntimeId" : 598 + }, + { + "id" : "minecraft:crimson_button", + "blockRuntimeId" : 4434 + }, + { + "id" : "minecraft:warped_button", + "blockRuntimeId" : 7252 + }, + { + "id" : "minecraft:polished_blackstone_button", + "blockRuntimeId" : 7792 + }, + { + "id" : "minecraft:tripwire_hook", + "blockRuntimeId" : 5916 + }, + { + "id" : "minecraft:wooden_pressure_plate", + "blockRuntimeId" : 8065 + }, + { + "id" : "minecraft:spruce_pressure_plate", + "blockRuntimeId" : 3761 + }, + { + "id" : "minecraft:birch_pressure_plate", + "blockRuntimeId" : 3557 + }, + { + "id" : "minecraft:jungle_pressure_plate", + "blockRuntimeId" : 3637 + }, + { + "id" : "minecraft:acacia_pressure_plate", + "blockRuntimeId" : 5249 + }, + { + "id" : "minecraft:dark_oak_pressure_plate", + "blockRuntimeId" : 5958 + }, + { + "id" : "minecraft:mangrove_pressure_plate", + "blockRuntimeId" : 3871 + }, + { + "id" : "minecraft:crimson_pressure_plate", + "blockRuntimeId" : 8270 + }, + { + "id" : "minecraft:warped_pressure_plate", + "blockRuntimeId" : 256 + }, + { + "id" : "minecraft:stone_pressure_plate", + "blockRuntimeId" : 3888 + }, + { + "id" : "minecraft:light_weighted_pressure_plate", + "blockRuntimeId" : 3667 + }, + { + "id" : "minecraft:heavy_weighted_pressure_plate", + "blockRuntimeId" : 1162 + }, + { + "id" : "minecraft:polished_blackstone_pressure_plate", + "blockRuntimeId" : 6234 + }, + { + "id" : "minecraft:observer", + "blockRuntimeId" : 3515 + }, + { + "id" : "minecraft:daylight_detector", + "blockRuntimeId" : 4199 + }, + { + "id" : "minecraft:repeater" + }, + { + "id" : "minecraft:comparator" + }, + { + "id" : "minecraft:hopper" + }, + { + "id" : "minecraft:dropper", + "blockRuntimeId" : 7387 + }, + { + "id" : "minecraft:dispenser", + "blockRuntimeId" : 8015 + }, + { + "id" : "minecraft:piston", + "blockRuntimeId" : 924 + }, + { + "id" : "minecraft:sticky_piston", + "blockRuntimeId" : 4366 + }, + { + "id" : "minecraft:tnt", + "blockRuntimeId" : 6709 + }, + { + "id" : "minecraft:name_tag" + }, + { + "id" : "minecraft:loom", + "blockRuntimeId" : 3828 + }, + { + "id" : "minecraft:banner" + }, + { + "id" : "minecraft:banner", + "damage" : 8 + }, + { + "id" : "minecraft:banner", + "damage" : 7 + }, + { + "id" : "minecraft:banner", + "damage" : 15 + }, + { + "id" : "minecraft:banner", + "damage" : 12 + }, + { + "id" : "minecraft:banner", + "damage" : 14 + }, + { + "id" : "minecraft:banner", + "damage" : 1 + }, + { + "id" : "minecraft:banner", + "damage" : 4 + }, + { + "id" : "minecraft:banner", + "damage" : 5 + }, + { + "id" : "minecraft:banner", + "damage" : 13 + }, + { + "id" : "minecraft:banner", + "damage" : 9 + }, + { + "id" : "minecraft:banner", + "damage" : 3 + }, + { + "id" : "minecraft:banner", + "damage" : 11 + }, + { + "id" : "minecraft:banner", + "damage" : 10 + }, + { + "id" : "minecraft:banner", + "damage" : 2 + }, + { + "id" : "minecraft:banner", + "damage" : 6 + }, + { + "id" : "minecraft:banner", + "damage" : 15, + "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" + }, + { + "id" : "minecraft:creeper_banner_pattern" + }, + { + "id" : "minecraft:skull_banner_pattern" + }, + { + "id" : "minecraft:flower_banner_pattern" + }, + { + "id" : "minecraft:mojang_banner_pattern" + }, + { + "id" : "minecraft:field_masoned_banner_pattern" + }, + { + "id" : "minecraft:bordure_indented_banner_pattern" + }, + { + "id" : "minecraft:piglin_banner_pattern" + }, + { + "id" : "minecraft:globe_banner_pattern" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_star", + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 8, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 7, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 15, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 12, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 14, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 1, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 4, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 5, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 13, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 9, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 3, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 11, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 10, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 2, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 6, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : "minecraft:chain" + }, + { + "id" : "minecraft:target", + "blockRuntimeId" : 6392 + }, + { + "id" : "minecraft:lodestone_compass" + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/entity_identifiers.dat b/core/src/main/resources/bedrock/entity_identifiers.dat index b3e6fdb77103286cf60af2a14913f213fc1afd69..297d30537148f5cb80ecd63810b352f3e4c7ab03 100644 GIT binary patch delta 776 zcmX|9OK4M35H-K%b6@gazD-TiJPCr5Ce6P!Kbt14TBYj3h0rQRRD@Dd6t{}|ko2JV zfoxo8MFd^BQc4#tZLNFe$4L`m5OGtv*@Bi`;MHIaH-njFzra0j z8cvTBv%WEzo~85?4e9v{_+SxuqU9v~*^}W}sxN~wN?!kEeJ(wGDt=9G@_{r ztb|lt4~;O zIHTs;x`v;@Btu1;fT|Xx0I4=Hw8-!-!K<#SggZm;SVzlOpzZxe$0#sIzv+Z#cx&5X z`k;bq^r0rrP6hMKun;-HZNEYVE_)N{GfNE*+~b5*X~_q0-WkLV*CcM!#JVdDT?p7* zDXdW4nzM*o&Ki$wIJ5Yn94CAVQuLBiLR4w+y1t4s9*YXQbV=liV;bKbc|3BQ#zl&f zOLr8}wb#kLN$L}@+Z9}}C$MXqrP*cToMzshwL~=v-dYQ|Vm*eBWXp)PLh&fScLsP_ zGXssjX(ig*1iR3&^qrrxkkxWNSy;^6z>;Z{jY=A_ z(!lo1LwpY33@L<6_5W<=j4_-t4I^fZOBQ*%PkMMD)^res?`iEU#}>S^z!<>K2{ P`Tm}nqPSzM8}|GGV~6qnRs@$0l0jF|+1f=HA^Lr9(c$>+d*6GW_j#Y^#?IL3&!8(a{K_^k zG-q*`ZsVT8aN8_mm|o-$!=3CRoUDy07ehT`(do+OG0fOdb1hs;8^lrO_?wy5;G{&K z^wOvC*2D0_oEr3Vc%3caqAp=u=PZ1RhqrfARjPgD%nwp!)KhZ;eb>wI-YB8(((uqI zqHZh^qheUtO}6nx*U(K?2%1l>BA;Y9Ow{o#v4&ABgzJe4cA^YsqDJTEcn$ry!+Q+A z)Ev4&hV^(0lUSSAma!FUk*r3n3L)iBw9Ijru*FY1x`IhWNDJXzq=?PP8IEJ9L>7qq zNUw2h2bcA8$mbZQbsL|wChZH#=&*z-0hoQXgG-!{c#{WE;UXOXn&@I2+V?=KO}NIIq#^2VVO z3V{v<G25ya=1F G6Mq3QY4m~s diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json new file mode 100644 index 00000000000..00be1af06ec --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json @@ -0,0 +1,4530 @@ +[ + { + "name" : "minecraft:acacia_boat", + "id" : 379 + }, + { + "name" : "minecraft:acacia_button", + "id" : -140 + }, + { + "name" : "minecraft:acacia_chest_boat", + "id" : 642 + }, + { + "name" : "minecraft:acacia_door", + "id" : 556 + }, + { + "name" : "minecraft:acacia_fence_gate", + "id" : 187 + }, + { + "name" : "minecraft:acacia_pressure_plate", + "id" : -150 + }, + { + "name" : "minecraft:acacia_sign", + "id" : 579 + }, + { + "name" : "minecraft:acacia_stairs", + "id" : 163 + }, + { + "name" : "minecraft:acacia_standing_sign", + "id" : -190 + }, + { + "name" : "minecraft:acacia_trapdoor", + "id" : -145 + }, + { + "name" : "minecraft:acacia_wall_sign", + "id" : -191 + }, + { + "name" : "minecraft:activator_rail", + "id" : 126 + }, + { + "name" : "minecraft:agent_spawn_egg", + "id" : 487 + }, + { + "name" : "minecraft:air", + "id" : -158 + }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 631 + }, + { + "name" : "minecraft:allow", + "id" : 210 + }, + { + "name" : "minecraft:amethyst_block", + "id" : -327 + }, + { + "name" : "minecraft:amethyst_cluster", + "id" : -329 + }, + { + "name" : "minecraft:amethyst_shard", + "id" : 624 + }, + { + "name" : "minecraft:ancient_debris", + "id" : -271 + }, + { + "name" : "minecraft:andesite_stairs", + "id" : -171 + }, + { + "name" : "minecraft:anvil", + "id" : 145 + }, + { + "name" : "minecraft:apple", + "id" : 257 + }, + { + "name" : "minecraft:armor_stand", + "id" : 552 + }, + { + "name" : "minecraft:arrow", + "id" : 301 + }, + { + "name" : "minecraft:axolotl_bucket", + "id" : 369 + }, + { + "name" : "minecraft:axolotl_spawn_egg", + "id" : 500 + }, + { + "name" : "minecraft:azalea", + "id" : -337 + }, + { + "name" : "minecraft:azalea_leaves", + "id" : -324 + }, + { + "name" : "minecraft:azalea_leaves_flowered", + "id" : -325 + }, + { + "name" : "minecraft:baked_potato", + "id" : 281 + }, + { + "name" : "minecraft:balloon", + "id" : 598 + }, + { + "name" : "minecraft:bamboo", + "id" : -163 + }, + { + "name" : "minecraft:bamboo_sapling", + "id" : -164 + }, + { + "name" : "minecraft:banner", + "id" : 567 + }, + { + "name" : "minecraft:banner_pattern", + "id" : 651 + }, + { + "name" : "minecraft:barrel", + "id" : -203 + }, + { + "name" : "minecraft:barrier", + "id" : -161 + }, + { + "name" : "minecraft:basalt", + "id" : -234 + }, + { + "name" : "minecraft:bat_spawn_egg", + "id" : 453 + }, + { + "name" : "minecraft:beacon", + "id" : 138 + }, + { + "name" : "minecraft:bed", + "id" : 418 + }, + { + "name" : "minecraft:bedrock", + "id" : 7 + }, + { + "name" : "minecraft:bee_nest", + "id" : -218 + }, + { + "name" : "minecraft:bee_spawn_egg", + "id" : 494 + }, + { + "name" : "minecraft:beef", + "id" : 273 + }, + { + "name" : "minecraft:beehive", + "id" : -219 + }, + { + "name" : "minecraft:beetroot", + "id" : 285 + }, + { + "name" : "minecraft:beetroot_seeds", + "id" : 295 + }, + { + "name" : "minecraft:beetroot_soup", + "id" : 286 + }, + { + "name" : "minecraft:bell", + "id" : -206 + }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, + { + "name" : "minecraft:birch_boat", + "id" : 376 + }, + { + "name" : "minecraft:birch_button", + "id" : -141 + }, + { + "name" : "minecraft:birch_chest_boat", + "id" : 639 + }, + { + "name" : "minecraft:birch_door", + "id" : 554 + }, + { + "name" : "minecraft:birch_fence_gate", + "id" : 184 + }, + { + "name" : "minecraft:birch_pressure_plate", + "id" : -151 + }, + { + "name" : "minecraft:birch_sign", + "id" : 577 + }, + { + "name" : "minecraft:birch_stairs", + "id" : 135 + }, + { + "name" : "minecraft:birch_standing_sign", + "id" : -186 + }, + { + "name" : "minecraft:birch_trapdoor", + "id" : -146 + }, + { + "name" : "minecraft:birch_wall_sign", + "id" : -187 + }, + { + "name" : "minecraft:black_candle", + "id" : -428 + }, + { + "name" : "minecraft:black_candle_cake", + "id" : -445 + }, + { + "name" : "minecraft:black_dye", + "id" : 395 + }, + { + "name" : "minecraft:black_glazed_terracotta", + "id" : 235 + }, + { + "name" : "minecraft:blackstone", + "id" : -273 + }, + { + "name" : "minecraft:blackstone_double_slab", + "id" : -283 + }, + { + "name" : "minecraft:blackstone_slab", + "id" : -282 + }, + { + "name" : "minecraft:blackstone_stairs", + "id" : -276 + }, + { + "name" : "minecraft:blackstone_wall", + "id" : -277 + }, + { + "name" : "minecraft:blast_furnace", + "id" : -196 + }, + { + "name" : "minecraft:blaze_powder", + "id" : 429 + }, + { + "name" : "minecraft:blaze_rod", + "id" : 423 + }, + { + "name" : "minecraft:blaze_spawn_egg", + "id" : 456 + }, + { + "name" : "minecraft:bleach", + "id" : 596 + }, + { + "name" : "minecraft:blue_candle", + "id" : -424 + }, + { + "name" : "minecraft:blue_candle_cake", + "id" : -441 + }, + { + "name" : "minecraft:blue_dye", + "id" : 399 + }, + { + "name" : "minecraft:blue_glazed_terracotta", + "id" : 231 + }, + { + "name" : "minecraft:blue_ice", + "id" : -11 + }, + { + "name" : "minecraft:boat", + "id" : 649 + }, + { + "name" : "minecraft:bone", + "id" : 415 + }, + { + "name" : "minecraft:bone_block", + "id" : 216 + }, + { + "name" : "minecraft:bone_meal", + "id" : 411 + }, + { + "name" : "minecraft:book", + "id" : 387 + }, + { + "name" : "minecraft:bookshelf", + "id" : 47 + }, + { + "name" : "minecraft:border_block", + "id" : 212 + }, + { + "name" : "minecraft:bordure_indented_banner_pattern", + "id" : 586 + }, + { + "name" : "minecraft:bow", + "id" : 300 + }, + { + "name" : "minecraft:bowl", + "id" : 321 + }, + { + "name" : "minecraft:bread", + "id" : 261 + }, + { + "name" : "minecraft:brewing_stand", + "id" : 431 + }, + { + "name" : "minecraft:brick", + "id" : 383 + }, + { + "name" : "minecraft:brick_block", + "id" : 45 + }, + { + "name" : "minecraft:brick_stairs", + "id" : 108 + }, + { + "name" : "minecraft:brown_candle", + "id" : -425 + }, + { + "name" : "minecraft:brown_candle_cake", + "id" : -442 + }, + { + "name" : "minecraft:brown_dye", + "id" : 398 + }, + { + "name" : "minecraft:brown_glazed_terracotta", + "id" : 232 + }, + { + "name" : "minecraft:brown_mushroom", + "id" : 39 + }, + { + "name" : "minecraft:brown_mushroom_block", + "id" : 99 + }, + { + "name" : "minecraft:bubble_column", + "id" : -160 + }, + { + "name" : "minecraft:bucket", + "id" : 360 + }, + { + "name" : "minecraft:budding_amethyst", + "id" : -328 + }, + { + "name" : "minecraft:cactus", + "id" : 81 + }, + { + "name" : "minecraft:cake", + "id" : 417 + }, + { + "name" : "minecraft:calcite", + "id" : -326 + }, + { + "name" : "minecraft:camera", + "id" : 593 + }, + { + "name" : "minecraft:campfire", + "id" : 589 + }, + { + "name" : "minecraft:candle", + "id" : -412 + }, + { + "name" : "minecraft:candle_cake", + "id" : -429 + }, + { + "name" : "minecraft:carpet", + "id" : 171 + }, + { + "name" : "minecraft:carrot", + "id" : 279 + }, + { + "name" : "minecraft:carrot_on_a_stick", + "id" : 517 + }, + { + "name" : "minecraft:carrots", + "id" : 141 + }, + { + "name" : "minecraft:cartography_table", + "id" : -200 + }, + { + "name" : "minecraft:carved_pumpkin", + "id" : -155 + }, + { + "name" : "minecraft:cat_spawn_egg", + "id" : 488 + }, + { + "name" : "minecraft:cauldron", + "id" : 432 + }, + { + "name" : "minecraft:cave_spider_spawn_egg", + "id" : 457 + }, + { + "name" : "minecraft:cave_vines", + "id" : -322 + }, + { + "name" : "minecraft:cave_vines_body_with_berries", + "id" : -375 + }, + { + "name" : "minecraft:cave_vines_head_with_berries", + "id" : -376 + }, + { + "name" : "minecraft:chain", + "id" : 619 + }, + { + "name" : "minecraft:chain_command_block", + "id" : 189 + }, + { + "name" : "minecraft:chainmail_boots", + "id" : 342 + }, + { + "name" : "minecraft:chainmail_chestplate", + "id" : 340 + }, + { + "name" : "minecraft:chainmail_helmet", + "id" : 339 + }, + { + "name" : "minecraft:chainmail_leggings", + "id" : 341 + }, + { + "name" : "minecraft:charcoal", + "id" : 303 + }, + { + "name" : "minecraft:chemical_heat", + "id" : 192 + }, + { + "name" : "minecraft:chemistry_table", + "id" : 238 + }, + { + "name" : "minecraft:chest", + "id" : 54 + }, + { + "name" : "minecraft:chest_boat", + "id" : 645 + }, + { + "name" : "minecraft:chest_minecart", + "id" : 389 + }, + { + "name" : "minecraft:chicken", + "id" : 275 + }, + { + "name" : "minecraft:chicken_spawn_egg", + "id" : 435 + }, + { + "name" : "minecraft:chiseled_deepslate", + "id" : -395 + }, + { + "name" : "minecraft:chiseled_nether_bricks", + "id" : -302 + }, + { + "name" : "minecraft:chiseled_polished_blackstone", + "id" : -279 + }, + { + "name" : "minecraft:chorus_flower", + "id" : 200 + }, + { + "name" : "minecraft:chorus_fruit", + "id" : 558 + }, + { + "name" : "minecraft:chorus_plant", + "id" : 240 + }, + { + "name" : "minecraft:clay", + "id" : 82 + }, + { + "name" : "minecraft:clay_ball", + "id" : 384 + }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, + { + "name" : "minecraft:clock", + "id" : 393 + }, + { + "name" : "minecraft:coal", + "id" : 302 + }, + { + "name" : "minecraft:coal_block", + "id" : 173 + }, + { + "name" : "minecraft:coal_ore", + "id" : 16 + }, + { + "name" : "minecraft:cobbled_deepslate", + "id" : -379 + }, + { + "name" : "minecraft:cobbled_deepslate_double_slab", + "id" : -396 + }, + { + "name" : "minecraft:cobbled_deepslate_slab", + "id" : -380 + }, + { + "name" : "minecraft:cobbled_deepslate_stairs", + "id" : -381 + }, + { + "name" : "minecraft:cobbled_deepslate_wall", + "id" : -382 + }, + { + "name" : "minecraft:cobblestone", + "id" : 4 + }, + { + "name" : "minecraft:cobblestone_wall", + "id" : 139 + }, + { + "name" : "minecraft:cocoa", + "id" : 127 + }, + { + "name" : "minecraft:cocoa_beans", + "id" : 412 + }, + { + "name" : "minecraft:cod", + "id" : 264 + }, + { + "name" : "minecraft:cod_bucket", + "id" : 364 + }, + { + "name" : "minecraft:cod_spawn_egg", + "id" : 480 + }, + { + "name" : "minecraft:colored_torch_bp", + "id" : 204 + }, + { + "name" : "minecraft:colored_torch_rg", + "id" : 202 + }, + { + "name" : "minecraft:command_block", + "id" : 137 + }, + { + "name" : "minecraft:command_block_minecart", + "id" : 563 + }, + { + "name" : "minecraft:comparator", + "id" : 522 + }, + { + "name" : "minecraft:compass", + "id" : 391 + }, + { + "name" : "minecraft:composter", + "id" : -213 + }, + { + "name" : "minecraft:compound", + "id" : 594 + }, + { + "name" : "minecraft:concrete", + "id" : 236 + }, + { + "name" : "minecraft:concrete_powder", + "id" : 237 + }, + { + "name" : "minecraft:conduit", + "id" : -157 + }, + { + "name" : "minecraft:cooked_beef", + "id" : 274 + }, + { + "name" : "minecraft:cooked_chicken", + "id" : 276 + }, + { + "name" : "minecraft:cooked_cod", + "id" : 268 + }, + { + "name" : "minecraft:cooked_mutton", + "id" : 551 + }, + { + "name" : "minecraft:cooked_porkchop", + "id" : 263 + }, + { + "name" : "minecraft:cooked_rabbit", + "id" : 289 + }, + { + "name" : "minecraft:cooked_salmon", + "id" : 269 + }, + { + "name" : "minecraft:cookie", + "id" : 271 + }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 504 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, + { + "name" : "minecraft:coral", + "id" : -131 + }, + { + "name" : "minecraft:coral_block", + "id" : -132 + }, + { + "name" : "minecraft:coral_fan", + "id" : -133 + }, + { + "name" : "minecraft:coral_fan_dead", + "id" : -134 + }, + { + "name" : "minecraft:coral_fan_hang", + "id" : -135 + }, + { + "name" : "minecraft:coral_fan_hang2", + "id" : -136 + }, + { + "name" : "minecraft:coral_fan_hang3", + "id" : -137 + }, + { + "name" : "minecraft:cow_spawn_egg", + "id" : 436 + }, + { + "name" : "minecraft:cracked_deepslate_bricks", + "id" : -410 + }, + { + "name" : "minecraft:cracked_deepslate_tiles", + "id" : -409 + }, + { + "name" : "minecraft:cracked_nether_bricks", + "id" : -303 + }, + { + "name" : "minecraft:cracked_polished_blackstone_bricks", + "id" : -280 + }, + { + "name" : "minecraft:crafting_table", + "id" : 58 + }, + { + "name" : "minecraft:creeper_banner_pattern", + "id" : 582 + }, + { + "name" : "minecraft:creeper_spawn_egg", + "id" : 441 + }, + { + "name" : "minecraft:crimson_button", + "id" : -260 + }, + { + "name" : "minecraft:crimson_door", + "id" : 616 + }, + { + "name" : "minecraft:crimson_double_slab", + "id" : -266 + }, + { + "name" : "minecraft:crimson_fence", + "id" : -256 + }, + { + "name" : "minecraft:crimson_fence_gate", + "id" : -258 + }, + { + "name" : "minecraft:crimson_fungus", + "id" : -228 + }, + { + "name" : "minecraft:crimson_hyphae", + "id" : -299 + }, + { + "name" : "minecraft:crimson_nylium", + "id" : -232 + }, + { + "name" : "minecraft:crimson_planks", + "id" : -242 + }, + { + "name" : "minecraft:crimson_pressure_plate", + "id" : -262 + }, + { + "name" : "minecraft:crimson_roots", + "id" : -223 + }, + { + "name" : "minecraft:crimson_sign", + "id" : 614 + }, + { + "name" : "minecraft:crimson_slab", + "id" : -264 + }, + { + "name" : "minecraft:crimson_stairs", + "id" : -254 + }, + { + "name" : "minecraft:crimson_standing_sign", + "id" : -250 + }, + { + "name" : "minecraft:crimson_stem", + "id" : -225 + }, + { + "name" : "minecraft:crimson_trapdoor", + "id" : -246 + }, + { + "name" : "minecraft:crimson_wall_sign", + "id" : -252 + }, + { + "name" : "minecraft:crossbow", + "id" : 575 + }, + { + "name" : "minecraft:crying_obsidian", + "id" : -289 + }, + { + "name" : "minecraft:cut_copper", + "id" : -347 + }, + { + "name" : "minecraft:cut_copper_slab", + "id" : -361 + }, + { + "name" : "minecraft:cut_copper_stairs", + "id" : -354 + }, + { + "name" : "minecraft:cyan_candle", + "id" : -422 + }, + { + "name" : "minecraft:cyan_candle_cake", + "id" : -439 + }, + { + "name" : "minecraft:cyan_dye", + "id" : 401 + }, + { + "name" : "minecraft:cyan_glazed_terracotta", + "id" : 229 + }, + { + "name" : "minecraft:dark_oak_boat", + "id" : 380 + }, + { + "name" : "minecraft:dark_oak_button", + "id" : -142 + }, + { + "name" : "minecraft:dark_oak_chest_boat", + "id" : 643 + }, + { + "name" : "minecraft:dark_oak_door", + "id" : 557 + }, + { + "name" : "minecraft:dark_oak_fence_gate", + "id" : 186 + }, + { + "name" : "minecraft:dark_oak_pressure_plate", + "id" : -152 + }, + { + "name" : "minecraft:dark_oak_sign", + "id" : 580 + }, + { + "name" : "minecraft:dark_oak_stairs", + "id" : 164 + }, + { + "name" : "minecraft:dark_oak_trapdoor", + "id" : -147 + }, + { + "name" : "minecraft:dark_prismarine_stairs", + "id" : -3 + }, + { + "name" : "minecraft:darkoak_standing_sign", + "id" : -192 + }, + { + "name" : "minecraft:darkoak_wall_sign", + "id" : -193 + }, + { + "name" : "minecraft:daylight_detector", + "id" : 151 + }, + { + "name" : "minecraft:daylight_detector_inverted", + "id" : 178 + }, + { + "name" : "minecraft:deadbush", + "id" : 32 + }, + { + "name" : "minecraft:deepslate", + "id" : -378 + }, + { + "name" : "minecraft:deepslate_brick_double_slab", + "id" : -399 + }, + { + "name" : "minecraft:deepslate_brick_slab", + "id" : -392 + }, + { + "name" : "minecraft:deepslate_brick_stairs", + "id" : -393 + }, + { + "name" : "minecraft:deepslate_brick_wall", + "id" : -394 + }, + { + "name" : "minecraft:deepslate_bricks", + "id" : -391 + }, + { + "name" : "minecraft:deepslate_coal_ore", + "id" : -406 + }, + { + "name" : "minecraft:deepslate_copper_ore", + "id" : -408 + }, + { + "name" : "minecraft:deepslate_diamond_ore", + "id" : -405 + }, + { + "name" : "minecraft:deepslate_emerald_ore", + "id" : -407 + }, + { + "name" : "minecraft:deepslate_gold_ore", + "id" : -402 + }, + { + "name" : "minecraft:deepslate_iron_ore", + "id" : -401 + }, + { + "name" : "minecraft:deepslate_lapis_ore", + "id" : -400 + }, + { + "name" : "minecraft:deepslate_redstone_ore", + "id" : -403 + }, + { + "name" : "minecraft:deepslate_tile_double_slab", + "id" : -398 + }, + { + "name" : "minecraft:deepslate_tile_slab", + "id" : -388 + }, + { + "name" : "minecraft:deepslate_tile_stairs", + "id" : -389 + }, + { + "name" : "minecraft:deepslate_tile_wall", + "id" : -390 + }, + { + "name" : "minecraft:deepslate_tiles", + "id" : -387 + }, + { + "name" : "minecraft:deny", + "id" : 211 + }, + { + "name" : "minecraft:detector_rail", + "id" : 28 + }, + { + "name" : "minecraft:diamond", + "id" : 304 + }, + { + "name" : "minecraft:diamond_axe", + "id" : 319 + }, + { + "name" : "minecraft:diamond_block", + "id" : 57 + }, + { + "name" : "minecraft:diamond_boots", + "id" : 350 + }, + { + "name" : "minecraft:diamond_chestplate", + "id" : 348 + }, + { + "name" : "minecraft:diamond_helmet", + "id" : 347 + }, + { + "name" : "minecraft:diamond_hoe", + "id" : 332 + }, + { + "name" : "minecraft:diamond_horse_armor", + "id" : 533 + }, + { + "name" : "minecraft:diamond_leggings", + "id" : 349 + }, + { + "name" : "minecraft:diamond_ore", + "id" : 56 + }, + { + "name" : "minecraft:diamond_pickaxe", + "id" : 318 + }, + { + "name" : "minecraft:diamond_shovel", + "id" : 317 + }, + { + "name" : "minecraft:diamond_sword", + "id" : 316 + }, + { + "name" : "minecraft:diorite_stairs", + "id" : -170 + }, + { + "name" : "minecraft:dirt", + "id" : 3 + }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, + { + "name" : "minecraft:disc_fragment_5", + "id" : 637 + }, + { + "name" : "minecraft:dispenser", + "id" : 23 + }, + { + "name" : "minecraft:dolphin_spawn_egg", + "id" : 484 + }, + { + "name" : "minecraft:donkey_spawn_egg", + "id" : 465 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 + }, + { + "name" : "minecraft:double_plant", + "id" : 175 + }, + { + "name" : "minecraft:double_stone_block_slab", + "id" : 43 + }, + { + "name" : "minecraft:double_stone_block_slab2", + "id" : 181 + }, + { + "name" : "minecraft:double_stone_block_slab3", + "id" : -167 + }, + { + "name" : "minecraft:double_stone_block_slab4", + "id" : -168 + }, + { + "name" : "minecraft:double_wooden_slab", + "id" : 157 + }, + { + "name" : "minecraft:dragon_breath", + "id" : 560 + }, + { + "name" : "minecraft:dragon_egg", + "id" : 122 + }, + { + "name" : "minecraft:dried_kelp", + "id" : 270 + }, + { + "name" : "minecraft:dried_kelp_block", + "id" : -139 + }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, + { + "name" : "minecraft:dropper", + "id" : 125 + }, + { + "name" : "minecraft:drowned_spawn_egg", + "id" : 483 + }, + { + "name" : "minecraft:dye", + "id" : 650 + }, + { + "name" : "minecraft:echo_shard", + "id" : 647 + }, + { + "name" : "minecraft:egg", + "id" : 390 + }, + { + "name" : "minecraft:elder_guardian_spawn_egg", + "id" : 471 + }, + { + "name" : "minecraft:element_0", + "id" : 36 + }, + { + "name" : "minecraft:element_1", + "id" : -12 + }, + { + "name" : "minecraft:element_10", + "id" : -21 + }, + { + "name" : "minecraft:element_100", + "id" : -111 + }, + { + "name" : "minecraft:element_101", + "id" : -112 + }, + { + "name" : "minecraft:element_102", + "id" : -113 + }, + { + "name" : "minecraft:element_103", + "id" : -114 + }, + { + "name" : "minecraft:element_104", + "id" : -115 + }, + { + "name" : "minecraft:element_105", + "id" : -116 + }, + { + "name" : "minecraft:element_106", + "id" : -117 + }, + { + "name" : "minecraft:element_107", + "id" : -118 + }, + { + "name" : "minecraft:element_108", + "id" : -119 + }, + { + "name" : "minecraft:element_109", + "id" : -120 + }, + { + "name" : "minecraft:element_11", + "id" : -22 + }, + { + "name" : "minecraft:element_110", + "id" : -121 + }, + { + "name" : "minecraft:element_111", + "id" : -122 + }, + { + "name" : "minecraft:element_112", + "id" : -123 + }, + { + "name" : "minecraft:element_113", + "id" : -124 + }, + { + "name" : "minecraft:element_114", + "id" : -125 + }, + { + "name" : "minecraft:element_115", + "id" : -126 + }, + { + "name" : "minecraft:element_116", + "id" : -127 + }, + { + "name" : "minecraft:element_117", + "id" : -128 + }, + { + "name" : "minecraft:element_118", + "id" : -129 + }, + { + "name" : "minecraft:element_12", + "id" : -23 + }, + { + "name" : "minecraft:element_13", + "id" : -24 + }, + { + "name" : "minecraft:element_14", + "id" : -25 + }, + { + "name" : "minecraft:element_15", + "id" : -26 + }, + { + "name" : "minecraft:element_16", + "id" : -27 + }, + { + "name" : "minecraft:element_17", + "id" : -28 + }, + { + "name" : "minecraft:element_18", + "id" : -29 + }, + { + "name" : "minecraft:element_19", + "id" : -30 + }, + { + "name" : "minecraft:element_2", + "id" : -13 + }, + { + "name" : "minecraft:element_20", + "id" : -31 + }, + { + "name" : "minecraft:element_21", + "id" : -32 + }, + { + "name" : "minecraft:element_22", + "id" : -33 + }, + { + "name" : "minecraft:element_23", + "id" : -34 + }, + { + "name" : "minecraft:element_24", + "id" : -35 + }, + { + "name" : "minecraft:element_25", + "id" : -36 + }, + { + "name" : "minecraft:element_26", + "id" : -37 + }, + { + "name" : "minecraft:element_27", + "id" : -38 + }, + { + "name" : "minecraft:element_28", + "id" : -39 + }, + { + "name" : "minecraft:element_29", + "id" : -40 + }, + { + "name" : "minecraft:element_3", + "id" : -14 + }, + { + "name" : "minecraft:element_30", + "id" : -41 + }, + { + "name" : "minecraft:element_31", + "id" : -42 + }, + { + "name" : "minecraft:element_32", + "id" : -43 + }, + { + "name" : "minecraft:element_33", + "id" : -44 + }, + { + "name" : "minecraft:element_34", + "id" : -45 + }, + { + "name" : "minecraft:element_35", + "id" : -46 + }, + { + "name" : "minecraft:element_36", + "id" : -47 + }, + { + "name" : "minecraft:element_37", + "id" : -48 + }, + { + "name" : "minecraft:element_38", + "id" : -49 + }, + { + "name" : "minecraft:element_39", + "id" : -50 + }, + { + "name" : "minecraft:element_4", + "id" : -15 + }, + { + "name" : "minecraft:element_40", + "id" : -51 + }, + { + "name" : "minecraft:element_41", + "id" : -52 + }, + { + "name" : "minecraft:element_42", + "id" : -53 + }, + { + "name" : "minecraft:element_43", + "id" : -54 + }, + { + "name" : "minecraft:element_44", + "id" : -55 + }, + { + "name" : "minecraft:element_45", + "id" : -56 + }, + { + "name" : "minecraft:element_46", + "id" : -57 + }, + { + "name" : "minecraft:element_47", + "id" : -58 + }, + { + "name" : "minecraft:element_48", + "id" : -59 + }, + { + "name" : "minecraft:element_49", + "id" : -60 + }, + { + "name" : "minecraft:element_5", + "id" : -16 + }, + { + "name" : "minecraft:element_50", + "id" : -61 + }, + { + "name" : "minecraft:element_51", + "id" : -62 + }, + { + "name" : "minecraft:element_52", + "id" : -63 + }, + { + "name" : "minecraft:element_53", + "id" : -64 + }, + { + "name" : "minecraft:element_54", + "id" : -65 + }, + { + "name" : "minecraft:element_55", + "id" : -66 + }, + { + "name" : "minecraft:element_56", + "id" : -67 + }, + { + "name" : "minecraft:element_57", + "id" : -68 + }, + { + "name" : "minecraft:element_58", + "id" : -69 + }, + { + "name" : "minecraft:element_59", + "id" : -70 + }, + { + "name" : "minecraft:element_6", + "id" : -17 + }, + { + "name" : "minecraft:element_60", + "id" : -71 + }, + { + "name" : "minecraft:element_61", + "id" : -72 + }, + { + "name" : "minecraft:element_62", + "id" : -73 + }, + { + "name" : "minecraft:element_63", + "id" : -74 + }, + { + "name" : "minecraft:element_64", + "id" : -75 + }, + { + "name" : "minecraft:element_65", + "id" : -76 + }, + { + "name" : "minecraft:element_66", + "id" : -77 + }, + { + "name" : "minecraft:element_67", + "id" : -78 + }, + { + "name" : "minecraft:element_68", + "id" : -79 + }, + { + "name" : "minecraft:element_69", + "id" : -80 + }, + { + "name" : "minecraft:element_7", + "id" : -18 + }, + { + "name" : "minecraft:element_70", + "id" : -81 + }, + { + "name" : "minecraft:element_71", + "id" : -82 + }, + { + "name" : "minecraft:element_72", + "id" : -83 + }, + { + "name" : "minecraft:element_73", + "id" : -84 + }, + { + "name" : "minecraft:element_74", + "id" : -85 + }, + { + "name" : "minecraft:element_75", + "id" : -86 + }, + { + "name" : "minecraft:element_76", + "id" : -87 + }, + { + "name" : "minecraft:element_77", + "id" : -88 + }, + { + "name" : "minecraft:element_78", + "id" : -89 + }, + { + "name" : "minecraft:element_79", + "id" : -90 + }, + { + "name" : "minecraft:element_8", + "id" : -19 + }, + { + "name" : "minecraft:element_80", + "id" : -91 + }, + { + "name" : "minecraft:element_81", + "id" : -92 + }, + { + "name" : "minecraft:element_82", + "id" : -93 + }, + { + "name" : "minecraft:element_83", + "id" : -94 + }, + { + "name" : "minecraft:element_84", + "id" : -95 + }, + { + "name" : "minecraft:element_85", + "id" : -96 + }, + { + "name" : "minecraft:element_86", + "id" : -97 + }, + { + "name" : "minecraft:element_87", + "id" : -98 + }, + { + "name" : "minecraft:element_88", + "id" : -99 + }, + { + "name" : "minecraft:element_89", + "id" : -100 + }, + { + "name" : "minecraft:element_9", + "id" : -20 + }, + { + "name" : "minecraft:element_90", + "id" : -101 + }, + { + "name" : "minecraft:element_91", + "id" : -102 + }, + { + "name" : "minecraft:element_92", + "id" : -103 + }, + { + "name" : "minecraft:element_93", + "id" : -104 + }, + { + "name" : "minecraft:element_94", + "id" : -105 + }, + { + "name" : "minecraft:element_95", + "id" : -106 + }, + { + "name" : "minecraft:element_96", + "id" : -107 + }, + { + "name" : "minecraft:element_97", + "id" : -108 + }, + { + "name" : "minecraft:element_98", + "id" : -109 + }, + { + "name" : "minecraft:element_99", + "id" : -110 + }, + { + "name" : "minecraft:elytra", + "id" : 564 + }, + { + "name" : "minecraft:emerald", + "id" : 512 + }, + { + "name" : "minecraft:emerald_block", + "id" : 133 + }, + { + "name" : "minecraft:emerald_ore", + "id" : 129 + }, + { + "name" : "minecraft:empty_map", + "id" : 515 + }, + { + "name" : "minecraft:enchanted_book", + "id" : 521 + }, + { + "name" : "minecraft:enchanted_golden_apple", + "id" : 259 + }, + { + "name" : "minecraft:enchanting_table", + "id" : 116 + }, + { + "name" : "minecraft:end_brick_stairs", + "id" : -178 + }, + { + "name" : "minecraft:end_bricks", + "id" : 206 + }, + { + "name" : "minecraft:end_crystal", + "id" : 653 + }, + { + "name" : "minecraft:end_gateway", + "id" : 209 + }, + { + "name" : "minecraft:end_portal", + "id" : 119 + }, + { + "name" : "minecraft:end_portal_frame", + "id" : 120 + }, + { + "name" : "minecraft:end_rod", + "id" : 208 + }, + { + "name" : "minecraft:end_stone", + "id" : 121 + }, + { + "name" : "minecraft:ender_chest", + "id" : 130 + }, + { + "name" : "minecraft:ender_eye", + "id" : 433 + }, + { + "name" : "minecraft:ender_pearl", + "id" : 422 + }, + { + "name" : "minecraft:enderman_spawn_egg", + "id" : 442 + }, + { + "name" : "minecraft:endermite_spawn_egg", + "id" : 460 + }, + { + "name" : "minecraft:evoker_spawn_egg", + "id" : 475 + }, + { + "name" : "minecraft:experience_bottle", + "id" : 508 + }, + { + "name" : "minecraft:exposed_copper", + "id" : -341 + }, + { + "name" : "minecraft:exposed_cut_copper", + "id" : -348 + }, + { + "name" : "minecraft:exposed_cut_copper_slab", + "id" : -362 + }, + { + "name" : "minecraft:exposed_cut_copper_stairs", + "id" : -355 + }, + { + "name" : "minecraft:exposed_double_cut_copper_slab", + "id" : -369 + }, + { + "name" : "minecraft:farmland", + "id" : 60 + }, + { + "name" : "minecraft:feather", + "id" : 327 + }, + { + "name" : "minecraft:fence", + "id" : 85 + }, + { + "name" : "minecraft:fence_gate", + "id" : 107 + }, + { + "name" : "minecraft:fermented_spider_eye", + "id" : 428 + }, + { + "name" : "minecraft:field_masoned_banner_pattern", + "id" : 585 + }, + { + "name" : "minecraft:filled_map", + "id" : 420 + }, + { + "name" : "minecraft:fire", + "id" : 51 + }, + { + "name" : "minecraft:fire_charge", + "id" : 509 + }, + { + "name" : "minecraft:firework_rocket", + "id" : 519 + }, + { + "name" : "minecraft:firework_star", + "id" : 520 + }, + { + "name" : "minecraft:fishing_rod", + "id" : 392 + }, + { + "name" : "minecraft:fletching_table", + "id" : -201 + }, + { + "name" : "minecraft:flint", + "id" : 356 + }, + { + "name" : "minecraft:flint_and_steel", + "id" : 299 + }, + { + "name" : "minecraft:flower_banner_pattern", + "id" : 581 + }, + { + "name" : "minecraft:flower_pot", + "id" : 514 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 + }, + { + "name" : "minecraft:flowing_lava", + "id" : 10 + }, + { + "name" : "minecraft:flowing_water", + "id" : 8 + }, + { + "name" : "minecraft:fox_spawn_egg", + "id" : 490 + }, + { + "name" : "minecraft:frame", + "id" : 513 + }, + { + "name" : "minecraft:frog_spawn", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 628 + }, + { + "name" : "minecraft:frosted_ice", + "id" : 207 + }, + { + "name" : "minecraft:furnace", + "id" : 61 + }, + { + "name" : "minecraft:ghast_spawn_egg", + "id" : 454 + }, + { + "name" : "minecraft:ghast_tear", + "id" : 424 + }, + { + "name" : "minecraft:gilded_blackstone", + "id" : -281 + }, + { + "name" : "minecraft:glass", + "id" : 20 + }, + { + "name" : "minecraft:glass_bottle", + "id" : 427 + }, + { + "name" : "minecraft:glass_pane", + "id" : 102 + }, + { + "name" : "minecraft:glistering_melon_slice", + "id" : 434 + }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, + { + "name" : "minecraft:glow_berries", + "id" : 654 + }, + { + "name" : "minecraft:glow_frame", + "id" : 623 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 503 + }, + { + "name" : "minecraft:glow_lichen", + "id" : -411 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 502 + }, + { + "name" : "minecraft:glow_stick", + "id" : 601 + }, + { + "name" : "minecraft:glowingobsidian", + "id" : 246 + }, + { + "name" : "minecraft:glowstone", + "id" : 89 + }, + { + "name" : "minecraft:glowstone_dust", + "id" : 394 + }, + { + "name" : "minecraft:goat_horn", + "id" : 627 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 501 + }, + { + "name" : "minecraft:gold_block", + "id" : 41 + }, + { + "name" : "minecraft:gold_ingot", + "id" : 306 + }, + { + "name" : "minecraft:gold_nugget", + "id" : 425 + }, + { + "name" : "minecraft:gold_ore", + "id" : 14 + }, + { + "name" : "minecraft:golden_apple", + "id" : 258 + }, + { + "name" : "minecraft:golden_axe", + "id" : 325 + }, + { + "name" : "minecraft:golden_boots", + "id" : 354 + }, + { + "name" : "minecraft:golden_carrot", + "id" : 283 + }, + { + "name" : "minecraft:golden_chestplate", + "id" : 352 + }, + { + "name" : "minecraft:golden_helmet", + "id" : 351 + }, + { + "name" : "minecraft:golden_hoe", + "id" : 333 + }, + { + "name" : "minecraft:golden_horse_armor", + "id" : 532 + }, + { + "name" : "minecraft:golden_leggings", + "id" : 353 + }, + { + "name" : "minecraft:golden_pickaxe", + "id" : 324 + }, + { + "name" : "minecraft:golden_rail", + "id" : 27 + }, + { + "name" : "minecraft:golden_shovel", + "id" : 323 + }, + { + "name" : "minecraft:golden_sword", + "id" : 322 + }, + { + "name" : "minecraft:granite_stairs", + "id" : -169 + }, + { + "name" : "minecraft:grass", + "id" : 2 + }, + { + "name" : "minecraft:grass_path", + "id" : 198 + }, + { + "name" : "minecraft:gravel", + "id" : 13 + }, + { + "name" : "minecraft:gray_candle", + "id" : -420 + }, + { + "name" : "minecraft:gray_candle_cake", + "id" : -437 + }, + { + "name" : "minecraft:gray_dye", + "id" : 403 + }, + { + "name" : "minecraft:gray_glazed_terracotta", + "id" : 227 + }, + { + "name" : "minecraft:green_candle", + "id" : -426 + }, + { + "name" : "minecraft:green_candle_cake", + "id" : -443 + }, + { + "name" : "minecraft:green_dye", + "id" : 397 + }, + { + "name" : "minecraft:green_glazed_terracotta", + "id" : 233 + }, + { + "name" : "minecraft:grindstone", + "id" : -195 + }, + { + "name" : "minecraft:guardian_spawn_egg", + "id" : 461 + }, + { + "name" : "minecraft:gunpowder", + "id" : 328 + }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, + { + "name" : "minecraft:hard_glass", + "id" : 253 + }, + { + "name" : "minecraft:hard_glass_pane", + "id" : 190 + }, + { + "name" : "minecraft:hard_stained_glass", + "id" : 254 + }, + { + "name" : "minecraft:hard_stained_glass_pane", + "id" : 191 + }, + { + "name" : "minecraft:hardened_clay", + "id" : 172 + }, + { + "name" : "minecraft:hay_block", + "id" : 170 + }, + { + "name" : "minecraft:heart_of_the_sea", + "id" : 571 + }, + { + "name" : "minecraft:heavy_weighted_pressure_plate", + "id" : 148 + }, + { + "name" : "minecraft:hoglin_spawn_egg", + "id" : 496 + }, + { + "name" : "minecraft:honey_block", + "id" : -220 + }, + { + "name" : "minecraft:honey_bottle", + "id" : 592 + }, + { + "name" : "minecraft:honeycomb", + "id" : 591 + }, + { + "name" : "minecraft:honeycomb_block", + "id" : -221 + }, + { + "name" : "minecraft:hopper", + "id" : 527 + }, + { + "name" : "minecraft:hopper_minecart", + "id" : 526 + }, + { + "name" : "minecraft:horse_spawn_egg", + "id" : 458 + }, + { + "name" : "minecraft:husk_spawn_egg", + "id" : 463 + }, + { + "name" : "minecraft:ice", + "id" : 79 + }, + { + "name" : "minecraft:ice_bomb", + "id" : 595 + }, + { + "name" : "minecraft:infested_deepslate", + "id" : -454 + }, + { + "name" : "minecraft:info_update", + "id" : 248 + }, + { + "name" : "minecraft:info_update2", + "id" : 249 + }, + { + "name" : "minecraft:ink_sac", + "id" : 413 + }, + { + "name" : "minecraft:invisible_bedrock", + "id" : 95 + }, + { + "name" : "minecraft:iron_axe", + "id" : 298 + }, + { + "name" : "minecraft:iron_bars", + "id" : 101 + }, + { + "name" : "minecraft:iron_block", + "id" : 42 + }, + { + "name" : "minecraft:iron_boots", + "id" : 346 + }, + { + "name" : "minecraft:iron_chestplate", + "id" : 344 + }, + { + "name" : "minecraft:iron_door", + "id" : 372 + }, + { + "name" : "minecraft:iron_helmet", + "id" : 343 + }, + { + "name" : "minecraft:iron_hoe", + "id" : 331 + }, + { + "name" : "minecraft:iron_horse_armor", + "id" : 531 + }, + { + "name" : "minecraft:iron_ingot", + "id" : 305 + }, + { + "name" : "minecraft:iron_leggings", + "id" : 345 + }, + { + "name" : "minecraft:iron_nugget", + "id" : 569 + }, + { + "name" : "minecraft:iron_ore", + "id" : 15 + }, + { + "name" : "minecraft:iron_pickaxe", + "id" : 297 + }, + { + "name" : "minecraft:iron_shovel", + "id" : 296 + }, + { + "name" : "minecraft:iron_sword", + "id" : 307 + }, + { + "name" : "minecraft:iron_trapdoor", + "id" : 167 + }, + { + "name" : "minecraft:item.acacia_door", + "id" : 196 + }, + { + "name" : "minecraft:item.bed", + "id" : 26 + }, + { + "name" : "minecraft:item.beetroot", + "id" : 244 + }, + { + "name" : "minecraft:item.birch_door", + "id" : 194 + }, + { + "name" : "minecraft:item.brewing_stand", + "id" : 117 + }, + { + "name" : "minecraft:item.cake", + "id" : 92 + }, + { + "name" : "minecraft:item.camera", + "id" : 242 + }, + { + "name" : "minecraft:item.campfire", + "id" : -209 + }, + { + "name" : "minecraft:item.cauldron", + "id" : 118 + }, + { + "name" : "minecraft:item.chain", + "id" : -286 + }, + { + "name" : "minecraft:item.crimson_door", + "id" : -244 + }, + { + "name" : "minecraft:item.dark_oak_door", + "id" : 197 + }, + { + "name" : "minecraft:item.flower_pot", + "id" : 140 + }, + { + "name" : "minecraft:item.frame", + "id" : 199 + }, + { + "name" : "minecraft:item.glow_frame", + "id" : -339 + }, + { + "name" : "minecraft:item.hopper", + "id" : 154 + }, + { + "name" : "minecraft:item.iron_door", + "id" : 71 + }, + { + "name" : "minecraft:item.jungle_door", + "id" : 195 + }, + { + "name" : "minecraft:item.kelp", + "id" : -138 + }, + { + "name" : "minecraft:item.mangrove_door", + "id" : -493 + }, + { + "name" : "minecraft:item.nether_sprouts", + "id" : -238 + }, + { + "name" : "minecraft:item.nether_wart", + "id" : 115 + }, + { + "name" : "minecraft:item.reeds", + "id" : 83 + }, + { + "name" : "minecraft:item.skull", + "id" : 144 + }, + { + "name" : "minecraft:item.soul_campfire", + "id" : -290 + }, + { + "name" : "minecraft:item.spruce_door", + "id" : 193 + }, + { + "name" : "minecraft:item.warped_door", + "id" : -245 + }, + { + "name" : "minecraft:item.wheat", + "id" : 59 + }, + { + "name" : "minecraft:item.wooden_door", + "id" : 64 + }, + { + "name" : "minecraft:jigsaw", + "id" : -211 + }, + { + "name" : "minecraft:jukebox", + "id" : 84 + }, + { + "name" : "minecraft:jungle_boat", + "id" : 377 + }, + { + "name" : "minecraft:jungle_button", + "id" : -143 + }, + { + "name" : "minecraft:jungle_chest_boat", + "id" : 640 + }, + { + "name" : "minecraft:jungle_door", + "id" : 555 + }, + { + "name" : "minecraft:jungle_fence_gate", + "id" : 185 + }, + { + "name" : "minecraft:jungle_pressure_plate", + "id" : -153 + }, + { + "name" : "minecraft:jungle_sign", + "id" : 578 + }, + { + "name" : "minecraft:jungle_stairs", + "id" : 136 + }, + { + "name" : "minecraft:jungle_standing_sign", + "id" : -188 + }, + { + "name" : "minecraft:jungle_trapdoor", + "id" : -148 + }, + { + "name" : "minecraft:jungle_wall_sign", + "id" : -189 + }, + { + "name" : "minecraft:kelp", + "id" : 382 + }, + { + "name" : "minecraft:ladder", + "id" : 65 + }, + { + "name" : "minecraft:lantern", + "id" : -208 + }, + { + "name" : "minecraft:lapis_block", + "id" : 22 + }, + { + "name" : "minecraft:lapis_lazuli", + "id" : 414 + }, + { + "name" : "minecraft:lapis_ore", + "id" : 21 + }, + { + "name" : "minecraft:large_amethyst_bud", + "id" : -330 + }, + { + "name" : "minecraft:lava", + "id" : 11 + }, + { + "name" : "minecraft:lava_bucket", + "id" : 363 + }, + { + "name" : "minecraft:lava_cauldron", + "id" : -210 + }, + { + "name" : "minecraft:lead", + "id" : 547 + }, + { + "name" : "minecraft:leather", + "id" : 381 + }, + { + "name" : "minecraft:leather_boots", + "id" : 338 + }, + { + "name" : "minecraft:leather_chestplate", + "id" : 336 + }, + { + "name" : "minecraft:leather_helmet", + "id" : 335 + }, + { + "name" : "minecraft:leather_horse_armor", + "id" : 530 + }, + { + "name" : "minecraft:leather_leggings", + "id" : 337 + }, + { + "name" : "minecraft:leaves", + "id" : 18 + }, + { + "name" : "minecraft:leaves2", + "id" : 161 + }, + { + "name" : "minecraft:lectern", + "id" : -194 + }, + { + "name" : "minecraft:lever", + "id" : 69 + }, + { + "name" : "minecraft:light_block", + "id" : -215 + }, + { + "name" : "minecraft:light_blue_candle", + "id" : -416 + }, + { + "name" : "minecraft:light_blue_candle_cake", + "id" : -433 + }, + { + "name" : "minecraft:light_blue_dye", + "id" : 407 + }, + { + "name" : "minecraft:light_blue_glazed_terracotta", + "id" : 223 + }, + { + "name" : "minecraft:light_gray_candle", + "id" : -421 + }, + { + "name" : "minecraft:light_gray_candle_cake", + "id" : -438 + }, + { + "name" : "minecraft:light_gray_dye", + "id" : 402 + }, + { + "name" : "minecraft:light_weighted_pressure_plate", + "id" : 147 + }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, + { + "name" : "minecraft:lime_candle", + "id" : -418 + }, + { + "name" : "minecraft:lime_candle_cake", + "id" : -435 + }, + { + "name" : "minecraft:lime_dye", + "id" : 405 + }, + { + "name" : "minecraft:lime_glazed_terracotta", + "id" : 225 + }, + { + "name" : "minecraft:lingering_potion", + "id" : 562 + }, + { + "name" : "minecraft:lit_blast_furnace", + "id" : -214 + }, + { + "name" : "minecraft:lit_deepslate_redstone_ore", + "id" : -404 + }, + { + "name" : "minecraft:lit_furnace", + "id" : 62 + }, + { + "name" : "minecraft:lit_pumpkin", + "id" : 91 + }, + { + "name" : "minecraft:lit_redstone_lamp", + "id" : 124 + }, + { + "name" : "minecraft:lit_redstone_ore", + "id" : 74 + }, + { + "name" : "minecraft:lit_smoker", + "id" : -199 + }, + { + "name" : "minecraft:llama_spawn_egg", + "id" : 473 + }, + { + "name" : "minecraft:lodestone", + "id" : -222 + }, + { + "name" : "minecraft:lodestone_compass", + "id" : 602 + }, + { + "name" : "minecraft:log", + "id" : 17 + }, + { + "name" : "minecraft:log2", + "id" : 162 + }, + { + "name" : "minecraft:loom", + "id" : -204 + }, + { + "name" : "minecraft:magenta_candle", + "id" : -415 + }, + { + "name" : "minecraft:magenta_candle_cake", + "id" : -432 + }, + { + "name" : "minecraft:magenta_dye", + "id" : 408 + }, + { + "name" : "minecraft:magenta_glazed_terracotta", + "id" : 222 + }, + { + "name" : "minecraft:magma", + "id" : 213 + }, + { + "name" : "minecraft:magma_cream", + "id" : 430 + }, + { + "name" : "minecraft:magma_cube_spawn_egg", + "id" : 455 + }, + { + "name" : "minecraft:mangrove_boat", + "id" : 635 + }, + { + "name" : "minecraft:mangrove_button", + "id" : -487 + }, + { + "name" : "minecraft:mangrove_chest_boat", + "id" : 644 + }, + { + "name" : "minecraft:mangrove_door", + "id" : 633 + }, + { + "name" : "minecraft:mangrove_double_slab", + "id" : -499 + }, + { + "name" : "minecraft:mangrove_fence", + "id" : -491 + }, + { + "name" : "minecraft:mangrove_fence_gate", + "id" : -492 + }, + { + "name" : "minecraft:mangrove_leaves", + "id" : -472 + }, + { + "name" : "minecraft:mangrove_log", + "id" : -484 + }, + { + "name" : "minecraft:mangrove_planks", + "id" : -486 + }, + { + "name" : "minecraft:mangrove_pressure_plate", + "id" : -490 + }, + { + "name" : "minecraft:mangrove_propagule", + "id" : -474 + }, + { + "name" : "minecraft:mangrove_roots", + "id" : -482 + }, + { + "name" : "minecraft:mangrove_sign", + "id" : 634 + }, + { + "name" : "minecraft:mangrove_slab", + "id" : -489 + }, + { + "name" : "minecraft:mangrove_stairs", + "id" : -488 + }, + { + "name" : "minecraft:mangrove_standing_sign", + "id" : -494 + }, + { + "name" : "minecraft:mangrove_trapdoor", + "id" : -496 + }, + { + "name" : "minecraft:mangrove_wall_sign", + "id" : -495 + }, + { + "name" : "minecraft:mangrove_wood", + "id" : -497 + }, + { + "name" : "minecraft:medicine", + "id" : 599 + }, + { + "name" : "minecraft:medium_amethyst_bud", + "id" : -331 + }, + { + "name" : "minecraft:melon_block", + "id" : 103 + }, + { + "name" : "minecraft:melon_seeds", + "id" : 293 + }, + { + "name" : "minecraft:melon_slice", + "id" : 272 + }, + { + "name" : "minecraft:melon_stem", + "id" : 105 + }, + { + "name" : "minecraft:milk_bucket", + "id" : 361 + }, + { + "name" : "minecraft:minecart", + "id" : 370 + }, + { + "name" : "minecraft:mob_spawner", + "id" : 52 + }, + { + "name" : "minecraft:mojang_banner_pattern", + "id" : 584 + }, + { + "name" : "minecraft:monster_egg", + "id" : 97 + }, + { + "name" : "minecraft:mooshroom_spawn_egg", + "id" : 440 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 + }, + { + "name" : "minecraft:mossy_cobblestone", + "id" : 48 + }, + { + "name" : "minecraft:mossy_cobblestone_stairs", + "id" : -179 + }, + { + "name" : "minecraft:mossy_stone_brick_stairs", + "id" : -175 + }, + { + "name" : "minecraft:moving_block", + "id" : 250 + }, + { + "name" : "minecraft:mud", + "id" : -473 + }, + { + "name" : "minecraft:mud_brick_double_slab", + "id" : -479 + }, + { + "name" : "minecraft:mud_brick_slab", + "id" : -478 + }, + { + "name" : "minecraft:mud_brick_stairs", + "id" : -480 + }, + { + "name" : "minecraft:mud_brick_wall", + "id" : -481 + }, + { + "name" : "minecraft:mud_bricks", + "id" : -475 + }, + { + "name" : "minecraft:muddy_mangrove_roots", + "id" : -483 + }, + { + "name" : "minecraft:mule_spawn_egg", + "id" : 466 + }, + { + "name" : "minecraft:mushroom_stew", + "id" : 260 + }, + { + "name" : "minecraft:music_disc_11", + "id" : 544 + }, + { + "name" : "minecraft:music_disc_13", + "id" : 534 + }, + { + "name" : "minecraft:music_disc_5", + "id" : 636 + }, + { + "name" : "minecraft:music_disc_blocks", + "id" : 536 + }, + { + "name" : "minecraft:music_disc_cat", + "id" : 535 + }, + { + "name" : "minecraft:music_disc_chirp", + "id" : 537 + }, + { + "name" : "minecraft:music_disc_far", + "id" : 538 + }, + { + "name" : "minecraft:music_disc_mall", + "id" : 539 + }, + { + "name" : "minecraft:music_disc_mellohi", + "id" : 540 + }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 626 + }, + { + "name" : "minecraft:music_disc_pigstep", + "id" : 620 + }, + { + "name" : "minecraft:music_disc_stal", + "id" : 541 + }, + { + "name" : "minecraft:music_disc_strad", + "id" : 542 + }, + { + "name" : "minecraft:music_disc_wait", + "id" : 545 + }, + { + "name" : "minecraft:music_disc_ward", + "id" : 543 + }, + { + "name" : "minecraft:mutton", + "id" : 550 + }, + { + "name" : "minecraft:mycelium", + "id" : 110 + }, + { + "name" : "minecraft:name_tag", + "id" : 548 + }, + { + "name" : "minecraft:nautilus_shell", + "id" : 570 + }, + { + "name" : "minecraft:nether_brick", + "id" : 112 + }, + { + "name" : "minecraft:nether_brick_fence", + "id" : 113 + }, + { + "name" : "minecraft:nether_brick_stairs", + "id" : 114 + }, + { + "name" : "minecraft:nether_gold_ore", + "id" : -288 + }, + { + "name" : "minecraft:nether_sprouts", + "id" : 621 + }, + { + "name" : "minecraft:nether_star", + "id" : 518 + }, + { + "name" : "minecraft:nether_wart", + "id" : 294 + }, + { + "name" : "minecraft:nether_wart_block", + "id" : 214 + }, + { + "name" : "minecraft:netherbrick", + "id" : 523 + }, + { + "name" : "minecraft:netherite_axe", + "id" : 607 + }, + { + "name" : "minecraft:netherite_block", + "id" : -270 + }, + { + "name" : "minecraft:netherite_boots", + "id" : 612 + }, + { + "name" : "minecraft:netherite_chestplate", + "id" : 610 + }, + { + "name" : "minecraft:netherite_helmet", + "id" : 609 + }, + { + "name" : "minecraft:netherite_hoe", + "id" : 608 + }, + { + "name" : "minecraft:netherite_ingot", + "id" : 603 + }, + { + "name" : "minecraft:netherite_leggings", + "id" : 611 + }, + { + "name" : "minecraft:netherite_pickaxe", + "id" : 606 + }, + { + "name" : "minecraft:netherite_scrap", + "id" : 613 + }, + { + "name" : "minecraft:netherite_shovel", + "id" : 605 + }, + { + "name" : "minecraft:netherite_sword", + "id" : 604 + }, + { + "name" : "minecraft:netherrack", + "id" : 87 + }, + { + "name" : "minecraft:netherreactor", + "id" : 247 + }, + { + "name" : "minecraft:normal_stone_stairs", + "id" : -180 + }, + { + "name" : "minecraft:noteblock", + "id" : 25 + }, + { + "name" : "minecraft:npc_spawn_egg", + "id" : 470 + }, + { + "name" : "minecraft:oak_boat", + "id" : 375 + }, + { + "name" : "minecraft:oak_chest_boat", + "id" : 638 + }, + { + "name" : "minecraft:oak_sign", + "id" : 358 + }, + { + "name" : "minecraft:oak_stairs", + "id" : 53 + }, + { + "name" : "minecraft:observer", + "id" : 251 + }, + { + "name" : "minecraft:obsidian", + "id" : 49 + }, + { + "name" : "minecraft:ocelot_spawn_egg", + "id" : 451 + }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, + { + "name" : "minecraft:orange_candle", + "id" : -414 + }, + { + "name" : "minecraft:orange_candle_cake", + "id" : -431 + }, + { + "name" : "minecraft:orange_dye", + "id" : 409 + }, + { + "name" : "minecraft:orange_glazed_terracotta", + "id" : 221 + }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "name" : "minecraft:oxidized_cut_copper", + "id" : -350 + }, + { + "name" : "minecraft:oxidized_cut_copper_slab", + "id" : -364 + }, + { + "name" : "minecraft:oxidized_cut_copper_stairs", + "id" : -357 + }, + { + "name" : "minecraft:oxidized_double_cut_copper_slab", + "id" : -371 + }, + { + "name" : "minecraft:packed_ice", + "id" : 174 + }, + { + "name" : "minecraft:packed_mud", + "id" : -477 + }, + { + "name" : "minecraft:painting", + "id" : 357 + }, + { + "name" : "minecraft:panda_spawn_egg", + "id" : 489 + }, + { + "name" : "minecraft:paper", + "id" : 386 + }, + { + "name" : "minecraft:parrot_spawn_egg", + "id" : 478 + }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, + { + "name" : "minecraft:phantom_membrane", + "id" : 574 + }, + { + "name" : "minecraft:phantom_spawn_egg", + "id" : 486 + }, + { + "name" : "minecraft:pig_spawn_egg", + "id" : 437 + }, + { + "name" : "minecraft:piglin_banner_pattern", + "id" : 587 + }, + { + "name" : "minecraft:piglin_brute_spawn_egg", + "id" : 499 + }, + { + "name" : "minecraft:piglin_spawn_egg", + "id" : 497 + }, + { + "name" : "minecraft:pillager_spawn_egg", + "id" : 491 + }, + { + "name" : "minecraft:pink_candle", + "id" : -419 + }, + { + "name" : "minecraft:pink_candle_cake", + "id" : -436 + }, + { + "name" : "minecraft:pink_dye", + "id" : 404 + }, + { + "name" : "minecraft:pink_glazed_terracotta", + "id" : 226 + }, + { + "name" : "minecraft:piston", + "id" : 33 + }, + { + "name" : "minecraft:piston_arm_collision", + "id" : 34 + }, + { + "name" : "minecraft:planks", + "id" : 5 + }, + { + "name" : "minecraft:podzol", + "id" : 243 + }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, + { + "name" : "minecraft:poisonous_potato", + "id" : 282 + }, + { + "name" : "minecraft:polar_bear_spawn_egg", + "id" : 472 + }, + { + "name" : "minecraft:polished_andesite_stairs", + "id" : -174 + }, + { + "name" : "minecraft:polished_basalt", + "id" : -235 + }, + { + "name" : "minecraft:polished_blackstone", + "id" : -291 + }, + { + "name" : "minecraft:polished_blackstone_brick_double_slab", + "id" : -285 + }, + { + "name" : "minecraft:polished_blackstone_brick_slab", + "id" : -284 + }, + { + "name" : "minecraft:polished_blackstone_brick_stairs", + "id" : -275 + }, + { + "name" : "minecraft:polished_blackstone_brick_wall", + "id" : -278 + }, + { + "name" : "minecraft:polished_blackstone_bricks", + "id" : -274 + }, + { + "name" : "minecraft:polished_blackstone_button", + "id" : -296 + }, + { + "name" : "minecraft:polished_blackstone_double_slab", + "id" : -294 + }, + { + "name" : "minecraft:polished_blackstone_pressure_plate", + "id" : -295 + }, + { + "name" : "minecraft:polished_blackstone_slab", + "id" : -293 + }, + { + "name" : "minecraft:polished_blackstone_stairs", + "id" : -292 + }, + { + "name" : "minecraft:polished_blackstone_wall", + "id" : -297 + }, + { + "name" : "minecraft:polished_deepslate", + "id" : -383 + }, + { + "name" : "minecraft:polished_deepslate_double_slab", + "id" : -397 + }, + { + "name" : "minecraft:polished_deepslate_slab", + "id" : -384 + }, + { + "name" : "minecraft:polished_deepslate_stairs", + "id" : -385 + }, + { + "name" : "minecraft:polished_deepslate_wall", + "id" : -386 + }, + { + "name" : "minecraft:polished_diorite_stairs", + "id" : -173 + }, + { + "name" : "minecraft:polished_granite_stairs", + "id" : -172 + }, + { + "name" : "minecraft:popped_chorus_fruit", + "id" : 559 + }, + { + "name" : "minecraft:porkchop", + "id" : 262 + }, + { + "name" : "minecraft:portal", + "id" : 90 + }, + { + "name" : "minecraft:potato", + "id" : 280 + }, + { + "name" : "minecraft:potatoes", + "id" : 142 + }, + { + "name" : "minecraft:potion", + "id" : 426 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 + }, + { + "name" : "minecraft:powered_comparator", + "id" : 150 + }, + { + "name" : "minecraft:powered_repeater", + "id" : 94 + }, + { + "name" : "minecraft:prismarine", + "id" : 168 + }, + { + "name" : "minecraft:prismarine_bricks_stairs", + "id" : -4 + }, + { + "name" : "minecraft:prismarine_crystals", + "id" : 549 + }, + { + "name" : "minecraft:prismarine_shard", + "id" : 565 + }, + { + "name" : "minecraft:prismarine_stairs", + "id" : -2 + }, + { + "name" : "minecraft:pufferfish", + "id" : 267 + }, + { + "name" : "minecraft:pufferfish_bucket", + "id" : 367 + }, + { + "name" : "minecraft:pufferfish_spawn_egg", + "id" : 481 + }, + { + "name" : "minecraft:pumpkin", + "id" : 86 + }, + { + "name" : "minecraft:pumpkin_pie", + "id" : 284 + }, + { + "name" : "minecraft:pumpkin_seeds", + "id" : 292 + }, + { + "name" : "minecraft:pumpkin_stem", + "id" : 104 + }, + { + "name" : "minecraft:purple_candle", + "id" : -423 + }, + { + "name" : "minecraft:purple_candle_cake", + "id" : -440 + }, + { + "name" : "minecraft:purple_dye", + "id" : 400 + }, + { + "name" : "minecraft:purple_glazed_terracotta", + "id" : 219 + }, + { + "name" : "minecraft:purpur_block", + "id" : 201 + }, + { + "name" : "minecraft:purpur_stairs", + "id" : 203 + }, + { + "name" : "minecraft:quartz", + "id" : 524 + }, + { + "name" : "minecraft:quartz_block", + "id" : 155 + }, + { + "name" : "minecraft:quartz_bricks", + "id" : -304 + }, + { + "name" : "minecraft:quartz_ore", + "id" : 153 + }, + { + "name" : "minecraft:quartz_stairs", + "id" : 156 + }, + { + "name" : "minecraft:rabbit", + "id" : 288 + }, + { + "name" : "minecraft:rabbit_foot", + "id" : 528 + }, + { + "name" : "minecraft:rabbit_hide", + "id" : 529 + }, + { + "name" : "minecraft:rabbit_spawn_egg", + "id" : 459 + }, + { + "name" : "minecraft:rabbit_stew", + "id" : 290 + }, + { + "name" : "minecraft:rail", + "id" : 66 + }, + { + "name" : "minecraft:rapid_fertilizer", + "id" : 597 + }, + { + "name" : "minecraft:ravager_spawn_egg", + "id" : 493 + }, + { + "name" : "minecraft:raw_copper", + "id" : 507 + }, + { + "name" : "minecraft:raw_copper_block", + "id" : -452 + }, + { + "name" : "minecraft:raw_gold", + "id" : 506 + }, + { + "name" : "minecraft:raw_gold_block", + "id" : -453 + }, + { + "name" : "minecraft:raw_iron", + "id" : 505 + }, + { + "name" : "minecraft:raw_iron_block", + "id" : -451 + }, + { + "name" : "minecraft:recovery_compass", + "id" : 646 + }, + { + "name" : "minecraft:red_candle", + "id" : -427 + }, + { + "name" : "minecraft:red_candle_cake", + "id" : -444 + }, + { + "name" : "minecraft:red_dye", + "id" : 396 + }, + { + "name" : "minecraft:red_flower", + "id" : 38 + }, + { + "name" : "minecraft:red_glazed_terracotta", + "id" : 234 + }, + { + "name" : "minecraft:red_mushroom", + "id" : 40 + }, + { + "name" : "minecraft:red_mushroom_block", + "id" : 100 + }, + { + "name" : "minecraft:red_nether_brick", + "id" : 215 + }, + { + "name" : "minecraft:red_nether_brick_stairs", + "id" : -184 + }, + { + "name" : "minecraft:red_sandstone", + "id" : 179 + }, + { + "name" : "minecraft:red_sandstone_stairs", + "id" : 180 + }, + { + "name" : "minecraft:redstone", + "id" : 373 + }, + { + "name" : "minecraft:redstone_block", + "id" : 152 + }, + { + "name" : "minecraft:redstone_lamp", + "id" : 123 + }, + { + "name" : "minecraft:redstone_ore", + "id" : 73 + }, + { + "name" : "minecraft:redstone_torch", + "id" : 76 + }, + { + "name" : "minecraft:redstone_wire", + "id" : 55 + }, + { + "name" : "minecraft:reinforced_deepslate", + "id" : -466 + }, + { + "name" : "minecraft:repeater", + "id" : 419 + }, + { + "name" : "minecraft:repeating_command_block", + "id" : 188 + }, + { + "name" : "minecraft:reserved6", + "id" : 255 + }, + { + "name" : "minecraft:respawn_anchor", + "id" : -272 + }, + { + "name" : "minecraft:rotten_flesh", + "id" : 277 + }, + { + "name" : "minecraft:saddle", + "id" : 371 + }, + { + "name" : "minecraft:salmon", + "id" : 265 + }, + { + "name" : "minecraft:salmon_bucket", + "id" : 365 + }, + { + "name" : "minecraft:salmon_spawn_egg", + "id" : 482 + }, + { + "name" : "minecraft:sand", + "id" : 12 + }, + { + "name" : "minecraft:sandstone", + "id" : 24 + }, + { + "name" : "minecraft:sandstone_stairs", + "id" : 128 + }, + { + "name" : "minecraft:sapling", + "id" : 6 + }, + { + "name" : "minecraft:scaffolding", + "id" : -165 + }, + { + "name" : "minecraft:sculk", + "id" : -458 + }, + { + "name" : "minecraft:sculk_catalyst", + "id" : -460 + }, + { + "name" : "minecraft:sculk_sensor", + "id" : -307 + }, + { + "name" : "minecraft:sculk_shrieker", + "id" : -461 + }, + { + "name" : "minecraft:sculk_vein", + "id" : -459 + }, + { + "name" : "minecraft:scute", + "id" : 572 + }, + { + "name" : "minecraft:sea_lantern", + "id" : 169 + }, + { + "name" : "minecraft:sea_pickle", + "id" : -156 + }, + { + "name" : "minecraft:seagrass", + "id" : -130 + }, + { + "name" : "minecraft:shears", + "id" : 421 + }, + { + "name" : "minecraft:sheep_spawn_egg", + "id" : 438 + }, + { + "name" : "minecraft:shield", + "id" : 355 + }, + { + "name" : "minecraft:shroomlight", + "id" : -230 + }, + { + "name" : "minecraft:shulker_box", + "id" : 218 + }, + { + "name" : "minecraft:shulker_shell", + "id" : 566 + }, + { + "name" : "minecraft:shulker_spawn_egg", + "id" : 469 + }, + { + "name" : "minecraft:silver_glazed_terracotta", + "id" : 228 + }, + { + "name" : "minecraft:silverfish_spawn_egg", + "id" : 443 + }, + { + "name" : "minecraft:skeleton_horse_spawn_egg", + "id" : 467 + }, + { + "name" : "minecraft:skeleton_spawn_egg", + "id" : 444 + }, + { + "name" : "minecraft:skull", + "id" : 516 + }, + { + "name" : "minecraft:skull_banner_pattern", + "id" : 583 + }, + { + "name" : "minecraft:slime", + "id" : 165 + }, + { + "name" : "minecraft:slime_ball", + "id" : 388 + }, + { + "name" : "minecraft:slime_spawn_egg", + "id" : 445 + }, + { + "name" : "minecraft:small_amethyst_bud", + "id" : -332 + }, + { + "name" : "minecraft:small_dripleaf_block", + "id" : -336 + }, + { + "name" : "minecraft:smithing_table", + "id" : -202 + }, + { + "name" : "minecraft:smoker", + "id" : -198 + }, + { + "name" : "minecraft:smooth_basalt", + "id" : -377 + }, + { + "name" : "minecraft:smooth_quartz_stairs", + "id" : -185 + }, + { + "name" : "minecraft:smooth_red_sandstone_stairs", + "id" : -176 + }, + { + "name" : "minecraft:smooth_sandstone_stairs", + "id" : -177 + }, + { + "name" : "minecraft:smooth_stone", + "id" : -183 + }, + { + "name" : "minecraft:snow", + "id" : 80 + }, + { + "name" : "minecraft:snow_layer", + "id" : 78 + }, + { + "name" : "minecraft:snowball", + "id" : 374 + }, + { + "name" : "minecraft:soul_campfire", + "id" : 622 + }, + { + "name" : "minecraft:soul_fire", + "id" : -237 + }, + { + "name" : "minecraft:soul_lantern", + "id" : -269 + }, + { + "name" : "minecraft:soul_sand", + "id" : 88 + }, + { + "name" : "minecraft:soul_soil", + "id" : -236 + }, + { + "name" : "minecraft:soul_torch", + "id" : -268 + }, + { + "name" : "minecraft:sparkler", + "id" : 600 + }, + { + "name" : "minecraft:spawn_egg", + "id" : 652 + }, + { + "name" : "minecraft:spider_eye", + "id" : 278 + }, + { + "name" : "minecraft:spider_spawn_egg", + "id" : 446 + }, + { + "name" : "minecraft:splash_potion", + "id" : 561 + }, + { + "name" : "minecraft:sponge", + "id" : 19 + }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, + { + "name" : "minecraft:spruce_boat", + "id" : 378 + }, + { + "name" : "minecraft:spruce_button", + "id" : -144 + }, + { + "name" : "minecraft:spruce_chest_boat", + "id" : 641 + }, + { + "name" : "minecraft:spruce_door", + "id" : 553 + }, + { + "name" : "minecraft:spruce_fence_gate", + "id" : 183 + }, + { + "name" : "minecraft:spruce_pressure_plate", + "id" : -154 + }, + { + "name" : "minecraft:spruce_sign", + "id" : 576 + }, + { + "name" : "minecraft:spruce_stairs", + "id" : 134 + }, + { + "name" : "minecraft:spruce_standing_sign", + "id" : -181 + }, + { + "name" : "minecraft:spruce_trapdoor", + "id" : -149 + }, + { + "name" : "minecraft:spruce_wall_sign", + "id" : -182 + }, + { + "name" : "minecraft:spyglass", + "id" : 625 + }, + { + "name" : "minecraft:squid_spawn_egg", + "id" : 450 + }, + { + "name" : "minecraft:stained_glass", + "id" : 241 + }, + { + "name" : "minecraft:stained_glass_pane", + "id" : 160 + }, + { + "name" : "minecraft:stained_hardened_clay", + "id" : 159 + }, + { + "name" : "minecraft:standing_banner", + "id" : 176 + }, + { + "name" : "minecraft:standing_sign", + "id" : 63 + }, + { + "name" : "minecraft:stick", + "id" : 320 + }, + { + "name" : "minecraft:sticky_piston", + "id" : 29 + }, + { + "name" : "minecraft:sticky_piston_arm_collision", + "id" : -217 + }, + { + "name" : "minecraft:stone", + "id" : 1 + }, + { + "name" : "minecraft:stone_axe", + "id" : 315 + }, + { + "name" : "minecraft:stone_block_slab", + "id" : 44 + }, + { + "name" : "minecraft:stone_block_slab2", + "id" : 182 + }, + { + "name" : "minecraft:stone_block_slab3", + "id" : -162 + }, + { + "name" : "minecraft:stone_block_slab4", + "id" : -166 + }, + { + "name" : "minecraft:stone_brick_stairs", + "id" : 109 + }, + { + "name" : "minecraft:stone_button", + "id" : 77 + }, + { + "name" : "minecraft:stone_hoe", + "id" : 330 + }, + { + "name" : "minecraft:stone_pickaxe", + "id" : 314 + }, + { + "name" : "minecraft:stone_pressure_plate", + "id" : 70 + }, + { + "name" : "minecraft:stone_shovel", + "id" : 313 + }, + { + "name" : "minecraft:stone_stairs", + "id" : 67 + }, + { + "name" : "minecraft:stone_sword", + "id" : 312 + }, + { + "name" : "minecraft:stonebrick", + "id" : 98 + }, + { + "name" : "minecraft:stonecutter", + "id" : 245 + }, + { + "name" : "minecraft:stonecutter_block", + "id" : -197 + }, + { + "name" : "minecraft:stray_spawn_egg", + "id" : 462 + }, + { + "name" : "minecraft:strider_spawn_egg", + "id" : 495 + }, + { + "name" : "minecraft:string", + "id" : 326 + }, + { + "name" : "minecraft:stripped_acacia_log", + "id" : -8 + }, + { + "name" : "minecraft:stripped_birch_log", + "id" : -6 + }, + { + "name" : "minecraft:stripped_crimson_hyphae", + "id" : -300 + }, + { + "name" : "minecraft:stripped_crimson_stem", + "id" : -240 + }, + { + "name" : "minecraft:stripped_dark_oak_log", + "id" : -9 + }, + { + "name" : "minecraft:stripped_jungle_log", + "id" : -7 + }, + { + "name" : "minecraft:stripped_mangrove_log", + "id" : -485 + }, + { + "name" : "minecraft:stripped_mangrove_wood", + "id" : -498 + }, + { + "name" : "minecraft:stripped_oak_log", + "id" : -10 + }, + { + "name" : "minecraft:stripped_spruce_log", + "id" : -5 + }, + { + "name" : "minecraft:stripped_warped_hyphae", + "id" : -301 + }, + { + "name" : "minecraft:stripped_warped_stem", + "id" : -241 + }, + { + "name" : "minecraft:structure_block", + "id" : 252 + }, + { + "name" : "minecraft:structure_void", + "id" : 217 + }, + { + "name" : "minecraft:sugar", + "id" : 416 + }, + { + "name" : "minecraft:sugar_cane", + "id" : 385 + }, + { + "name" : "minecraft:suspicious_stew", + "id" : 590 + }, + { + "name" : "minecraft:sweet_berries", + "id" : 287 + }, + { + "name" : "minecraft:sweet_berry_bush", + "id" : -207 + }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 630 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 629 + }, + { + "name" : "minecraft:tallgrass", + "id" : 31 + }, + { + "name" : "minecraft:target", + "id" : -239 + }, + { + "name" : "minecraft:tinted_glass", + "id" : -334 + }, + { + "name" : "minecraft:tnt", + "id" : 46 + }, + { + "name" : "minecraft:tnt_minecart", + "id" : 525 + }, + { + "name" : "minecraft:torch", + "id" : 50 + }, + { + "name" : "minecraft:totem_of_undying", + "id" : 568 + }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 648 + }, + { + "name" : "minecraft:trapdoor", + "id" : 96 + }, + { + "name" : "minecraft:trapped_chest", + "id" : 146 + }, + { + "name" : "minecraft:trident", + "id" : 546 + }, + { + "name" : "minecraft:trip_wire", + "id" : 132 + }, + { + "name" : "minecraft:tripwire_hook", + "id" : 131 + }, + { + "name" : "minecraft:tropical_fish", + "id" : 266 + }, + { + "name" : "minecraft:tropical_fish_bucket", + "id" : 366 + }, + { + "name" : "minecraft:tropical_fish_spawn_egg", + "id" : 479 + }, + { + "name" : "minecraft:tuff", + "id" : -333 + }, + { + "name" : "minecraft:turtle_egg", + "id" : -159 + }, + { + "name" : "minecraft:turtle_helmet", + "id" : 573 + }, + { + "name" : "minecraft:turtle_spawn_egg", + "id" : 485 + }, + { + "name" : "minecraft:twisting_vines", + "id" : -287 + }, + { + "name" : "minecraft:underwater_torch", + "id" : 239 + }, + { + "name" : "minecraft:undyed_shulker_box", + "id" : 205 + }, + { + "name" : "minecraft:unknown", + "id" : -305 + }, + { + "name" : "minecraft:unlit_redstone_torch", + "id" : 75 + }, + { + "name" : "minecraft:unpowered_comparator", + "id" : 149 + }, + { + "name" : "minecraft:unpowered_repeater", + "id" : 93 + }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, + { + "name" : "minecraft:vex_spawn_egg", + "id" : 476 + }, + { + "name" : "minecraft:villager_spawn_egg", + "id" : 449 + }, + { + "name" : "minecraft:vindicator_spawn_egg", + "id" : 474 + }, + { + "name" : "minecraft:vine", + "id" : 106 + }, + { + "name" : "minecraft:wall_banner", + "id" : 177 + }, + { + "name" : "minecraft:wall_sign", + "id" : 68 + }, + { + "name" : "minecraft:wandering_trader_spawn_egg", + "id" : 492 + }, + { + "name" : "minecraft:warden_spawn_egg", + "id" : 632 + }, + { + "name" : "minecraft:warped_button", + "id" : -261 + }, + { + "name" : "minecraft:warped_door", + "id" : 617 + }, + { + "name" : "minecraft:warped_double_slab", + "id" : -267 + }, + { + "name" : "minecraft:warped_fence", + "id" : -257 + }, + { + "name" : "minecraft:warped_fence_gate", + "id" : -259 + }, + { + "name" : "minecraft:warped_fungus", + "id" : -229 + }, + { + "name" : "minecraft:warped_fungus_on_a_stick", + "id" : 618 + }, + { + "name" : "minecraft:warped_hyphae", + "id" : -298 + }, + { + "name" : "minecraft:warped_nylium", + "id" : -233 + }, + { + "name" : "minecraft:warped_planks", + "id" : -243 + }, + { + "name" : "minecraft:warped_pressure_plate", + "id" : -263 + }, + { + "name" : "minecraft:warped_roots", + "id" : -224 + }, + { + "name" : "minecraft:warped_sign", + "id" : 615 + }, + { + "name" : "minecraft:warped_slab", + "id" : -265 + }, + { + "name" : "minecraft:warped_stairs", + "id" : -255 + }, + { + "name" : "minecraft:warped_standing_sign", + "id" : -251 + }, + { + "name" : "minecraft:warped_stem", + "id" : -226 + }, + { + "name" : "minecraft:warped_trapdoor", + "id" : -247 + }, + { + "name" : "minecraft:warped_wall_sign", + "id" : -253 + }, + { + "name" : "minecraft:warped_wart_block", + "id" : -227 + }, + { + "name" : "minecraft:water", + "id" : 9 + }, + { + "name" : "minecraft:water_bucket", + "id" : 362 + }, + { + "name" : "minecraft:waterlily", + "id" : 111 + }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "name" : "minecraft:waxed_cut_copper", + "id" : -351 + }, + { + "name" : "minecraft:waxed_cut_copper_slab", + "id" : -365 + }, + { + "name" : "minecraft:waxed_cut_copper_stairs", + "id" : -358 + }, + { + "name" : "minecraft:waxed_double_cut_copper_slab", + "id" : -372 + }, + { + "name" : "minecraft:waxed_exposed_copper", + "id" : -345 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper", + "id" : -352 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_slab", + "id" : -366 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_stairs", + "id" : -359 + }, + { + "name" : "minecraft:waxed_exposed_double_cut_copper_slab", + "id" : -373 + }, + { + "name" : "minecraft:waxed_oxidized_copper", + "id" : -446 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper", + "id" : -447 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_slab", + "id" : -449 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_stairs", + "id" : -448 + }, + { + "name" : "minecraft:waxed_oxidized_double_cut_copper_slab", + "id" : -450 + }, + { + "name" : "minecraft:waxed_weathered_copper", + "id" : -346 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper", + "id" : -353 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_slab", + "id" : -367 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_stairs", + "id" : -360 + }, + { + "name" : "minecraft:waxed_weathered_double_cut_copper_slab", + "id" : -374 + }, + { + "name" : "minecraft:weathered_copper", + "id" : -342 + }, + { + "name" : "minecraft:weathered_cut_copper", + "id" : -349 + }, + { + "name" : "minecraft:weathered_cut_copper_slab", + "id" : -363 + }, + { + "name" : "minecraft:weathered_cut_copper_stairs", + "id" : -356 + }, + { + "name" : "minecraft:weathered_double_cut_copper_slab", + "id" : -370 + }, + { + "name" : "minecraft:web", + "id" : 30 + }, + { + "name" : "minecraft:weeping_vines", + "id" : -231 + }, + { + "name" : "minecraft:wheat", + "id" : 334 + }, + { + "name" : "minecraft:wheat_seeds", + "id" : 291 + }, + { + "name" : "minecraft:white_candle", + "id" : -413 + }, + { + "name" : "minecraft:white_candle_cake", + "id" : -430 + }, + { + "name" : "minecraft:white_dye", + "id" : 410 + }, + { + "name" : "minecraft:white_glazed_terracotta", + "id" : 220 + }, + { + "name" : "minecraft:witch_spawn_egg", + "id" : 452 + }, + { + "name" : "minecraft:wither_rose", + "id" : -216 + }, + { + "name" : "minecraft:wither_skeleton_spawn_egg", + "id" : 464 + }, + { + "name" : "minecraft:wolf_spawn_egg", + "id" : 439 + }, + { + "name" : "minecraft:wood", + "id" : -212 + }, + { + "name" : "minecraft:wooden_axe", + "id" : 311 + }, + { + "name" : "minecraft:wooden_button", + "id" : 143 + }, + { + "name" : "minecraft:wooden_door", + "id" : 359 + }, + { + "name" : "minecraft:wooden_hoe", + "id" : 329 + }, + { + "name" : "minecraft:wooden_pickaxe", + "id" : 310 + }, + { + "name" : "minecraft:wooden_pressure_plate", + "id" : 72 + }, + { + "name" : "minecraft:wooden_shovel", + "id" : 309 + }, + { + "name" : "minecraft:wooden_slab", + "id" : 158 + }, + { + "name" : "minecraft:wooden_sword", + "id" : 308 + }, + { + "name" : "minecraft:wool", + "id" : 35 + }, + { + "name" : "minecraft:writable_book", + "id" : 510 + }, + { + "name" : "minecraft:written_book", + "id" : 511 + }, + { + "name" : "minecraft:yellow_candle", + "id" : -417 + }, + { + "name" : "minecraft:yellow_candle_cake", + "id" : -434 + }, + { + "name" : "minecraft:yellow_dye", + "id" : 406 + }, + { + "name" : "minecraft:yellow_flower", + "id" : 37 + }, + { + "name" : "minecraft:yellow_glazed_terracotta", + "id" : 224 + }, + { + "name" : "minecraft:zoglin_spawn_egg", + "id" : 498 + }, + { + "name" : "minecraft:zombie_horse_spawn_egg", + "id" : 468 + }, + { + "name" : "minecraft:zombie_pigman_spawn_egg", + "id" : 448 + }, + { + "name" : "minecraft:zombie_spawn_egg", + "id" : 447 + }, + { + "name" : "minecraft:zombie_villager_spawn_egg", + "id" : 477 + } +] \ No newline at end of file From eab92da98884308b6e4b4a5deb1de97b2fbd3c6e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 9 Aug 2022 19:07:11 -0400 Subject: [PATCH 186/358] Fix form responses on 1.19.20 --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 88bde33600b..372b5769aae 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -129,7 +129,7 @@ com.github.CloudburstMC.Protocol bedrock-v544 - 92d9854 + 0bd459f compile From 88727fb473fff1f1040c34ac64c52a2d4153880c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 9 Aug 2022 20:30:49 -0400 Subject: [PATCH 187/358] Bump Geyser to version 2.0.7-SNAPSHOT and default Bedrock to 1.19.20 --- ap/pom.xml | 4 ++-- api/base/pom.xml | 2 +- api/geyser/pom.xml | 4 ++-- api/pom.xml | 2 +- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- core/pom.xml | 8 ++++---- .../org/geysermc/geyser/network/MinecraftProtocol.java | 6 +++--- pom.xml | 2 +- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ap/pom.xml b/ap/pom.xml index 90bb1dc7397..feb77e92240 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT ap - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index 0eeb536ea70..4e172650edd 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT 4.0.0 diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 0071668bf4a..9aa8560d1d8 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index 9b4816954ec..79e999c1698 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 4ec01539fcd..5a1e8e262d7 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 0da8638114e..371ed9bcae2 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 25bcb23f908..5142d2bc374 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-spigot @@ -30,7 +30,7 @@ org.geysermc core - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 25f709ec40d..fc7bbc624e2 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 5d27c8a2a2d..5577f920684 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 0c530b21e03..35e6df15bef 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 5326ca01409..0b11532305b 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT common diff --git a/core/pom.xml b/core/pom.xml index 372b5769aae..489f99cb318 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT core @@ -29,19 +29,19 @@ org.geysermc ap - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT provided org.geysermc geyser-api - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile org.geysermc common - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT compile diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index e0a06b5e38e..3452ec7d571 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -45,7 +45,7 @@ public final class MinecraftProtocol { * 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_v544.V544_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -61,10 +61,10 @@ public final class MinecraftProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.V527_CODEC.toBuilder() .minecraftVersion("1.19.0/1.19.2") .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v534.V534_CODEC.toBuilder() .minecraftVersion("1.19.10/1.19.11") .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/pom.xml b/pom.xml index 1d99d93e093..c5b293c436a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.6-SNAPSHOT + 2.0.7-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From ef8130e7c081a19d9ec5ba9c0c58b0584f5e6b0b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 9 Aug 2022 20:31:29 -0400 Subject: [PATCH 188/358] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49aba79ac34..42979bdbe9b 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.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0/1.19.1x/1.19.20 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. From 25a18a2e4f77adde68e61a7dbdf8039f9ef6ea33 Mon Sep 17 00:00:00 2001 From: David Choo Date: Tue, 9 Aug 2022 21:35:26 -0400 Subject: [PATCH 189/358] Fix maps not loading in Bedrock (#3218) --- .../main/java/org/geysermc/geyser/level/BedrockMapIcon.java | 5 +++-- .../protocol/java/level/JavaMapItemDataTranslator.java | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) 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/translator/protocol/java/level/JavaMapItemDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java index eb658aa5400..495455958b8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java @@ -28,6 +28,7 @@ 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.session.GeyserSession; @@ -48,6 +49,7 @@ 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()); MapData data = packet.getData(); From ab2b79485b7c2eeccd1c4994260bf631af712c45 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 10 Aug 2022 16:09:55 -0400 Subject: [PATCH 190/358] Fix sending forms with floodgate for 1.19.20 (#3217) * Fix sending forms with floodgate * Comment about 1.19.20 * Swapped if-else Co-authored-by: Tim203 --- .../java/JavaCustomPayloadTranslator.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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..e25285114e4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -72,12 +72,19 @@ public void translate(GeyserSession session, ClientboundCustomPayloadPacket pack String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8); Form form = Forms.fromJson(dataString, type, (ignored, response) -> { - 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)); }); From 678e285cd4a78fa9f2f513c299e02f6c6d62753f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 10 Aug 2022 22:12:05 +0200 Subject: [PATCH 191/358] Bump Cumulus version to 1.1.1 --- common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pom.xml b/common/pom.xml index 0b11532305b..67b77a98a61 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ org.geysermc.cumulus cumulus - 1.1 + 1.1.1 com.google.code.gson From 8b57a7c6918893179e097c6f8bc2296e63581b99 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 10 Aug 2022 22:27:24 +0200 Subject: [PATCH 192/358] Use StandardCharsets instead of Charsets --- .../floodgate/pluginmessage/PluginMessageChannels.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 2a2e63e51961b82a066fcd70d157fe84b3b46c0a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 10 Aug 2022 17:39:48 -0400 Subject: [PATCH 193/358] Explicitly set gson dependency in common --- build-logic/src/main/kotlin/Versions.kt | 1 + common/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 38ef82d26de..d9af831f087 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -28,6 +28,7 @@ object Versions { const val fastutilVersion = "8.5.2" const val nettyVersion = "4.1.66.Final" const val guavaVersion = "29.0-jre" + const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" const val protocolVersion = "0bd459f" diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 205b20c0e1d..6c14141054b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,3 +1,4 @@ dependencies { api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) + api("com.google.code.gson", "gson", Versions.gsonVersion) } \ No newline at end of file From 50ea5eac9a44f443bccf68c7bd5a30efe37b1e7f Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 10 Aug 2022 15:05:59 -0700 Subject: [PATCH 194/358] Set baby if armor stand is small for OptionalPack (#3210) --- .../org/geysermc/geyser/entity/type/living/ArmorStandEntity.java | 1 + 1 file changed, 1 insertion(+) 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) { From 80588a07bd39314cafab6cf84a779ab022ef6ae0 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 9 Aug 2022 20:06:53 +0200 Subject: [PATCH 195/358] Initial API changes --- api/base/build.gradle.kts | 6 ++- .../java/org/geysermc/api/GeyserApiBase.java | 50 ++++++++++++------- .../geysermc/api/connection/Connection.java | 21 ++++++-- .../org/geysermc/geyser/api/GeyserApi.java | 2 +- .../network/session/GeyserSession.java | 2 +- .../java/org/geysermc/geyser/GeyserImpl.java | 12 +++-- .../geyser/command/defaults/ListCommand.java | 2 +- .../geyser/scoreboard/ScoreboardUpdater.java | 2 +- .../geyser/session/GeyserSession.java | 16 +++--- .../geyser/session/cache/TagCache.java | 4 +- .../geyser/skin/FloodgateSkinUploader.java | 2 +- .../inventory/InventoryTranslator.java | 4 +- .../inventory/PlayerInventoryTranslator.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 4 +- .../player/BedrockMovePlayerTranslator.java | 2 +- .../JavaContainerSetContentTranslator.java | 2 +- .../JavaContainerSetSlotTranslator.java | 2 +- 18 files changed, 87 insertions(+), 50 deletions(-) diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index d7500fdaaf4..c9ddf44892b 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -1 +1,5 @@ -provided("net.kyori", "event-api", Versions.eventVersion) \ No newline at end of file +dependencies { + api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) +} + +provided("net.kyori", "event-api", Versions.eventVersion) 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 e5105b1be43..e9957e21c6a 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,12 @@ 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.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 +40,65 @@ */ 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); + + boolean sendForm(UUID uuid, Form form); + + boolean sendForm(UUID uuid, FormBuilder formBuilder); + + boolean transfer(UUID uuid, String address, int port); + /** - * Gets all the online sessions. - * - * @return all the online sessions + * Returns all the online connections. */ @NonNull List onlineConnections(); /** - * @return the major API version. Bumped whenever a significant breaking change or feature addition is added. + * Returns the amount of online connections. + */ + int onlineConnectionsCount(); + + /** + * Returns the prefix used by Floodgate. Will be null when the auth-type isn't Floodgate. + */ + @MonotonicNonNull + String usernamePrefix(); + + /** + * Returns the major API version. Bumped whenever a significant breaking change or feature addition is added. */ default int majorApiVersion() { return 1; } /** - * @return the minor API version. May be bumped for new API additions. + * Returns the minor API version. May be bumped for new API additions. */ default int minorApiVersion() { return 0; diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java index fc6cdae201e..db09149e26e 100644 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/connection/Connection.java @@ -25,6 +25,7 @@ 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; @@ -33,27 +34,37 @@ /** * Represents a player connection. */ -@NonNull public interface Connection { /** - * Gets the name of the connection. + * Gets the bedrock name of the connection. * - * @return the name of the connection + * @return the bedrock name of the connection */ - String name(); + @NonNull + String bedrockUsername(); + + /** + * Gets the java name of the connection. + * + * @return the java name of the connection + */ + @MonotonicNonNull + String javaUsername(); /** * Gets the {@link UUID} of the connection. * * @return the UUID of the connection */ - UUID uuid(); + @MonotonicNonNull + UUID javaUuid(); /** * Gets the XUID of the connection. * * @return the XUID of the connection */ + @NonNull String xuid(); /** 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 16bfe7070a0..9b584b98535 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 @@ -59,7 +59,7 @@ public interface GeyserApi extends GeyserApiBase { * {@inheritDoc} */ @Override - @Nullable GeyserConnection connectionByName(@NonNull String name); + @Nullable GeyserConnection connectionByUsername(@NonNull String username); /** * {@inheritDoc} 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 21aa35efcad..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 @@ -128,7 +128,7 @@ public void executeInEventLoop(Runnable runnable) { } public String getName() { - return this.handle.name(); + return this.handle.bedrockUsername(); } public boolean isConsole() { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 276c5328d74..67a144f363c 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -465,9 +465,10 @@ private void start() { } @Override - public @Nullable GeyserSession connectionByName(@NonNull String name) { + public @Nullable GeyserSession connectionByUsername(@NonNull String username) { for (GeyserSession session : sessionManager.getAllSessions()) { - if (session.name().equals(name) || session.getProtocol().getProfile().getName().equals(name)) { + if (session.bedrockUsername().equals(username) || session.getProtocol().getProfile().getName().equals( + username)) { return session; } } @@ -477,7 +478,12 @@ private void start() { @Override public @NonNull List onlineConnections() { - return this.sessionManager.getAllSessions(); + return sessionManager.getAllSessions(); + } + + @Override + public int onlineConnectionsCount() { + return sessionManager.size(); } @Override 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 f911e431e29..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 @@ -47,7 +47,7 @@ public ListCommand(GeyserImpl geyser, String name, String description, String pe 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/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 031d8f7f56f..c8ae4792b61 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -735,7 +735,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; } @@ -747,7 +747,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) { @@ -838,7 +838,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; } } @@ -1071,7 +1071,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); } }); } @@ -1084,7 +1084,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); } @@ -1326,13 +1326,13 @@ protected void disableSrvResolving() { } @Override - public String name() { + public String bedrockUsername() { return authData.name(); } @Override - public UUID uuid() { - return authData.uuid(); + public UUID javaUuid() { + } @Override 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/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index 4d0e98444ff..7b6dacd16c0 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -114,7 +114,7 @@ public 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/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 6f4ca7ee4c4..fc442c329c3 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 @@ -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); 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..ee7d6a7c6df 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); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 815456132b2..521adb68706 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -546,7 +546,7 @@ private boolean isIncorrectHeldItem(GeyserSession session, InventoryTransactionP int heldItemId = packet.getItemInHand() == null ? ItemData.AIR.getId() : packet.getItemInHand().getId(); if (expectedItemId != heldItemId) { - session.getGeyser().getLogger().debug(session.name() + "'s held item has desynced! Expected: " + expectedItemId + " Received: " + heldItemId); + session.getGeyser().getLogger().debug(session.bedrockUsername() + "'s held item has desynced! Expected: " + expectedItemId + " Received: " + heldItemId); session.getGeyser().getLogger().debug("Packet: " + packet); return true; } 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 8b86be69b78..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 @@ -43,8 +43,8 @@ public void translate(GeyserSession session, SetLocalPlayerAsInitializedPacket p 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/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index b9f5939611e..035c14faf47 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 @@ -175,7 +175,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/inventory/JavaContainerSetContentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java index 0775028fecd..61982533813 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java @@ -48,7 +48,7 @@ public void translate(GeyserSession session, ClientboundContainerSetContentPacke for (int i = 0; i < packet.getItems().length; i++) { 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); 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..9a556939270 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 @@ -76,7 +76,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); From ab6e0d1e168d46e8883477998b1acc2cd17cb5d9 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 12 Aug 2022 01:01:26 +0200 Subject: [PATCH 196/358] Some more API changes --- .../java/org/geysermc/api/GeyserApiBase.java | 30 ++++- .../geysermc/api/connection/Connection.java | 76 +++++++++--- .../geysermc/api/util/BedrockPlatform.java | 70 +++++++++++ .../java/org/geysermc/api/util/InputMode.java | 46 +++++++ .../java/org/geysermc/api/util/UiProfile.java | 42 +++++++ .../java/org/geysermc/geyser/GeyserImpl.java | 43 ++++++- .../type/player/SessionPlayerEntity.java | 2 +- .../geyser/session/GeyserSession.java | 112 ++++++++++++------ .../geyser/session/SessionManager.java | 11 ++ 9 files changed, 372 insertions(+), 60 deletions(-) create mode 100644 api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java create mode 100644 api/base/src/main/java/org/geysermc/api/util/InputMode.java create mode 100644 api/base/src/main/java/org/geysermc/api/util/UiProfile.java 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 e9957e21c6a..a845e37fdf8 100644 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java @@ -28,6 +28,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; import org.geysermc.api.connection.Connection; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; @@ -66,11 +67,34 @@ public interface GeyserApiBase { */ boolean isBedrockPlayer(@NonNull UUID uuid); - boolean sendForm(UUID uuid, Form form); + /** + * 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); - boolean sendForm(UUID uuid, FormBuilder formBuilder); + /** + * Sends a form to the given connection and opens it. + * + * @param uuid the uuid of the connection to open it on + * @param formBuilder the formBuilder to send + * @return whether the form was successfully sent + */ + boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder); - boolean transfer(UUID uuid, String address, int port); + /** + * 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); /** diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java index db09149e26e..1cd7a9d1384 100644 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/connection/Connection.java @@ -28,6 +28,11 @@ 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; @@ -36,43 +41,80 @@ */ public interface Connection { /** - * Gets the bedrock name of the connection. - * - * @return the bedrock name of the connection + * Returns the bedrock name of the connection. */ - @NonNull - String bedrockUsername(); + @NonNull String bedrockUsername(); /** - * Gets the java name of the connection. - * - * @return the java name of the connection + * Returns the java name of the connection. */ @MonotonicNonNull String javaUsername(); /** - * Gets the {@link UUID} of the connection. - * - * @return the UUID of the connection + * Returns the UUID of the connection. */ @MonotonicNonNull UUID javaUuid(); /** - * Gets the XUID of the connection. + * 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. * - * @return the XUID of the connection + * @param formBuilder the formBuilder to send + * @return whether the form was successfully sent */ - @NonNull - String xuid(); + boolean sendForm(@NonNull FormBuilder formBuilder); /** * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are * currently playing on. * - * @param address The address of the server - * @param port The port of the server + * @param address the address of the server + * @param port the port of the server * @return true if the transfer was a success */ boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); diff --git a/api/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..d66077a87f0 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.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.api.util; + +public enum BedrockPlatform { + UNKNOWN("Unknown"), + GOOGLE("Android"), + IOS("iOS"), + OSX("macOS"), + AMAZON("Amazon"), + GEARVR("Gear VR"), + HOLOLENS("Hololens"), + UWP("Windows 10"), + 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 + */ + 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..eadb457abbc --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/InputMode.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.api.util; + +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 + */ + 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..c28ff869cd3 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.api.util; + +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 + */ + public static UiProfile fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 67a144f363c..94a2b43043e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -41,10 +41,13 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +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; @@ -486,6 +489,11 @@ public int onlineConnectionsCount() { return sessionManager.size(); } + @Override + public @MonotonicNonNull String usernamePrefix() { + return null; + } + @Override public @Nullable GeyserSession connectionByUuid(@NonNull UUID uuid) { return this.sessionManager.getSessions().get(uuid); @@ -493,13 +501,38 @@ public int onlineConnectionsCount() { @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() { 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..7a5d3497368 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; } 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 c8ae4792b61..f67d8d92d11 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -91,6 +91,9 @@ 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; @@ -136,7 +139,6 @@ 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; @@ -151,13 +153,13 @@ @Getter public class GeyserSession implements GeyserConnection, GeyserCommandSource { - private final @Nonnull GeyserImpl geyser; - private final @Nonnull UpstreamSession upstream; + private final @NonNull GeyserImpl geyser; + private final @NonNull UpstreamSession upstream; /** * The loop where all packets and ticking is processed to prevent concurrency issues. * If this is manually called, ensure that any exceptions are properly handled. */ - private final @Nonnull EventLoop eventLoop; + private final @NonNull EventLoop eventLoop; private TcpSession downstream; @Setter private AuthData authData; @@ -1326,33 +1328,8 @@ protected void disableSrvResolving() { } @Override - public String bedrockUsername() { - return authData.name(); - } - - @Override - public UUID javaUuid() { - - } - - @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; + public String name() { + return null; } @Override @@ -1405,12 +1382,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; } /** @@ -1765,7 +1744,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 +1830,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 fc6c3735616..2660f54b1f0 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableList; import lombok.AccessLevel; import lombok.Getter; +import lombok.NonNull; import org.geysermc.geyser.text.GeyserLocale; import java.util.*; @@ -67,6 +68,16 @@ public void removeSession(GeyserSession 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. */ From 33af9e094c9b3b7844e57c67c25b8c5b3577c127 Mon Sep 17 00:00:00 2001 From: David Choo Date: Fri, 12 Aug 2022 22:25:07 -0400 Subject: [PATCH 197/358] Fix missing cool down indicator when attacking mobs (#3230) --- .../BedrockInventoryTransactionTranslator.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 24c046ef296..7c2a4813712 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -54,6 +54,7 @@ import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -62,10 +63,7 @@ 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.util.BlockUtils; -import org.geysermc.geyser.util.EntityUtils; -import org.geysermc.geyser.util.InteractionResult; -import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.geyser.util.*; import java.util.List; import java.util.concurrent.TimeUnit; @@ -468,6 +466,11 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entityId, InteractAction.ATTACK, session.isSneaking()); session.sendDownstreamPacket(attackPacket); + + if (MinecraftProtocol.supports1_19_10(session)) { + // Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities + CooldownUtils.sendCooldown(session); + } } } break; From 13046a8602a1f9499dd2618e10c94fe1fb316849 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 13 Aug 2022 22:48:12 +0200 Subject: [PATCH 198/358] Fixed building and switched event library --- api/base/build.gradle.kts | 3 +- .../org/geysermc/geyser/api/GeyserApi.java | 6 - .../geysermc/geyser/api/event/EventBus.java | 63 +---------- ...{Cancellable.java => EventSubscriber.java} | 26 ++--- .../geyser/api/event/EventSubscription.java | 82 -------------- .../geyser/api/event/ExtensionEventBus.java | 31 +----- ...ent.java => ExtensionEventSubscriber.java} | 15 +-- .../geysermc/geyser/api/event/Subscribe.java | 103 ------------------ .../api/event/connection/ConnectionEvent.java | 2 +- .../downstream/ServerDefineCommandsEvent.java | 2 +- .../GeyserDefineCustomItemsEvent.java | 29 ++--- .../GeyserLoadResourcePacksEvent.java | 2 +- .../lifecycle/GeyserPostInitializeEvent.java | 2 +- .../lifecycle/GeyserPreInitializeEvent.java | 2 +- .../event/lifecycle/GeyserShutdownEvent.java | 2 +- build-logic/src/main/kotlin/Versions.kt | 2 +- core/build.gradle.kts | 3 - .../java/org/geysermc/geyser/GeyserImpl.java | 12 -- .../event/AbstractEventSubscription.java | 68 ------------ .../event/GeneratedEventSubscription.java | 60 ---------- .../geysermc/geyser/event/GeyserEventBus.java | 95 ++++------------ ...iption.java => GeyserEventSubscriber.java} | 35 ++---- .../event/type/DefineCustomItemsEvent.java | 85 +++++++++++++++ .../event/GeyserExtensionEventBus.java | 56 ++++------ .../populator/ItemRegistryPopulator.java | 3 +- 25 files changed, 180 insertions(+), 609 deletions(-) rename api/geyser/src/main/java/org/geysermc/geyser/api/event/{Cancellable.java => EventSubscriber.java} (74%) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java rename api/geyser/src/main/java/org/geysermc/geyser/api/event/{Event.java => ExtensionEventSubscriber.java} (85%) delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java delete mode 100644 core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java delete mode 100644 core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java rename core/src/main/java/org/geysermc/geyser/event/{BaseEventSubscription.java => GeyserEventSubscriber.java} (55%) create mode 100644 core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.java diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index c9ddf44892b..03f08c6d234 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -1,5 +1,4 @@ dependencies { api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) + api("org.geysermc.event", "events", Versions.eventsVersion) } - -provided("net.kyori", "event-api", Versions.eventVersion) 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 9b584b98535..82f0566feac 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 @@ -55,12 +55,6 @@ public interface GeyserApi extends GeyserApiBase { @Override @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); - /** - * {@inheritDoc} - */ - @Override - @Nullable GeyserConnection connectionByUsername(@NonNull String username); - /** * {@inheritDoc} */ diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java index b13f123002a..c42698d4752 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java @@ -26,71 +26,18 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.event.bus.OwnedEventBus; import org.geysermc.geyser.api.extension.Extension; import java.util.Set; -import java.util.function.Consumer; /** * Represents a bus capable of subscribing * or "listening" to events and firing them. */ -public interface EventBus { - - /** - * Subscribes to the given event see {@link EventSubscription}. - * - * The difference between this method and {@link ExtensionEventBus#subscribe(Class, Consumer)} - * is that this method takes in an extension parameter which allows for - * the event to be unsubscribed upon extension disable and reloads. - * - * @param extension the extension to subscribe the event to - * @param eventClass the class of the event - * @param consumer the consumer for handling the event - * @param the event class - * @return the event subscription - */ - @NonNull - EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer); - - /** - * Unsubscribes the given {@link EventSubscription}. - * - * @param subscription the event subscription - */ - void unsubscribe(@NonNull EventSubscription subscription); - - /** - * Registers events for the given listener. - * - * @param extension the extension registering the event - * @param eventHolder the listener - */ - void register(@NonNull Extension extension, @NonNull Object eventHolder); - - /** - * Unregisters all events from a given {@link Extension}. - * - * @param extension the extension - */ - void unregisterAll(@NonNull Extension extension); - - /** - * Fires the given {@link Event} and returns the result. - * - * @param event the event to fire - * - * @return true if the event successfully fired - */ - boolean fire(@NonNull Event event); - - /** - * Gets the subscriptions for the given event class. - * - * @param eventClass the event class - * @param the value - * @return the subscriptions for the event class - */ +public interface EventBus extends OwnedEventBus> { + @Override @NonNull - Set> subscriptions(@NonNull Class eventClass); + Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java similarity index 74% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java index 94d0b832dfa..7ce5b78838b 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Cancellable.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java @@ -25,22 +25,16 @@ 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 cancellable event. + * 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 Cancellable { - - /** - * Gets if the event is cancelled. - * - * @return if the event is cancelled - */ - boolean isCancelled(); - - /** - * Cancels the event. - * - * @param cancelled if the event is cancelled - */ - void setCancelled(boolean cancelled); +public interface EventSubscriber extends OwnedSubscriber { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java deleted file mode 100644 index 9a04b697c1c..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscription.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.extension.Extension; - -/** - * Represents a subscribed listener to a {@link Event}. Wraps around - * the event and is capable of unsubscribing from the event or give - * information about it. - * - * @param the class of the event - */ -public interface EventSubscription { - - /** - * Gets the event class. - * - * @return the event class - */ - @NonNull - Class eventClass(); - - /** - * Gets the {@link Extension} that owns this - * event subscription. - * - * @return the extension that owns this subscription - */ - @NonNull - Extension owner(); - - /** - * Gets the post order of this event subscription. - * - * @return the post order of this event subscription - */ - Subscribe.PostOrder order(); - - /** - * Gets if this event subscription is active. - * - * @return if this event subscription is active - */ - boolean isActive(); - - /** - * Unsubscribes from this event listener - */ - void unsubscribe(); - - /** - * Invokes the given event - * - * @param event the event - */ - void invoke(@NonNull T event) throws Throwable; -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java index db0209e5ff9..172c0f9de6f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java @@ -26,36 +26,15 @@ package org.geysermc.geyser.api.event; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; -import java.util.function.Consumer; +import java.util.Set; /** * An {@link EventBus} with additional methods that implicitly * set the extension instance. - * */ -public interface ExtensionEventBus extends EventBus { - - /** - * Subscribes to the given event see {@link EventSubscription}. - * - * @param eventClass the class of the event - * @param consumer the consumer for handling the event - * @param the event class - * @return the event subscription - */ - @NonNull - EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer); - - /** - * Registers events for the given listener. - * - * @param eventHolder the listener - */ - void register(@NonNull Object eventHolder); - - /** - * Unregisters all events for this extension. - */ - void unregisterAll(); +public interface ExtensionEventBus extends org.geysermc.event.bus.EventBus> { + @Override + @NonNull Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java similarity index 85% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java index c32e1701e03..9c5fffa2f17 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Event.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java @@ -25,17 +25,8 @@ package org.geysermc.geyser.api.event; -/** - * Represents an event. - */ -public interface Event { +import org.geysermc.event.Event; +import org.geysermc.event.subscribe.Subscriber; - /** - * Gets if the event is async. - * - * @return if the event is async - */ - default boolean isAsync() { - return false; - } +public interface ExtensionEventSubscriber extends Subscriber { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java deleted file mode 100644 index 488fa0ea3a5..00000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/Subscribe.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An annotation used to signify the given method is - * an {@link Event}. Only should be applied to methods - * where the class containing them is designated for - * events specifically. - * - * When using {@link EventBus#subscribe}, this annotation should - * not be applied whatsoever as it will have no use and potentially - * throw errors due to it being used wrongly. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface Subscribe { - - /** - * The {@link PostOrder} of the event - * - * @return the post order of the event - */ - Subscribe.PostOrder postOrder() default PostOrder.NORMAL; - - /** - * Represents the post order of an event. - */ - enum PostOrder { - - /** - * The lowest priority. Called first to - * allow for other events to customize - * the outcome - */ - FIRST(net.kyori.event.PostOrders.FIRST), - - /** - * The second lowest priority. - */ - EARLY(net.kyori.event.PostOrders.EARLY), - - /** - * Normal priority. Event is neither - * important nor unimportant - */ - NORMAL(net.kyori.event.PostOrders.NORMAL), - - /** - * The second highest priority - */ - LATE(net.kyori.event.PostOrders.LATE), - - /** - * The highest of importance! Event is called - * last and has the final say in the outcome - */ - LAST(net.kyori.event.PostOrders.LAST); - - private final int postOrder; - - PostOrder(int postOrder) { - this.postOrder = postOrder; - } - - /** - * The numerical post order value. - * - * @return numerical post order value - */ - public int postOrder() { - return this.postOrder; - } - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java index 48f3acdb7d2..158f14d53bb 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.api.event.connection; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.Event; /** * An event that contains a {@link GeyserConnection}. diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java index 2ab1b9611aa..e46492b3697 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -26,8 +26,8 @@ package org.geysermc.geyser.api.event.downstream; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Cancellable; import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.Cancellable; import org.geysermc.geyser.api.event.connection.ConnectionEvent; import java.util.Set; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index 308b39d2231..bfed5d534d7 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -25,45 +25,34 @@ package org.geysermc.geyser.api.event.lifecycle; -import com.google.common.collect.Multimap; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * Called on Geyser's startup when looking for custom items. Custom items must be registered through this event. * * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. */ -public abstract class GeyserDefineCustomItemsEvent implements Event { - private final Multimap customItems; - private final List nonVanillaCustomItems; - - public GeyserDefineCustomItemsEvent(Multimap customItems, List nonVanillaCustomItems) { - this.customItems = customItems; - this.nonVanillaCustomItems = nonVanillaCustomItems; - } - +public interface GeyserDefineCustomItemsEvent extends Event { /** * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. * * @return a multimap of all the already registered custom items */ - public Map> getExistingCustomItems() { - return Collections.unmodifiableMap(this.customItems.asMap()); - } + Map> getExistingCustomItems(); /** * Gets the list of the already registered non-vanilla custom items. * * @return the list of the already registered non-vanilla custom items */ - public List getExistingNonVanillaCustomItems() { - return Collections.unmodifiableList(this.nonVanillaCustomItems); - } + List getExistingNonVanillaCustomItems(); /** * Registers a custom item with a base Java item. This is used to register items with custom textures and properties @@ -73,7 +62,7 @@ public List getExistingNonVanillaCustomItems() { * @param customItemData the custom item data to register * @return if the item was registered */ - public abstract boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); + boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); /** * Registers a custom item with no base item. This is used for mods. @@ -81,5 +70,5 @@ public List getExistingNonVanillaCustomItems() { * @param customItemData the custom item data to register * @return if the item was registered */ - public abstract boolean register(@NonNull NonVanillaCustomItemData customItemData); + boolean register(@NonNull NonVanillaCustomItemData customItemData); } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java index 0f181aedfcd..e9b283ecbbd 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import java.nio.file.Path; import java.util.List; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java index 94e42e07596..9e88c017b12 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java index fa130c88301..2be0272dce0 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java index a0fc2294bdc..f2fab901d92 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -26,8 +26,8 @@ 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.CommandManager; -import org.geysermc.geyser.api.event.Event; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index d9af831f087..f7b20cca1e5 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -39,9 +39,9 @@ object Versions { const val mcprotocollibversion = "9f78bd5" const val packetlibVersion = "3.0" const val adventureVersion = "4.9.3" - const val eventVersion = "3.0.0" const val junitVersion = "4.13.1" const val checkerQualVersion = "3.19.0" const val cumulusVersion = "1.1.1" + const val eventsVersion = "1.0-SNAPSHOT" const val log4jVersion = "2.17.1" } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 602e0e8e362..83591e7ad3e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -67,9 +67,6 @@ dependencies { implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) implementation("net.kyori", "adventure-text-serializer-plain", Versions.adventureVersion) - // Kyori Misc - implementation("net.kyori", "event-api", Versions.eventVersion) - // Test testImplementation("junit", "junit", Versions.junitVersion) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 94a2b43043e..87a40c82853 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -467,18 +467,6 @@ private void start() { this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); } - @Override - public @Nullable GeyserSession connectionByUsername(@NonNull String username) { - for (GeyserSession session : sessionManager.getAllSessions()) { - if (session.bedrockUsername().equals(username) || session.getProtocol().getProfile().getName().equals( - username)) { - return session; - } - } - - return null; - } - @Override public @NonNull List onlineConnections() { return sessionManager.getAllSessions(); diff --git a/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java deleted file mode 100644 index 69dc7493582..00000000000 --- a/core/src/main/java/org/geysermc/geyser/event/AbstractEventSubscription.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.event; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; -import net.kyori.event.EventSubscriber; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; -import org.geysermc.geyser.api.event.Subscribe; -import org.geysermc.geyser.api.extension.Extension; - -@Getter -@Accessors(fluent = true) -@RequiredArgsConstructor -public abstract class AbstractEventSubscription implements EventSubscription, EventSubscriber { - protected final EventBus eventBus; - protected final Class eventClass; - protected final Extension owner; - protected final Subscribe.PostOrder order; - @Getter(AccessLevel.NONE) private boolean active; - - @Override - public boolean isActive() { - return this.active; - } - - @Override - public void unsubscribe() { - if (!this.active) { - return; - } - - this.active = false; - this.eventBus.unsubscribe(this); - } - - @Override - public int postOrder() { - return this.order.postOrder(); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java deleted file mode 100644 index b1ba7bf8b10..00000000000 --- a/core/src/main/java/org/geysermc/geyser/event/GeneratedEventSubscription.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.event; - -import lombok.Getter; -import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.Subscribe; -import org.geysermc.geyser.api.extension.Extension; - -import java.util.function.BiConsumer; - -@Getter -@Accessors(fluent = true) -public class GeneratedEventSubscription extends AbstractEventSubscription { - private final Object eventHolder; - private final BiConsumer eventConsumer; - - public GeneratedEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Object eventHolder, BiConsumer eventConsumer) { - super(eventBus, eventClass, owner, order); - - this.eventHolder = eventHolder; - this.eventConsumer = eventConsumer; - } - - @Override - public void invoke(@NonNull T event) throws Throwable { - try { - this.eventConsumer.accept(this.eventHolder, event); - } catch (Throwable ex) { - this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); - ex.printStackTrace(); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index 60e354ac970..7d0d6336dfd 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -25,97 +25,40 @@ package org.geysermc.geyser.event; -import net.kyori.event.EventSubscriber; -import net.kyori.event.SimpleEventBus; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; +import org.geysermc.event.bus.impl.OwnedEventBusImpl; +import org.geysermc.event.subscribe.OwnedSubscriber; +import org.geysermc.event.subscribe.Subscribe; import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; -import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.geyser.api.event.EventSubscriber; import org.geysermc.geyser.api.extension.Extension; -import org.lanternpowered.lmbda.LambdaFactory; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class GeyserEventBus implements EventBus { - private static final MethodHandles.Lookup CALLER = MethodHandles.lookup(); - - private final SimpleEventBus bus = new SimpleEventBus<>(Event.class); - - @NonNull - @Override - public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { - return this.subscribe(eventClass, consumer, extension, Subscribe.PostOrder.NORMAL); - } +@SuppressWarnings("unchecked") +public final class GeyserEventBus extends OwnedEventBusImpl> + implements EventBus { @Override - public void unsubscribe(@NonNull EventSubscription subscription) { - this.bus.unregister((AbstractEventSubscription) subscription); - } - - @SuppressWarnings("unchecked") - @Override - public void register(@NonNull Extension extension, @NonNull Object eventHolder) { - for (Method method : eventHolder.getClass().getMethods()) { - if (!method.isAnnotationPresent(Subscribe.class)) { - continue; - } - - if (method.getParameterCount() > 1) { - continue; - } - - if (!Event.class.isAssignableFrom(method.getParameters()[0].getType())) { - continue; - } - - Subscribe subscribe = method.getAnnotation(Subscribe.class); - - try { - Class type = (Class) method.getParameters()[0].getType(); - this.subscribe(type, eventHolder, LambdaFactory.createBiConsumer(CALLER.unreflect(method)), extension, subscribe.postOrder()); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } - } + protected > B makeSubscription( + Extension owner, Class eventClass, Subscribe subscribe, + L listener, BiConsumer handler) { + return (B) new GeyserEventSubscriber<>( + owner, eventClass, subscribe.postOrder(), subscribe.ignoreCancelled(), listener, handler + ); } @Override - public void unregisterAll(@NonNull Extension extension) { - this.bus.unregister((Predicate>) subscriber -> extension.equals(((AbstractEventSubscription) subscriber).owner())); + protected > B makeSubscription( + Extension owner, Class eventClass, Consumer handler) { + return (B) new GeyserEventSubscriber<>(owner, eventClass, handler); } @Override - public boolean fire(@NonNull Event event) { - return this.bus.post(event).wasSuccessful(); - } - - @SuppressWarnings("unchecked") @NonNull - @Override - public Set> subscriptions(@NonNull Class eventClass) { - return bus.subscribers().values() - .stream() - .filter(sub -> sub instanceof EventSubscription && ((EventSubscription) sub).eventClass().isAssignableFrom(eventClass)) - .map(sub -> ((EventSubscription) sub)) - .collect(Collectors.toSet()); - } - - private EventSubscription subscribe(Class eventClass, Consumer handler, Extension extension, Subscribe.PostOrder postOrder) { - BaseEventSubscription eventSubscription = new BaseEventSubscription<>(this, eventClass, extension, postOrder, handler); - this.bus.register(eventClass, eventSubscription); - return eventSubscription; - } - - private EventSubscription subscribe(Class eventClass, Object eventHolder, BiConsumer handler, Extension extension, Subscribe.PostOrder postOrder) { - GeneratedEventSubscription eventSubscription = new GeneratedEventSubscription<>(this, eventClass, extension, postOrder, eventHolder, handler); - this.bus.register(eventClass, eventSubscription); - return eventSubscription; + public Set> subscribers(@NonNull Class eventClass) { + return castGenericSet(super.subscribers(eventClass)); } } diff --git a/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java similarity index 55% rename from core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java rename to core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java index 79df94e5d6b..6fac82b9656 100644 --- a/core/src/main/java/org/geysermc/geyser/event/BaseEventSubscription.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java @@ -25,34 +25,23 @@ package org.geysermc.geyser.event; -import lombok.Getter; -import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.Subscribe; +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.event.subscribe.impl.OwnedSubscriberImpl; +import org.geysermc.geyser.api.event.ExtensionEventSubscriber; import org.geysermc.geyser.api.extension.Extension; +import java.util.function.BiConsumer; import java.util.function.Consumer; -@Getter -@Accessors(fluent = true) -public class BaseEventSubscription extends AbstractEventSubscription { - private final Consumer eventConsumer; - - public BaseEventSubscription(EventBus eventBus, Class eventClass, Extension owner, Subscribe.PostOrder order, Consumer eventConsumer) { - super(eventBus, eventClass, owner, order); - - this.eventConsumer = eventConsumer; +public final class GeyserEventSubscriber extends OwnedSubscriberImpl + implements ExtensionEventSubscriber { + GeyserEventSubscriber(Extension owner, Class eventClass, Consumer handler) { + super(owner, eventClass, handler); } - @Override - public void invoke(@NonNull T event) throws Throwable { - try { - this.eventConsumer.accept(event); - } catch (Throwable ex) { - this.owner.logger().warning("Unable to fire event " + event.getClass().getSimpleName() + " with subscription " + this.eventConsumer.getClass().getSimpleName()); - ex.printStackTrace(); - } + GeyserEventSubscriber(Extension owner, Class eventClass, PostOrder postOrder, boolean ignoreCancelled, + H handlerInstance, BiConsumer handler) { + super(owner, eventClass, postOrder, ignoreCancelled, handlerInstance, handler); } } diff --git a/core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.java b/core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.java new file mode 100644 index 00000000000..9011bfb3a68 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.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 DefineCustomItemsEvent implements GeyserDefineCustomItemsEvent { + private final Multimap customItems; + private final List nonVanillaCustomItems; + + public DefineCustomItemsEvent(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/event/GeyserExtensionEventBus.java b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java index 4104871faff..d1a647be705 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -26,62 +26,50 @@ package org.geysermc.geyser.extension.event; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.Event; +import org.geysermc.event.Event; +import org.geysermc.event.bus.impl.EventBusImpl; +import org.geysermc.event.subscribe.Subscribe; +import org.geysermc.event.subscribe.Subscriber; import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventSubscription; +import org.geysermc.geyser.api.event.EventSubscriber; import org.geysermc.geyser.api.event.ExtensionEventBus; +import org.geysermc.geyser.api.event.ExtensionEventSubscriber; import org.geysermc.geyser.api.extension.Extension; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; -public record GeyserExtensionEventBus(EventBus eventBus, - Extension extension) implements ExtensionEventBus { - @NonNull +public record GeyserExtensionEventBus(EventBus eventBus, Extension extension) implements ExtensionEventBus { @Override - public EventSubscription subscribe(@NonNull Class eventClass, @NonNull Consumer consumer) { - return this.eventBus.subscribe(this.extension, eventClass, consumer); + public void unsubscribe(@NonNull EventSubscriber subscription) { + eventBus.unsubscribe(subscription); } @Override - public void register(@NonNull Object eventHolder) { - this.eventBus.register(this.extension, eventHolder); - } - - @Override - public void unregisterAll() { - this.eventBus.unregisterAll(this.extension); - } - - @NonNull - @Override - public EventSubscription subscribe(@NonNull Extension extension, @NonNull Class eventClass, @NonNull Consumer consumer) { - return this.eventBus.subscribe(extension, eventClass, consumer); - } - - @Override - public void unsubscribe(@NonNull EventSubscription subscription) { - this.eventBus.unsubscribe(subscription); + public boolean fire(@NonNull Event event) { + return eventBus.fire(event); } @Override - public void register(@NonNull Extension extension, @NonNull Object eventHolder) { - this.eventBus.register(extension, eventHolder); + public @NonNull Set> subscribers(@NonNull Class eventClass) { + return eventBus.subscribers(eventClass); } @Override - public void unregisterAll(@NonNull Extension extension) { - this.eventBus.unregisterAll(extension); + public void register(@NonNull Object listener) { + eventBus.register(extension, listener); } @Override - public boolean fire(@NonNull Event event) { - return this.eventBus.fire(event); + @SuppressWarnings("unchecked") + public > @NonNull U subscribe( + @NonNull Class eventClass, @NonNull Consumer consumer) { + return eventBus.subscribe(extension, eventClass, consumer); } - @NonNull @Override - public Set> subscriptions(@NonNull Class eventClass) { - return this.eventBus.subscriptions(eventClass); + public void unregisterAll() { + eventBus.unregisterAll(extension); } } 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 72ec4af1e51..e6d1cb3e43d 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 @@ -52,6 +52,7 @@ 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.DefineCustomItemsEvent; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.mappings.MappingsConfigReader; @@ -108,7 +109,7 @@ public static void populate() { }); nonVanillaCustomItems = new ObjectArrayList<>(); - GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEvent(customItems, nonVanillaCustomItems) { + GeyserImpl.getInstance().eventBus().fire(new DefineCustomItemsEvent(customItems, nonVanillaCustomItems) { @Override public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { if (CustomItemRegistryPopulator.initialCheck(identifier, customItemData, items)) { From e960303352f8a5a575ba002e1b1427e7b02e2542 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 13 Aug 2022 20:27:13 -0400 Subject: [PATCH 199/358] Update Geyser version --- bootstrap/fabric/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index c42b9c71304..9c79b7141d0 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.19.1 yarn_mappings=1.19.1+build.1 loader_version=0.14.8 # Mod Properties -mod_version=2.0.6-SNAPSHOT +mod_version=2.0.7-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies From 62bd2cc543c4b9819b46151a43ac7d6e5df874c0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 18 Aug 2022 19:33:08 -0400 Subject: [PATCH 200/358] Allow any 1.19 version --- bootstrap/fabric/src/main/resources/fabric.mod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index c02f07c3f65..9d66ca08c53 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ "depends": { "fabricloader": ">=0.14.8", "fabric": "*", - "minecraft": ">=1.19.1" + "minecraft": ">=1.19" } } From 3716b7a84f71022538adea40bc778311bb7691fa Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 20 Aug 2022 14:56:40 -0400 Subject: [PATCH 201/358] Remove initialized check in movement This probably isn't needed anymore. This was introduced in https://github.com/GeyserMC/Geyser/pull/41 and is probably no longer needed since we never send movement before the player is spawned, and we don't allow movement to go through until the Bedrock player matches the unconfirmed teleport we create in JavaPlayerPositionTranslator. By removing this we should fix some instances of players kicked for 'flying' as players joining in the air would never respond to gravity until Bedrock finished loading. --- .../entity/player/BedrockMovePlayerTranslator.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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..17d424b0034 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 From a3b1cf61ad37709a50e2796ee3e0bc57c096ae14 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 20 Aug 2022 16:32:24 -0400 Subject: [PATCH 202/358] Geyser end of https://github.com/GeyserMC/GeyserOptionalPack/pull/34 --- .../java/org/geysermc/geyser/entity/type/LivingEntity.java | 3 +++ 1 file changed, 3 insertions(+) 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) { From 67a65c45d3f5530b31fa4ca587d339a77018d4c9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 21 Aug 2022 21:22:15 -0400 Subject: [PATCH 203/358] Implement update notifications for Geyser Geyser installations will now get notified when a new Bedrock release is out and Geyser must be updated. The system works similarly to ViaVersion where OPs get a notification of an update when they join. The permission node for players to see update notifications is `geyser.update` and the backing JSON that controls this can be found at https://github.com/GeyserMC/GeyserSite/blob/gh-pages/versions.json. There is also a config option to disable update checking. This update also fixes modern Paper installations not being able to see colored text logged from Geyser in the console. --- bootstrap/bungeecord/pom.xml | 6 + .../bungeecord/GeyserBungeePlugin.java | 2 + .../GeyserBungeeUpdateListener.java | 48 ++++++ .../command/BungeeCommandSender.java | 23 ++- bootstrap/pom.xml | 4 + bootstrap/spigot/pom.xml | 11 +- .../platform/spigot/GeyserPaperLogger.java | 59 +++++++ .../platform/spigot/GeyserSpigotPlugin.java | 23 ++- .../spigot/GeyserSpigotUpdateListener.java | 48 ++++++ .../platform/spigot/PaperAdventure.java | 154 ++++++++++++++++++ .../spigot/command/SpigotCommandSender.java | 13 ++ .../manager/GeyserSpigotWorldManager.java | 8 +- .../standalone/GeyserStandaloneLogger.java | 23 +-- .../velocity/GeyserVelocityPlugin.java | 2 + .../GeyserVelocityUpdateListener.java | 47 ++++++ .../command/VelocityCommandSender.java | 7 + .../java/org/geysermc/geyser/Constants.java | 3 + .../java/org/geysermc/geyser/GeyserImpl.java | 10 +- .../org/geysermc/geyser/GeyserLogger.java | 34 +++- .../geyser/command/CommandSender.java | 6 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../geyser/session/cache/WorldCache.java | 2 + .../geyser/util/VersionCheckUtils.java | 47 ++++++ .../org/geysermc/geyser/util/WebUtils.java | 2 + core/src/main/resources/config.yml | 5 + 26 files changed, 558 insertions(+), 34 deletions(-) create mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java create mode 100644 bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 5a1e8e262d7..d71a20f428e 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -24,6 +24,12 @@ a7c6ede provided + + net.kyori + adventure-text-serializer-bungeecord + ${adventure-platform.version} + compile + ${outputName}-BungeeCord diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 0883c5ff022..e8d44b02f35 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 @@ -149,6 +149,8 @@ public void onEnable() { } this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(geyser)); + + this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener()); } @Override diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java new file mode 100644 index 00000000000..bbde8771efd --- /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.BungeeCommandSender; +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 BungeeCommandSender(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/BungeeCommandSender.java index 05df8ba9796..dcf5bd68982 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/BungeeCommandSender.java @@ -25,11 +25,15 @@ package org.geysermc.geyser.platform.bungeecord.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.text.GeyserLocale; +import java.util.Locale; + public class BungeeCommandSender implements CommandSender { private final net.md_5.bungee.api.CommandSender handle; @@ -50,6 +54,18 @@ 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); @@ -58,8 +74,11 @@ public boolean isConsole() { @Override public String getLocale() { 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/pom.xml b/bootstrap/pom.xml index 371ed9bcae2..35ec15abe79 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -11,6 +11,10 @@ bootstrap-parent pom + + 4.1.2 + + spigot-public diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 5142d2bc374..ad4b58fe23d 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -59,7 +59,13 @@ me.lucko commodore - 1.13 + 2.2 + compile + + + net.kyori + adventure-text-serializer-bungeecord + ${adventure-platform.version} compile @@ -107,6 +113,9 @@ net.kyori org.geysermc.geyser.platform.spigot.shaded.kyori + + net.kyori.adventure.text.logger.slf4j.ComponentLogger + org.objectweb.asm 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/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 21c54308de6..a1d9245e83f 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 @@ -123,6 +123,22 @@ public void onEnable() { 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("*********************************************"); + + Bukkit.getPluginManager().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")) { geyserConfig.setAutoconfiguredRemote(true); @@ -137,7 +153,8 @@ public void onEnable() { geyserConfig.getBedrock().setPort(Bukkit.getPort()); } - this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); + this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) + : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); // Remove this in like a year @@ -266,12 +283,16 @@ public void onEnable() { GeyserLocale.getLocaleStringLog(command.getDescription()), command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); } + Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION, + "Whether update notifications can be seen", PermissionDefault.OP)); // Events cannot be unregistered - re-registering results in duplicate firings GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); } boolean brigadierSupported = CommodoreProvider.isSupported(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java new file mode 100644 index 00000000000..02f5367b3c5 --- /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.SpigotCommandSender; +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 SpigotCommandSender(player)); + } + } + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java new file mode 100644 index 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/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java index a05a6ebe0d1..c6314ced50d 100644 --- 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 @@ -25,10 +25,13 @@ package org.geysermc.geyser.platform.spigot.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; import java.lang.reflect.InvocationTargetException; @@ -63,6 +66,16 @@ public void sendMessage(String message) { handle.sendMessage(message); } + @Override + public void sendMessage(Component message) { + if (PaperAdventure.canSendMessageUsingComponent()) { + PaperAdventure.sendMessage(handle, message); + return; + } + + handle.sendMessage(BungeeComponentSerializer.get().serialize(message)); + } + @Override public boolean isConsole() { return handle instanceof ConsoleCommandSender; 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..0a6117b43c8 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 @@ -38,14 +38,14 @@ 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.GameRule; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.network.MinecraftProtocol; 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; 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..78e603d7cbf 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 @@ -31,11 +31,10 @@ 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.text.ChatColor; @Log4j2 -public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender { +public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger { @Override protected boolean isRunning() { @@ -95,24 +94,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/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..13a07121e6a 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 @@ -161,6 +161,8 @@ public void onEnable() { } else { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } + + proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java new file mode 100644 index 00000000000..506dfff7143 --- /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.VelocityCommandSender; +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 VelocityCommandSender(player)); + } + } + } +} 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/VelocityCommandSender.java index d5e4804eebc..a5474c3e0a4 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/VelocityCommandSender.java @@ -28,6 +28,7 @@ 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.text.GeyserLocale; @@ -59,6 +60,12 @@ 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; 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/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 4322dde5916..d9f4d8a1566 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -41,6 +41,8 @@ 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.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; @@ -66,7 +68,6 @@ 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; @@ -303,8 +304,8 @@ private void start() { int port = config.getBedrock().getPort(); 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(); @@ -454,6 +455,9 @@ private void start() { } newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); + if (config.isNotifyOnNewBedrockUpdate()) { + VersionCheckUtils.checkForGeyserUpdate(this::getLogger); + } } @Override diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index b47801cb5e0..197a031dd21 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.CommandSender; + import javax.annotation.Nullable; -public interface GeyserLogger { +public interface GeyserLogger extends CommandSender { /** * 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/CommandSender.java b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java index d9d1bcfbc8a..61adad717f8 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.geyser.text.GeyserLocale; /** @@ -43,6 +45,10 @@ default void sendMessage(String[] messages) { void sendMessage(String message); + default void sendMessage(Component message) { + sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); + } + /** * @return true if the specified sender is from the console. */ 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..f605ad103fe 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -105,6 +105,8 @@ public interface GeyserConfiguration { int getCustomSkullRenderDistance(); + boolean isNotifyOnNewBedrockUpdate(); + IMetricsInfo getMetrics(); int getPendingAuthenticationTimeout(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 30a947e5310..80fa22edeb8 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -148,6 +148,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("xbox-achievements-enabled") private boolean xboxAchievementsEnabled = false; + @JsonProperty("notify-on-new-bedrock-update") + private boolean notifyOnNewBedrockUpdate = true; + private MetricsInfo metrics = new MetricsInfo(); @JsonProperty("pending-authentication-timeout") 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..7996d11885b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -31,6 +31,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; @@ -172,6 +173,7 @@ public void endPredictionsUpTo(int sequence) { if (serverVerifiedState.sequence <= 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 + System.out.println("Resetting " + entry.getKey() + " to " + BlockRegistries.JAVA_BLOCKS.get(serverVerifiedState.blockState).getJavaIdentifier()); ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey()); it.remove(); } 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..b1f97989fd4 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.CommandSender; +import org.geysermc.geyser.network.MinecraftProtocol; 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 (MinecraftProtocol.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. + CommandSender 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.getLocale(), 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/resources/config.yml b/core/src/main/resources/config.yml index c331a7e62ab..5a32a659922 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -175,6 +175,11 @@ 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 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 + # 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/. From d499e22502208fcc974f82233f9ef24e92de6f90 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 21 Aug 2022 21:25:32 -0400 Subject: [PATCH 204/358] Debugging always sneaks in... --- .../main/java/org/geysermc/geyser/session/cache/WorldCache.java | 1 - 1 file changed, 1 deletion(-) 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 7996d11885b..30f8c3ba85b 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 @@ -173,7 +173,6 @@ public void endPredictionsUpTo(int sequence) { if (serverVerifiedState.sequence <= 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 - System.out.println("Resetting " + entry.getKey() + " to " + BlockRegistries.JAVA_BLOCKS.get(serverVerifiedState.blockState).getJavaIdentifier()); ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey()); it.remove(); } From 82411978c859205e78b1c8c4da7c1c7b7999eb2a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:34:26 -0400 Subject: [PATCH 205/358] Update languages submodule --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 4351fc11d5f..ad92d550bab 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 4351fc11d5fbd9fecc8334910234cdf8a4bc730b +Subproject commit ad92d550bab49bc46f17db6aa0042035b66a1a10 From 8dde4b434790e40221cf147e47e4aa83e3263893 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:20:57 -0400 Subject: [PATCH 206/358] Support Bedrock 1.19.21 --- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 3452ec7d571..98f431d1575 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -45,7 +45,10 @@ public final class MinecraftProtocol { * 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_v544.V544_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v544.V544_CODEC.toBuilder() + .minecraftVersion("1.19.21") + .protocolVersion(545) + .build(); /** * A list of all supported Bedrock versions that can join Geyser */ @@ -64,6 +67,7 @@ public final class MinecraftProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v534.V534_CODEC.toBuilder() .minecraftVersion("1.19.10/1.19.11") .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } From e35f3785b2566b3f8ca78ff7fea271afe71ac8c2 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 24 Aug 2022 04:53:13 +0000 Subject: [PATCH 207/358] Resolve fallout --- api/geyser/build.gradle.kts | 2 ++ bootstrap/bungeecord/build.gradle.kts | 2 ++ .../bungeecord/GeyserBungeeUpdateListener.java | 4 ++-- bootstrap/spigot/build.gradle.kts | 7 ++++++- .../platform/spigot/GeyserSpigotUpdateListener.java | 4 ++-- .../velocity/GeyserVelocityUpdateListener.java | 4 ++-- build-logic/src/main/kotlin/Versions.kt | 1 + .../main/java/org/geysermc/geyser/GeyserLogger.java | 4 ++-- .../BedrockInventoryTransactionTranslator.java | 4 ++-- .../org/geysermc/geyser/util/VersionCheckUtils.java | 12 ++++++------ 10 files changed, 27 insertions(+), 17 deletions(-) diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts index dcde8533749..60bd4a431a6 100644 --- a/api/geyser/build.gradle.kts +++ b/api/geyser/build.gradle.kts @@ -4,6 +4,8 @@ plugins { dependencies { api(projects.api) + + implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) } publishing { diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 873df692ade..9f3b49b67d1 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -2,6 +2,8 @@ val bungeeVersion = "a7c6ede"; dependencies { api(projects.core) + + implementation("net.kyori", "adventure-text-serializer-bungeecord", Versions.adventurePlatformVersion) } platformRelocate("net.md_5.bungee.jni") diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java index bbde8771efd..c68839b2095 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java @@ -31,7 +31,7 @@ import net.md_5.bungee.event.EventHandler; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSender; +import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserBungeeUpdateListener implements Listener { @@ -41,7 +41,7 @@ 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 BungeeCommandSender(player)); + VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player)); } } } diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 02883999da0..2fe3d6e2b24 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,7 +1,7 @@ val paperVersion = "1.19-R0.1-SNAPSHOT" val viaVersion = "4.0.0" val adaptersVersion = "1.5-SNAPSHOT" -val commodoreVersion = "1.13" +val commodoreVersion = "2.2" dependencies { api(projects.core) @@ -9,6 +9,8 @@ dependencies { implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion) implementation("me.lucko", "commodore", commodoreVersion) + + implementation("net.kyori", "adventure-text-serializer-bungeecord", Versions.adventurePlatformVersion) // Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 compileOnly("io.papermc.paper", "paper-api", paperVersion) { @@ -61,5 +63,8 @@ tasks.withType { // Commodore includes Brigadier exclude(dependency("com.mojang:.*")) + + // Adventure slf4j + exclude(dependency("net.kyori.adventure.text.logger.slf4j:ComponentLogger")) } } \ No newline at end of file diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java index 02f5367b3c5..5e3c4def8e1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java @@ -31,7 +31,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserSpigotUpdateListener implements Listener { @@ -41,7 +41,7 @@ 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 SpigotCommandSender(player)); + VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); } } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java index 506dfff7143..31e5846128b 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java @@ -30,7 +30,7 @@ import com.velocitypowered.api.proxy.Player; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.platform.velocity.command.VelocityCommandSender; +import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserVelocityUpdateListener { @@ -40,7 +40,7 @@ public void onPlayerJoin(PostLoginEvent event) { if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { final Player player = event.getPlayer(); if (player.hasPermission(Constants.UPDATE_PERMISSION)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSender(player)); + VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player)); } } } diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index f7b20cca1e5..d4a0a80e360 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -39,6 +39,7 @@ object Versions { const val mcprotocollibversion = "9f78bd5" const val packetlibVersion = "3.0" const val adventureVersion = "4.9.3" + const val adventurePlatformVersion = "4.1.2" const val junitVersion = "4.13.1" const val checkerQualVersion = "3.19.0" const val cumulusVersion = "1.1.1" diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index 197a031dd21..88220eec9d7 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -26,11 +26,11 @@ package org.geysermc.geyser; import net.kyori.adventure.text.Component; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import javax.annotation.Nullable; -public interface GeyserLogger extends CommandSender { +public interface GeyserLogger extends GeyserCommandSource { /** * Logs a severe message to console diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 02a7ddaceee..e72f7d78691 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -54,7 +54,7 @@ import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -467,7 +467,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) InteractAction.ATTACK, session.isSneaking()); session.sendDownstreamPacket(attackPacket); - if (MinecraftProtocol.supports1_19_10(session)) { + if (GameProtocol.supports1_19_10(session)) { // Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities CooldownUtils.sendCooldown(session); } 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 b1f97989fd4..049d7861948 100644 --- a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java @@ -34,8 +34,8 @@ import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.command.CommandSender; -import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.GeyserLocale; import java.util.concurrent.CompletableFuture; @@ -54,13 +54,13 @@ public static void checkForOutdatedFloodgate(GeyserLogger logger) { } } - public static void checkForGeyserUpdate(Supplier recipient) { + 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 (MinecraftProtocol.getBedrockCodec(protocolVersion) != null) { + if (GameProtocol.getBedrockCodec(protocolVersion) != null) { // We support the latest version! No need to print a message. return; } @@ -68,11 +68,11 @@ public static void checkForGeyserUpdate(Supplier recipient) { final String newBedrockVersion = bedrock.get("name").asText(); // Delayed for two reasons: save unnecessary processing, and wait to load locale if this is on join. - CommandSender sender = recipient.get(); + 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.getLocale(), newBedrockVersion)) + .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() From dc29d997fd7e7ea81ca7bfe4534b6ea946ec24cd Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 24 Aug 2022 05:18:12 +0000 Subject: [PATCH 208/358] Exclude from relocation, not inclusion --- bootstrap/spigot/build.gradle.kts | 4 ++-- build-logic/src/main/kotlin/extensions.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 2fe3d6e2b24..1b954e1d47b 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -27,7 +27,7 @@ dependencies { platformRelocate("it.unimi.dsi.fastutil") platformRelocate("com.fasterxml.jackson") -platformRelocate("net.kyori") +platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") platformRelocate("io.netty.channel.kqueue") @@ -65,6 +65,6 @@ tasks.withType { exclude(dependency("com.mojang:.*")) // Adventure slf4j - exclude(dependency("net.kyori.adventure.text.logger.slf4j:ComponentLogger")) + //exclude(dependency("net.kyori.adventure.text.logger.slf4j:ComponentLogger")) } } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 1f9793ee458..43cdafdccc3 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -43,9 +43,11 @@ fun Project.exclude(group: String) { } } -fun Project.platformRelocate(pattern: String) { +fun Project.platformRelocate(pattern: String, exclusion: String = "") { tasks.named("shadowJar") { - relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") + relocate(pattern, "org.geysermc.geyser.platform.${project.name}.shaded.$pattern") { + exclude(exclusion) + } } } From f1642d81dc2e2c03ae9bb78c300c305aa6a9dbc3 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:41:24 +0000 Subject: [PATCH 209/358] Fix comment --- bootstrap/spigot/build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 1b954e1d47b..5a459a09b53 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { 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") @@ -63,8 +64,5 @@ tasks.withType { // Commodore includes Brigadier exclude(dependency("com.mojang:.*")) - - // Adventure slf4j - //exclude(dependency("net.kyori.adventure.text.logger.slf4j:ComponentLogger")) } } \ No newline at end of file From 936fed1ded9e8c778e296433a2221c693a9462ba Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Wed, 24 Aug 2022 15:38:54 +0000 Subject: [PATCH 210/358] Move sendMessage(Component) to GeyserCommandSource --- api/geyser/build.gradle.kts | 2 -- .../org/geysermc/geyser/api/command/CommandSource.java | 7 ------- .../org/geysermc/geyser/command/GeyserCommandSource.java | 6 ++++++ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts index 60bd4a431a6..dcde8533749 100644 --- a/api/geyser/build.gradle.kts +++ b/api/geyser/build.gradle.kts @@ -4,8 +4,6 @@ plugins { dependencies { api(projects.api) - - implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) } publishing { diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index 4465e79b23b..aabf0c4e8d9 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.api.command; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - /** * Represents an instance capable of sending commands. */ @@ -58,10 +55,6 @@ default void sendMessage(String[] messages) { } } - default void sendMessage(Component message) { - sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); - } - /** * If this source is the console. * diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index eabccc2439b..88d148b1147 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -27,6 +27,8 @@ import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.text.GeyserLocale; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; /** * Implemented on top of any class that can send a command. @@ -40,4 +42,8 @@ public interface GeyserCommandSource extends CommandSource { default String locale() { return GeyserLocale.getDefaultLocale(); } + + default void sendMessage(Component message) { + sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); + } } From 6ec1ba39c6ed5f452b141ad0b35ddea31f6ffc7a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:21:45 -0400 Subject: [PATCH 211/358] Add 1.19.21 to the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42979bdbe9b..fd253db7790 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.1x/1.19.20 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0/1.19.1x/1.19.20/1.19.21 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. From 29fcce7ec842ea92bd306e1a6d6603c7f8dfe748 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:10:43 -0400 Subject: [PATCH 212/358] Add option to not log player IP addresses Resolves #3246 --- .../geysermc/geyser/configuration/GeyserConfiguration.java | 2 ++ .../geyser/configuration/GeyserJacksonConfiguration.java | 3 +++ .../geyser/network/ConnectorServerEventHandler.java | 6 ++++-- .../java/org/geysermc/geyser/session/GeyserSession.java | 3 +-- core/src/main/resources/config.yml | 3 +++ 5 files changed, 13 insertions(+), 4 deletions(-) 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 f605ad103fe..4d9b3f2a4f0 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -105,6 +105,8 @@ public interface GeyserConfiguration { int getCustomSkullRenderDistance(); + boolean isLogPlayerIpAddresses(); + boolean isNotifyOnNewBedrockUpdate(); IMetricsInfo getMetrics(); 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 80fa22edeb8..b93b9a6a075 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -148,6 +148,9 @@ 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; 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..1c6f9db88cd 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -84,14 +84,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(); 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 f7c990ac3e1..4e00294a872 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -135,7 +135,6 @@ 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; @@ -1045,7 +1044,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()) { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 5a32a659922..d5b9c755f23 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -175,6 +175,9 @@ 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. From b7337fa03268efb583dbfc4b7c7e39a713e57959 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:11:30 -0400 Subject: [PATCH 213/358] Update mappings Fixes #3252 --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 2c68dab9d75..f1c9c2fbba0 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 2c68dab9d751f78b2f5b0298da5e338ad6bc07ca +Subproject commit f1c9c2fbba0e102dc4f8c96dd9485f7ec9768174 From 670308edc2a8dba8e34312481bb04036892477d7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Aug 2022 11:19:23 -0400 Subject: [PATCH 214/358] Update Netty version used in standalone --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 489f99cb318..ca6d4d37058 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,7 +14,7 @@ 4.12.0-20220629.025215-9 8.5.2 2.13.2 - 4.1.66.Final + 4.1.80.Final From 1db77ad2bd22d941750908a02bde1441838e29f0 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 28 Aug 2022 17:42:31 -0700 Subject: [PATCH 215/358] Fix address, port, & motd being ignored in config (#3259) --- .../geyser/configuration/GeyserJacksonConfiguration.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 9f2eaa89833..acb78a8a395 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -159,6 +159,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "0.0.0.0"; @Override @@ -167,6 +168,7 @@ public String address() { } @Setter + @JsonProperty("port") private int port = 19132; @Override @@ -178,6 +180,7 @@ public int port() { @JsonProperty("clone-remote-port") private boolean cloneRemotePort = false; + @JsonProperty("motd1") private String motd1 = "GeyserMC"; @Override @@ -185,6 +188,7 @@ public String primaryMotd() { return motd1; } + @JsonProperty("motd2") private String motd2 = "Geyser"; @Override @@ -237,6 +241,7 @@ public List getWhitelistedIPsMatchers() { public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(isIp = true) + @JsonProperty("address") private String address = "auto"; @Override @@ -246,6 +251,7 @@ public String address() { @JsonDeserialize(using = PortDeserializer.class) @Setter + @JsonProperty("port") private int port = 25565; @Override From 8e47a9f5e9b27819dedcd403b143f4cf0e7ec0dc Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 29 Aug 2022 12:26:30 -0400 Subject: [PATCH 216/358] Ensure bedrock inventory id is at most 100 (#3260) --- .../org/geysermc/geyser/inventory/Inventory.java | 13 ++++++++++--- .../geysermc/geyser/inventory/click/ClickPlan.java | 2 +- .../inventory/holder/BlockInventoryHolder.java | 4 ++-- .../inventory/updater/ChestInventoryUpdater.java | 4 ++-- .../updater/ContainerInventoryUpdater.java | 4 ++-- .../inventory/updater/HorseInventoryUpdater.java | 2 +- .../inventory/BeaconInventoryTranslator.java | 2 +- .../inventory/BrewingInventoryTranslator.java | 4 ++-- .../inventory/EnchantingInventoryTranslator.java | 2 +- .../inventory/Generic3X3InventoryTranslator.java | 2 +- .../translator/inventory/InventoryTranslator.java | 2 +- .../inventory/LecternInventoryTranslator.java | 8 ++++---- .../inventory/LoomInventoryTranslator.java | 2 +- .../inventory/StonecutterInventoryTranslator.java | 2 +- .../chest/DoubleChestInventoryTranslator.java | 4 ++-- .../furnace/AbstractFurnaceInventoryTranslator.java | 2 +- .../horse/ChestedHorseInventoryTranslator.java | 2 +- .../bedrock/BedrockContainerCloseTranslator.java | 12 ++++++------ .../BedrockInventoryTransactionTranslator.java | 4 ++-- .../bedrock/BedrockLecternUpdateTranslator.java | 8 ++++---- .../inventory/JavaContainerCloseTranslator.java | 2 +- .../inventory/JavaMerchantOffersTranslator.java | 2 +- .../java/inventory/JavaOpenScreenTranslator.java | 4 ++-- .../org/geysermc/geyser/util/InventoryUtils.java | 12 ++++++------ 24 files changed, 56 insertions(+), 49 deletions(-) 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..137291dc95c 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -45,7 +45,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 +94,22 @@ 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) + public int getBedrockId() { + return javaId <= 100 ? javaId : (javaId % 100) + 1; + } + public GeyserItemStack getItem(int slot) { if (slot > this.size) { GeyserImpl.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index ec36645da9e..da72f9f99c7 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 @@ -144,7 +144,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..379eb2566e3 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 @@ -133,7 +133,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 +146,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/updater/ChestInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java index 1e5c6946de1..a468e53bc72 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java @@ -59,7 +59,7 @@ public 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/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..b12cd8354f8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java @@ -43,7 +43,7 @@ public 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/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..9f7a521077b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java @@ -52,7 +52,7 @@ public 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..394a394edb7 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 @@ -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..f6d24363aa7 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 @@ -99,10 +99,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 +150,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/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/DoubleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java index fc3279de14c..ec5c882c361 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 @@ -130,7 +130,7 @@ public void prepareInventory(GeyserSession session, Inventory inventory) { @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 +143,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/furnace/AbstractFurnaceInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java index 472f92b4dbf..6794b17e4e9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java @@ -43,7 +43,7 @@ public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockIn @Override public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setWindowId((byte) inventory.getBedrockId()); switch (key) { case 0: dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java index 035f8efa24a..08462249e74 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java @@ -105,7 +105,7 @@ public 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/protocol/bedrock/BedrockContainerCloseTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockContainerCloseTranslator.java index a3f4b4959ee..9a1979c23ae 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockContainerCloseTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockContainerCloseTranslator.java @@ -40,23 +40,23 @@ public class BedrockContainerCloseTranslator 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/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 { 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()) { @@ -108,11 +108,11 @@ public static void displayInventory(GeyserSession session, Inventory inventory) } } - 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,12 +124,12 @@ 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; From 94d56f04bb2f92ffb9a999a7429bbcc5d96c51a5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 29 Aug 2022 12:29:45 -0400 Subject: [PATCH 217/358] Spigot: Use most compatible signature for CommandSender#sendMessage --- .../geyser/platform/spigot/command/SpigotCommandSender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index c6314ced50d..cef92f744cb 100644 --- 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 @@ -73,7 +73,8 @@ public void sendMessage(Component message) { return; } - handle.sendMessage(BungeeComponentSerializer.get().serialize(message)); + // CommandSender#sendMessage(BaseComponent[]) is Paper-only + handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message)); } @Override From d9db035d95309e42ef17571fbc78aead51d1882d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 29 Aug 2022 12:30:16 -0400 Subject: [PATCH 218/358] Add example issue to Inventory#getBedrockId --- core/src/main/java/org/geysermc/geyser/inventory/Inventory.java | 1 + 1 file changed, 1 insertion(+) 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 137291dc95c..29233a2e7d8 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -106,6 +106,7 @@ protected Inventory(String title, int javaId, int size, ContainerType containerT // 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; } From f8a84f977737d39412eb70cd4b523816e6e8ab0a Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 31 Aug 2022 16:42:38 -0400 Subject: [PATCH 219/358] Loopback exemption fixes (#3261) * Fix potential hang when checking loopback exemptions * Remove single quotes from LoopbackExempt command --- .../main/java/org/geysermc/geyser/util/LoopbackUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); From c07c7b933749630eb3512ea59ae0cbd659eb4f76 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Sep 2022 00:50:03 +0200 Subject: [PATCH 220/358] Added support for latest events version --- .../geysermc/geyser/event/GeyserEventBus.java | 15 +++++++++++---- .../geyser/event/GeyserEventSubscriber.java | 18 ++++++++++++++---- .../event/GeyserExtensionEventBus.java | 15 +++++++++++---- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index 7d0d6336dfd..f634931f431 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -27,6 +27,7 @@ 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; @@ -43,8 +44,11 @@ public final class GeyserEventBus extends OwnedEventBusImpl> B makeSubscription( - Extension owner, Class eventClass, Subscribe subscribe, - L listener, BiConsumer handler) { + @NonNull Extension 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 ); @@ -52,8 +56,11 @@ protected > B makeSu @Override protected > B makeSubscription( - Extension owner, Class eventClass, Consumer handler) { - return (B) new GeyserEventSubscriber<>(owner, eventClass, handler); + @NonNull Extension owner, + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder) { + return (B) new GeyserEventSubscriber<>(owner, eventClass, handler, postOrder); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java index 6fac82b9656..5012037bbe1 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java @@ -25,6 +25,7 @@ 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; @@ -36,12 +37,21 @@ public final class GeyserEventSubscriber extends OwnedSubscriberImpl implements ExtensionEventSubscriber { - GeyserEventSubscriber(Extension owner, Class eventClass, Consumer handler) { - super(owner, eventClass, handler); + GeyserEventSubscriber( + @NonNull Extension owner, + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder) { + super(owner, eventClass, handler, postOrder); } - GeyserEventSubscriber(Extension owner, Class eventClass, PostOrder postOrder, boolean ignoreCancelled, - H handlerInstance, BiConsumer handler) { + GeyserEventSubscriber( + @NonNull Extension 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/extension/event/GeyserExtensionEventBus.java b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java index d1a647be705..7294d434582 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -27,17 +27,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.Event; -import org.geysermc.event.bus.impl.EventBusImpl; -import org.geysermc.event.subscribe.Subscribe; +import org.geysermc.event.PostOrder; import org.geysermc.event.subscribe.Subscriber; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventSubscriber; import org.geysermc.geyser.api.event.ExtensionEventBus; -import org.geysermc.geyser.api.event.ExtensionEventSubscriber; import org.geysermc.geyser.api.extension.Extension; import java.util.Set; -import java.util.function.BiConsumer; import java.util.function.Consumer; public record GeyserExtensionEventBus(EventBus eventBus, Extension extension) implements ExtensionEventBus { @@ -68,6 +65,16 @@ public void register(@NonNull Object listener) { 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); From 7d7a38a502dce1bc8155eea258e157fc4e376ffd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 3 Sep 2022 14:03:22 -0400 Subject: [PATCH 221/358] Indicate support for 1.19.22 --- README.md | 2 +- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fd253db7790..464e67d76da 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.1x/1.19.20/1.19.21 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0/1.19.1x/1.19.20/1.19.21/1.19.22 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 98f431d1575..cec5c5ce673 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -46,7 +46,7 @@ public final class MinecraftProtocol { * release of the game that Geyser supports. */ public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v544.V544_CODEC.toBuilder() - .minecraftVersion("1.19.21") + .minecraftVersion("1.19.22") .protocolVersion(545) .build(); /** @@ -68,7 +68,9 @@ public final class MinecraftProtocol { .minecraftVersion("1.19.10/1.19.11") .build()); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.19.21/1.19.22") + .build()); } /** From a16fc9c07c9c6c2c00e03ba5b0637d89f05a1d4d Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Sat, 3 Sep 2022 20:12:48 +0200 Subject: [PATCH 222/358] Add way to specify key for encrypted packs (#3263) --- .../geyser/network/UpstreamPacketHandler.java | 2 +- .../org/geysermc/geyser/pack/ResourcePack.java | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) 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..b4c4ae47174 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -106,7 +106,7 @@ 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); 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..c0913f31c20 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -26,16 +26,20 @@ package org.geysermc.geyser.pack; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import lombok.Getter; + /** * This represents a resource pack and all the data relevant to it */ @@ -55,6 +59,9 @@ public class ResourcePack { private ResourcePackManifest manifest; private ResourcePackManifest.Version version; + @Getter + private String contentKey; + /** * Loop through the packs directory and locate valid resource pack files */ @@ -97,6 +104,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(); From e5337b629828f8531868f2abbe6eeada9653eba8 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 4 Sep 2022 13:08:17 -0500 Subject: [PATCH 223/358] Reintroduce GeyserDefineCommandsEvent and cleanup a few things --- api/base/build.gradle.kts | 7 +- .../main/java/org/geysermc/api/Geyser.java | 4 +- .../org/geysermc/geyser/api/GeyserApi.java | 13 +- .../geysermc/geyser/api/command/Command.java | 39 ++++-- .../lifecycle/GeyserDefineCommandsEvent.java} | 29 ++--- .../event/lifecycle/GeyserShutdownEvent.java | 3 +- .../api/extension/ExtensionDescription.java | 20 ++- .../bungeecord/GeyserBungeePlugin.java | 14 +- .../platform/spigot/GeyserSpigotPlugin.java | 50 ++++++- .../command/GeyserSpigotCommandManager.java | 4 + .../spigot/src/main/resources/plugin.yml | 5 +- .../platform/sponge/GeyserSpongePlugin.java | 13 +- .../velocity/GeyserVelocityPlugin.java | 13 +- .../java/org/geysermc/geyser/GeyserImpl.java | 8 +- .../geyser/command/GeyserCommandManager.java | 122 ++++++++++++------ .../geyser/command/defaults/HelpCommand.java | 6 +- .../type/GeyserDefineCommandsEventImpl.java | 46 +++++++ ... => GeyserDefineCustomItemsEventImpl.java} | 4 +- .../extension/GeyserExtensionDescription.java | 9 +- .../command/GeyserExtensionCommand.java | 43 ++++++ .../loader/ProviderRegistryLoader.java | 5 +- .../populator/ItemRegistryPopulator.java | 5 +- 22 files changed, 355 insertions(+), 107 deletions(-) rename api/geyser/src/main/java/org/geysermc/geyser/api/{command/CommandManager.java => event/lifecycle/GeyserDefineCommandsEvent.java} (68%) create mode 100644 core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java rename core/src/main/java/org/geysermc/geyser/event/type/{DefineCustomItemsEvent.java => GeyserDefineCustomItemsEventImpl.java} (93%) create mode 100644 core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index 03f08c6d234..a6fa608cc76 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -1,4 +1,7 @@ dependencies { api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) - api("org.geysermc.event", "events", Versions.eventsVersion) -} + api("org.geysermc.event", "events", Versions.eventsVersion) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "org.lanternpowered", module = "lmbda") + } +} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java index 9f315faf489..d0bdf3b5e11 100644 --- a/api/base/src/main/java/org/geysermc/api/Geyser.java +++ b/api/base/src/main/java/org/geysermc/api/Geyser.java @@ -69,7 +69,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 +88,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/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java index 82f0566feac..e7372710404 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -29,7 +29,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; import org.geysermc.api.GeyserApiBase; -import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; @@ -66,15 +65,9 @@ public interface GeyserApi extends GeyserApiBase { * * @return the extension manager */ + @NonNull ExtensionManager extensionManager(); - /** - * Gets the {@link CommandManager}. - * - * @return the command manager - */ - CommandManager commandManager(); - /** * Provides an implementation for the specified API type. * @@ -92,6 +85,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the event bus */ + @NonNull EventBus eventBus(); /** @@ -100,6 +94,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the default remote server used within Geyser */ + @NonNull RemoteServer defaultRemoteServer(); /** @@ -108,6 +103,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the listener used for Bedrock client connectins */ + @NonNull BedrockListener bedrockListener(); /** @@ -115,6 +111,7 @@ public interface GeyserApi extends GeyserApiBase { * * @return the current geyser api instance */ + @NonNull static GeyserApi api() { return Geyser.api(GeyserApi.class); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java index 0ad2966692d..2f1f2b24dba 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -27,6 +27,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.extension.Extension; import java.util.Collections; import java.util.List; @@ -104,19 +106,39 @@ default boolean isBedrockOnly() { return false; } - static Command.Builder builder(Class sourceType) { - return GeyserApi.api().provider(Builder.class, sourceType); + /** + * Creates a new {@link Command.Builder} used to construct commands. + * + * @param extension the extension + * @param the source type + * @return a new command builder used to construct commands + */ + static Command.Builder builder(@NonNull Extension extension) { + return GeyserApi.api().provider(Builder.class, extension); } interface Builder { + /** + * Defines the source type to use for this command. + *

+ * Command source types can be anything that extend + * {@link CommandSource}, such as {@link GeyserConnection}. + * This will guarantee that the source used in the executor + * is an instance of this source. + * + * @param sourceType the source type + * @return the builder + */ + Builder source(@NonNull Class sourceType); + /** * Sets the command name. * * @param name the command name * @return the builder */ - Builder name(String name); + Builder name(@NonNull String name); /** * Sets the command description. @@ -124,7 +146,7 @@ interface Builder { * @param description the command description * @return the builder */ - Builder description(String description); + Builder description(@NonNull String description); /** * Sets the permission node. @@ -132,7 +154,7 @@ interface Builder { * @param permission the permission node * @return the builder */ - Builder permission(String permission); + Builder permission(@NonNull String permission); /** * Sets the aliases. @@ -140,7 +162,7 @@ interface Builder { * @param aliases the aliases * @return the builder */ - Builder aliases(List aliases); + Builder aliases(@NonNull List aliases); /** * Sets if this command is designed to be used only by server operators. @@ -164,7 +186,7 @@ interface Builder { * @param subCommands the subcommands * @return the builder */ - Builder subCommands(List subCommands); + Builder subCommands(@NonNull List subCommands); /** * Sets if this command is bedrock only. @@ -180,13 +202,14 @@ interface Builder { * @param executor the command executor * @return the builder */ - Builder executor(CommandExecutor executor); + Builder executor(@NonNull CommandExecutor executor); /** * Builds the command. * * @return the command */ + @NonNull Command build(); } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java similarity index 68% rename from api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index 9f29651ba8d..77d5efa658e 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -23,36 +23,35 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.command; +package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.command.Command; import java.util.Map; /** - * Manages Bedrock commands within Geyser. + * Called when commands are defined within Geyser. + * + * This event allows you to register new commands using the {@link #register(Command)} + * method and retrieve the default commands defined. */ -public abstract class CommandManager { +public interface GeyserDefineCommandsEvent extends Event { /** - * Registers the given {@link Command}. + * Registers the given {@link Command} into the Geyser + * command manager. * * @param command the command to register */ - public abstract void register(@NonNull Command command); - - /** - * Unregisters the given {@link Command}. - * - * @param command the command to unregister - */ - public abstract void unregister(@NonNull Command command); + void register(@NonNull Command command); /** - * Gets all the registered {@link Command}s. + * Gets all the registered built-in {@link Command}s. * - * @return all the registered commands + * @return all the registered built-in commands */ @NonNull - public abstract Map commands(); + Map commands(); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java index f2fab901d92..a1c68d8769a 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -27,12 +27,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.Event; -import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.extension.ExtensionManager; /** * Called when Geyser is shutting down. */ -public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull CommandManager commandManager, @NonNull EventBus eventBus) implements Event { +public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index e7741114498..8136bd76142 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -30,12 +30,20 @@ import java.util.List; /** - * This is the Geyser extension description + * Represents the description of an {@link Extension}. */ public interface ExtensionDescription { /** - * Gets the extension's name + * Gets the extension's id. + * + * @return the extension's id + */ + @NonNull + String id(); + + /** + * Gets the extension's name. * * @return the extension's name */ @@ -43,7 +51,7 @@ public interface ExtensionDescription { String name(); /** - * Gets the extension's main class + * Gets the extension's main class. * * @return the extension's main class */ @@ -51,7 +59,7 @@ public interface ExtensionDescription { String main(); /** - * Gets the extension's api version + * Gets the extension's api version. * * @return the extension's api version */ @@ -59,7 +67,7 @@ public interface ExtensionDescription { String apiVersion(); /** - * Gets the extension's description + * Gets the extension's description. * * @return the extension's description */ @@ -67,7 +75,7 @@ public interface ExtensionDescription { String version(); /** - * Gets the extension's authors + * Gets the extension's authors. * * @return the extension's authors */ 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 c725034505a..ed2c340db80 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 @@ -32,6 +32,8 @@ 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; @@ -49,6 +51,7 @@ import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -149,8 +152,15 @@ public void onEnable() { this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); } - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", geyser, geyserCommandManager.getCommands())); - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyserext", geyser, geyserCommandManager.commands())); + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands)); + } } @Override 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 4a371cfe8e0..e3b79457831 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -32,9 +32,11 @@ import io.netty.buffer.ByteBuf; import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; +import org.bukkit.command.CommandMap; import org.bukkit.command.PluginCommand; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -42,6 +44,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -62,6 +65,8 @@ 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; @@ -269,13 +274,32 @@ public void onEnable() { PluginCommand geyserCommand = this.getCommand("geyser"); geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); - PluginCommand geyserExtCommand = this.getCommand("geyserext"); - geyserExtCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); + + CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + // Thanks again, Bukkit + try { + Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); + constructor.setAccessible(true); + + PluginCommand pluginCommand = constructor.newInstance(entry.getKey().description().id(), this); + pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); + pluginCommand.setDescription("The main command for the " + entry.getKey().name() + " Geyser extension!"); + commandMap.register(entry.getKey().description().id(), "geyserext", pluginCommand); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { + this.geyserLogger.error("Failed to construct PluginCommand for extension " + entry.getKey().description().name(), ex); + } + } if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions throws an error - for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { + for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { Command command = entry.getValue(); if (command.aliases().contains(entry.getKey())) { // Don't register aliases @@ -286,6 +310,26 @@ public void onEnable() { 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)); 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 9fb19f0dae2..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 @@ -65,4 +65,8 @@ 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/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 0f6398b491c..e28b8981d87 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -8,7 +8,4 @@ api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser - geyserext: - description: The command any extensions can register to. - usage: /geyserext \ No newline at end of file + usage: /geyser \ No newline at end of file diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index 312dfb0879a..d912d28d883 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 @@ -29,6 +29,8 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -50,6 +52,7 @@ 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") @@ -122,7 +125,15 @@ public void onEnable() { this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); this.geyserCommandManager.init(); Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.commands()), "geyserext"); + + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id()); + } } @Override 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 e2f84a2a7e0..30d8deccd9e 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,6 +39,8 @@ 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; @@ -58,6 +60,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") @@ -159,7 +162,15 @@ public void onEnable() { this.geyserCommandManager.init(); this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); - this.commandManager.register("geyserext", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.commands())); + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); + } + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index ed65ffbe923..24a5f5d2c1a 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -547,7 +547,7 @@ public void shutdown() { ResourcePack.PACKS.clear(); - this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.commandManager(), this.eventBus)); + this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); @@ -572,11 +572,12 @@ public boolean isProductionEnvironment() { } @Override + @NonNull public GeyserExtensionManager extensionManager() { return this.extensionManager; } - @Override + @NonNull public GeyserCommandManager commandManager() { return this.bootstrap.getGeyserCommandManager(); } @@ -587,15 +588,18 @@ public GeyserCommandManager commandManager() { } @Override + @NonNull public EventBus eventBus() { return this.eventBus; } + @NonNull public RemoteServer defaultRemoteServer() { return getConfig().getRemote(); } @Override + @NonNull public BedrockListener bedrockListener() { return getConfig().getBedrock(); } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index cb3cf6eee1a..7c5bd65807b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -33,30 +33,46 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandExecutor; -import org.geysermc.geyser.api.command.CommandManager; import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.command.defaults.*; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; +import org.geysermc.geyser.command.defaults.AdvancementsCommand; +import org.geysermc.geyser.command.defaults.ConnectionTestCommand; +import org.geysermc.geyser.command.defaults.DumpCommand; +import org.geysermc.geyser.command.defaults.ExtensionsCommand; +import org.geysermc.geyser.command.defaults.HelpCommand; +import org.geysermc.geyser.command.defaults.ListCommand; +import org.geysermc.geyser.command.defaults.OffhandCommand; +import org.geysermc.geyser.command.defaults.ReloadCommand; +import org.geysermc.geyser.command.defaults.SettingsCommand; +import org.geysermc.geyser.command.defaults.StatisticsCommand; +import org.geysermc.geyser.command.defaults.StopCommand; +import org.geysermc.geyser.command.defaults.VersionCommand; +import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; +import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @RequiredArgsConstructor -public abstract class GeyserCommandManager extends CommandManager { +public abstract class GeyserCommandManager { @Getter private final Map commands = new Object2ObjectOpenHashMap<>(12); - private final Map extensionCommands = new Object2ObjectOpenHashMap<>(0); + private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); private final GeyserImpl geyser; public void init() { - registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", commands)); + registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); @@ -67,11 +83,32 @@ public void init() { 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 (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { + if (this.geyser.getPlatformType() == PlatformType.STANDALONE) { registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } - register(new HelpCommand(geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", "geyserext", extensionCommands)); + if (this.geyser.extensionManager().extensions().size() > 0) { + registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); + } + + GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) { + + @Override + public void register(@NonNull Command command) { + if (!(command instanceof GeyserExtensionCommand extensionCommand)) { + throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?"); + } + + registerExtensionCommand(extensionCommand.extension(), extensionCommand); + } + }; + + this.geyser.eventBus().fire(defineCommandsEvent); + + // Register help commands for all extensions with commands + for (Map.Entry> entry : this.extensionCommands.entrySet()) { + registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", entry.getKey().description().id(), entry.getValue())); + } } /** @@ -81,9 +118,8 @@ public void registerBuiltInCommand(GeyserCommand command) { register(command, this.commands); } - @Override - public void register(@NonNull Command command) { - register(command, this.extensionCommands); + public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) { + register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); } private void register(Command command, Map commands) { @@ -99,32 +135,30 @@ private void register(Command command, Map commands) { } } - @Override - public void unregister(@NonNull Command command) { - this.extensionCommands.remove(command.name(), command); - - if (command.aliases().isEmpty()) { - return; - } - - for (String alias : command.aliases()) { - this.extensionCommands.remove(alias, command); - } + @NotNull + public Map commands() { + return Collections.unmodifiableMap(this.commands); } @NotNull - @Override - public Map commands() { + public Map> extensionCommands() { return Collections.unmodifiableMap(this.extensionCommands); } public boolean runCommand(GeyserCommandSource sender, String command) { - boolean extensionCommand = command.startsWith("geyserext "); - if (!command.startsWith("geyser ") && !extensionCommand) { + Extension extension = null; + for (Extension loopedExtension : this.extensionCommands.keySet()) { + if (command.startsWith(loopedExtension.description().id() + " ")) { + extension = loopedExtension; + break; + } + } + + if (!command.startsWith("geyser ") && extension == null) { return false; } - command = command.trim().replace(extensionCommand ? "geyserext " : "geyser ", ""); + command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", ""); String label; String[] args; @@ -137,9 +171,9 @@ public boolean runCommand(GeyserCommandSource sender, String command) { args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; } - Command cmd = (extensionCommand ? this.extensionCommands : this.commands).get(label); + Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label); if (cmd == null) { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); + sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); return false; } @@ -168,7 +202,8 @@ public boolean runCommand(GeyserCommandSource sender, String command) { @RequiredArgsConstructor public static class CommandBuilder implements Command.Builder { - private final Class sourceType; + private final Extension extension; + private Class sourceType; private String name; private String description = ""; private String permission = ""; @@ -179,22 +214,28 @@ public static class CommandBuilder implements Command.B private boolean bedrockOnly; private CommandExecutor executor; - public CommandBuilder name(String name) { + @Override + public Command.Builder source(@NonNull Class sourceType) { + this.sourceType = sourceType; + return this; + } + + public CommandBuilder name(@NonNull String name) { this.name = name; return this; } - public CommandBuilder description(String description) { + public CommandBuilder description(@NonNull String description) { this.description = description; return this; } - public CommandBuilder permission(String permission) { + public CommandBuilder permission(@NonNull String permission) { this.permission = permission; return this; } - public CommandBuilder aliases(List aliases) { + public CommandBuilder aliases(@NonNull List aliases) { this.aliases = aliases; return this; } @@ -210,7 +251,7 @@ public CommandBuilder executableOnConsole(boolean executableOnConsole) { return this; } - public CommandBuilder subCommands(List subCommands) { + public CommandBuilder subCommands(@NonNull List subCommands) { this.subCommands = subCommands; return this; } @@ -220,22 +261,27 @@ public CommandBuilder bedrockOnly(boolean bedrockOnly) { return this; } - public CommandBuilder executor(CommandExecutor executor) { + public CommandBuilder executor(@NonNull CommandExecutor executor) { this.executor = executor; return this; } - public GeyserCommand build() { + @NonNull + public GeyserExtensionCommand build() { if (this.name == null || this.name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); } - return new GeyserCommand(this.name, this.description, this.permission) { + if (this.sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); + } + + return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { @SuppressWarnings("unchecked") @Override public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { - Class sourceType = CommandBuilder.this.sourceType; + Class sourceType = CommandBuilder.this.sourceType; CommandExecutor executor = CommandBuilder.this.executor; if (sourceType.isInstance(session)) { executor.execute((T) session, this, args); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 81f34b75965..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 @@ -66,19 +66,19 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); sender.sendMessage(header); - for (Map.Entry entry : commands.entrySet()) { + this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { Command cmd = entry.getValue(); // Standalone hack-in since it doesn't have a concept of permissions if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.permission())) { // Only list commands the player can actually run if (cmd.isBedrockOnly() && session == null) { - continue; + return; } sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " + GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale())); } - } + }); } } diff --git a/core/src/main/java/org/geysermc/geyser/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/DefineCustomItemsEvent.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java similarity index 93% rename from core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.java rename to core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java index 9011bfb3a68..65fd7ea0d9e 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/DefineCustomItemsEvent.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java @@ -36,11 +36,11 @@ import java.util.List; import java.util.Map; -public abstract class DefineCustomItemsEvent implements GeyserDefineCustomItemsEvent { +public abstract class GeyserDefineCustomItemsEventImpl implements GeyserDefineCustomItemsEvent { private final Multimap customItems; private final List nonVanillaCustomItems; - public DefineCustomItemsEvent(Multimap customItems, List nonVanillaCustomItems) { + public GeyserDefineCustomItemsEventImpl(Multimap customItems, List nonVanillaCustomItems) { this.customItems = customItems; this.nonVanillaCustomItems = nonVanillaCustomItems; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index eaf29a819f8..04f80e19dae 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -33,7 +33,7 @@ import java.io.Reader; import java.util.*; -public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { +public record GeyserExtensionDescription(String id, String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { @SuppressWarnings("unchecked") public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { DumperOptions dumperOptions = new DumperOptions(); @@ -42,6 +42,11 @@ public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidD Yaml yaml = new Yaml(dumperOptions); Map yamlMap = yaml.loadAs(reader, LinkedHashMap.class); + String id = ((String) yamlMap.get("id")).replaceAll("[^A-Za-z0-9 _.-]", ""); + if (id.isBlank()) { + throw new InvalidDescriptionException("Invalid extension id, cannot be empty"); + } + String name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); if (name.isBlank()) { throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); @@ -72,6 +77,6 @@ public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidD } } - return new GeyserExtensionDescription(name, main, apiVersion, version, authors); + return new GeyserExtensionDescription(id, name, main, apiVersion, version, Collections.unmodifiableList(authors)); } } 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/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 449f6574e6e..e7837688cbb 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.registry.loader; import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandSource; +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; @@ -43,10 +43,9 @@ */ public class ProviderRegistryLoader implements RegistryLoader, ProviderSupplier>, Map, ProviderSupplier>> { - @SuppressWarnings("unchecked") @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { - providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class) args[0])); + providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); providers.put(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()); 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 e6d1cb3e43d..60a16245c02 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 @@ -48,11 +48,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.event.type.DefineCustomItemsEvent; +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; @@ -109,7 +108,7 @@ public static void populate() { }); nonVanillaCustomItems = new ObjectArrayList<>(); - GeyserImpl.getInstance().eventBus().fire(new DefineCustomItemsEvent(customItems, nonVanillaCustomItems) { + GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { @Override public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { if (CustomItemRegistryPopulator.initialCheck(identifier, customItemData, items)) { From f1da9d70728ad50cbff5c345052f8b18fabb06f2 Mon Sep 17 00:00:00 2001 From: ImDaBigBoss <67973871+imdabigboss@users.noreply.github.com> Date: Sun, 4 Sep 2022 16:11:08 -0500 Subject: [PATCH 224/358] Allow events to be registered by any class Supersedes & closes #3073 Co-authored-by: Redned --- .../main/java/org/geysermc/api/Geyser.java | 1 + .../geysermc/api/util/BedrockPlatform.java | 3 + .../java/org/geysermc/api/util/InputMode.java | 3 + .../java/org/geysermc/api/util/UiProfile.java | 3 + .../org/geysermc/geyser/api/GeyserApi.java | 3 +- .../geysermc/geyser/api/event/EventBus.java | 4 +- .../geyser/api/event/EventRegistrar.java | 45 ++++++++ .../geyser/api/event/EventSubscriber.java | 2 +- .../geyser/api/event/ExtensionEventBus.java | 5 +- .../GeyserDefineCustomItemsEvent.java | 2 + .../lifecycle/GeyserPostInitializeEvent.java | 3 +- .../lifecycle/GeyserPreInitializeEvent.java | 3 +- .../event/lifecycle/GeyserShutdownEvent.java | 3 +- .../geyser/api/extension/Extension.java | 15 ++- .../bungeecord/GeyserBungeePlugin.java | 88 ++++++++++---- .../platform/spigot/GeyserSpigotPlugin.java | 108 ++++++++++++------ .../platform/sponge/GeyserSpongePlugin.java | 17 ++- .../standalone/GeyserStandaloneBootstrap.java | 4 +- .../velocity/GeyserVelocityPlugin.java | 36 +++--- .../java/org/geysermc/geyser/GeyserImpl.java | 46 +++++--- .../geysermc/geyser/event/GeyserEventBus.java | 15 +-- .../geyser/event/GeyserEventRegistrar.java | 38 ++++++ .../geyser/event/GeyserEventSubscriber.java | 7 +- .../event/GeyserExtensionEventBus.java | 11 +- .../loader/ProviderRegistryLoader.java | 3 + 25 files changed, 353 insertions(+), 115 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java create mode 100644 core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java 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 d0bdf3b5e11..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!"); 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 index d66077a87f0..e486f73bc1e 100644 --- a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java +++ b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java @@ -25,6 +25,8 @@ package org.geysermc.api.util; +import org.checkerframework.checker.nullness.qual.NonNull; + public enum BedrockPlatform { UNKNOWN("Unknown"), GOOGLE("Android"), @@ -56,6 +58,7 @@ public enum BedrockPlatform { * @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]; } 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 index eadb457abbc..70346ffa563 100644 --- a/api/base/src/main/java/org/geysermc/api/util/InputMode.java +++ b/api/base/src/main/java/org/geysermc/api/util/InputMode.java @@ -25,6 +25,8 @@ package org.geysermc.api.util; +import org.checkerframework.checker.nullness.qual.NonNull; + public enum InputMode { UNKNOWN, KEYBOARD_MOUSE, @@ -40,6 +42,7 @@ public enum InputMode { * @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]; } 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 index c28ff869cd3..cddb9726033 100644 --- a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java +++ b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java @@ -25,6 +25,8 @@ package org.geysermc.api.util; +import org.checkerframework.checker.nullness.qual.NonNull; + public enum UiProfile { CLASSIC, POCKET; @@ -36,6 +38,7 @@ public enum UiProfile { * @param id the UiProfile identifier * @return The UiProfile or {@link #CLASSIC} if the profile wasn't found */ + @NonNull public static UiProfile fromId(int id) { return VALUES.length > id ? VALUES[id] : VALUES[0]; } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java index e7372710404..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 @@ -31,6 +31,7 @@ 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; @@ -86,7 +87,7 @@ public interface GeyserApi extends GeyserApiBase { * @return the event bus */ @NonNull - EventBus eventBus(); + EventBus eventBus(); /** * Gets the default {@link RemoteServer} configured diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java index c42698d4752..801bfa45fd3 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java @@ -36,8 +36,8 @@ * Represents a bus capable of subscribing * or "listening" to events and firing them. */ -public interface EventBus extends OwnedEventBus> { +public interface EventBus extends OwnedEventBus> { @Override @NonNull - Set> subscribers(@NonNull Class eventClass); + Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java new file mode 100644 index 00000000000..7a2cc0071e3 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.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.event; + +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 + */ + static EventRegistrar of(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 index 7ce5b78838b..7f91d09a39e 100644 --- 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 @@ -36,5 +36,5 @@ * * @param the class of the event */ -public interface EventSubscriber extends OwnedSubscriber { +public interface EventSubscriber extends OwnedSubscriber { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java index 172c0f9de6f..a58d3589173 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java @@ -27,6 +27,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.Event; +import org.geysermc.geyser.api.extension.Extension; import java.util.Set; @@ -34,7 +35,7 @@ * An {@link EventBus} with additional methods that implicitly * set the extension instance. */ -public interface ExtensionEventBus extends org.geysermc.event.bus.EventBus> { +public interface ExtensionEventBus extends org.geysermc.event.bus.EventBus> { @Override - @NonNull Set> subscribers(@NonNull Class eventClass); + @NonNull Set> subscribers(@NonNull Class eventClass); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index bfed5d534d7..0957b8551c1 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -45,6 +45,7 @@ public interface GeyserDefineCustomItemsEvent extends Event { * * @return a multimap of all the already registered custom items */ + @NonNull Map> getExistingCustomItems(); /** @@ -52,6 +53,7 @@ public interface GeyserDefineCustomItemsEvent extends Event { * * @return the list of the already registered non-vanilla custom items */ + @NonNull List getExistingNonVanillaCustomItems(); /** diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java index 9e88c017b12..8d145f615dc 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java @@ -28,6 +28,7 @@ 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; /** @@ -36,5 +37,5 @@ * @param extensionManager the extension manager * @param eventBus the event bus */ -public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java index 2be0272dce0..8be89dafdcf 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java @@ -28,6 +28,7 @@ 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; /** @@ -36,5 +37,5 @@ * @param extensionManager the extension manager * @param eventBus the event bus */ -public record GeyserPreInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +public record GeyserPreInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java index a1c68d8769a..7793ef997c8 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java @@ -28,10 +28,11 @@ 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 { +public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 2982a76fb4a..8d4e4da6473 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -25,16 +25,19 @@ package org.geysermc.geyser.api.extension; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.GeyserApiBase; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.ExtensionEventBus; import java.nio.file.Path; +import java.util.Objects; /** * Represents an extension within Geyser. */ -public interface Extension { +public interface Extension extends EventRegistrar { /** * Gets if the extension is enabled @@ -59,6 +62,7 @@ default void setEnabled(boolean enabled) { * * @return the extension's data folder */ + @NonNull default Path dataFolder() { return this.extensionLoader().dataFolder(this); } @@ -68,6 +72,7 @@ default Path dataFolder() { * * @return the extension event bus */ + @NonNull default ExtensionEventBus eventBus() { return this.extensionLoader().eventBus(this); } @@ -77,6 +82,7 @@ default ExtensionEventBus eventBus() { * * @return the extension manager */ + @NonNull default ExtensionManager extensionManager() { return this.geyserApi().extensionManager(); } @@ -86,6 +92,7 @@ default ExtensionManager extensionManager() { * * @return the extension's name */ + @NonNull default String name() { return this.description().name(); } @@ -95,6 +102,7 @@ default String name() { * * @return the extension's description */ + @NonNull default ExtensionDescription description() { return this.extensionLoader().description(this); } @@ -104,6 +112,7 @@ default ExtensionDescription description() { * * @return the extension's logger */ + @NonNull default ExtensionLogger logger() { return this.extensionLoader().logger(this); } @@ -113,8 +122,9 @@ default ExtensionLogger logger() { * * @return the extension loader */ + @NonNull default ExtensionLoader extensionLoader() { - return this.extensionManager().extensionLoader(this); + return Objects.requireNonNull(this.extensionManager().extensionLoader(this)); } /** @@ -122,6 +132,7 @@ default ExtensionLoader extensionLoader() { * * @return the geyser api instance */ + @NonNull default GeyserApi geyserApi() { return GeyserApi.api(); } 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 ed2c340db80..eb2a3b9c4f9 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,6 +25,8 @@ 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; @@ -47,12 +49,15 @@ 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 { @@ -66,23 +71,9 @@ 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"); - } catch (NoSuchFieldException e) { - getLogger().warning(" / \\"); - getLogger().warning(" / \\"); - getLogger().warning(" / | \\"); - getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); - getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); - getLogger().warning(" / o \\"); - getLogger().warning("/_____________\\"); - } - if (!getDataFolder().exists()) getDataFolder().mkdir(); @@ -121,6 +112,25 @@ public void onEnable() { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); + } + + @Override + public void onEnable() { + // 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"); + } catch (NoSuchFieldException e) { + getLogger().warning(" / \\"); + getLogger().warning(" / \\"); + getLogger().warning(" / | \\"); + getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); + getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); + getLogger().warning(" / o \\"); + getLogger().warning("/_____________\\"); + } + // 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/")); @@ -138,7 +148,41 @@ 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); @@ -146,12 +190,6 @@ public void onEnable() { this.geyserCommandManager = new GeyserBungeeCommandManager(geyser); this.geyserCommandManager.init(); - if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - } else { - this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); - } - 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(); @@ -161,6 +199,12 @@ public void onEnable() { 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()); + } } @Override 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 e3b79457831..2000dc114b8 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 @@ -34,6 +34,9 @@ import org.bukkit.Bukkit; 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; @@ -59,7 +62,12 @@ import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.geyser.platform.spigot.world.manager.*; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigot1_12NativeWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigot1_12WorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotFallbackWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -95,7 +103,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private String minecraftVersion; @Override - public void onEnable() { + public void onLoad() { GeyserLocale.init(this); // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed @@ -113,6 +121,30 @@ public void onEnable() { return; } + // By default this should be localhost but may need to be changed in some circumstances + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { + geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { + geyserConfig.getRemote().setAddress(Bukkit.getIp()); + } + geyserConfig.getRemote().setPort(Bukkit.getPort()); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(Bukkit.getPort()); + } + + 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() { try { // AvailableCommandsSerializer_v291 complains otherwise ByteBuf.class.getMethod("writeShortLE", int.class); @@ -144,24 +176,6 @@ public void onEnable() { } } - // By default this should be localhost but may need to be changed in some circumstances - if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { - geyserConfig.setAutoconfiguredRemote(true); - // Don't use localhost if not listening on all interfaces - if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { - geyserConfig.getRemote().setAddress(Bukkit.getIp()); - } - geyserConfig.getRemote().setPort(Bukkit.getPort()); - } - - if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(Bukkit.getPort()); - } - - this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) - : 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)); @@ -172,7 +186,6 @@ public void onEnable() { 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."); @@ -181,11 +194,43 @@ public void onEnable() { geyserConfig.loadFloodgate(this); + // 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); + + // 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.geyser.extensionManager().extensions()) { + + // 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); + } + } + } + + 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 { @@ -275,25 +320,18 @@ public void onEnable() { PluginCommand geyserCommand = this.getCommand("geyser"); geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); - CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { Map commands = entry.getValue(); if (commands.isEmpty()) { continue; } - // Thanks again, Bukkit - try { - Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); - constructor.setAccessible(true); - - PluginCommand pluginCommand = constructor.newInstance(entry.getKey().description().id(), this); - pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); - pluginCommand.setDescription("The main command for the " + entry.getKey().name() + " Geyser extension!"); - commandMap.register(entry.getKey().description().id(), "geyserext", pluginCommand); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { - this.geyserLogger.error("Failed to construct PluginCommand for extension " + entry.getKey().description().name(), ex); + PluginCommand command = this.getCommand(entry.getKey().description().id()); + if (command == null) { + continue; } + + command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); } if (!INITIALIZED) { 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 d912d28d883..42040f6abf1 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 @@ -72,8 +72,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { private GeyserImpl geyser; - @Override - public void onEnable() { + public void onLoad() { GeyserLocale.init(this); if (!configDir.exists()) @@ -114,7 +113,13 @@ public void onEnable() { this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.start(PlatformType.SPONGE, this); + + this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); + } + + @Override + public void onEnable() { + GeyserImpl.start(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -124,6 +129,7 @@ public void onEnable() { this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); this.geyserCommandManager.init(); + Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { @@ -166,6 +172,11 @@ public Path getConfigFolder() { return configDir.toPath(); } + @Listener + public void onServerStarting() { + onLoad(); + } + @Listener public void onServerStart(GameStartedServerEvent event) { onEnable(); 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 052a41439e3..80d17f6a7eb 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 @@ -217,7 +217,9 @@ 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 GeyserStandaloneCommandManager(geyser); geyserCommandManager.init(); 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 30d8deccd9e..dc31b3fdd21 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 @@ -88,15 +88,6 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public void onEnable() { - try { - Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); - } catch (NoSuchMethodException e) { - // velocitypowered.com has a build that is very outdated - logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + - "that has likely been downloaded is very outdated and does not support 1.19."); - return; - } - GeyserLocale.init(this); try { @@ -131,6 +122,17 @@ public void onEnable() { this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); + + try { + Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); + } catch (NoSuchMethodException e) { + // velocitypowered.com has a build that is very outdated + logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + + "that has likely been downloaded is very outdated and does not support 1.19."); + return; + } + // Remove this in like a year try { // Should only exist on 1.0 @@ -153,7 +155,10 @@ 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 @@ -222,9 +227,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/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 24a5f5d2c1a..115a7245e03 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -57,6 +57,7 @@ import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; @@ -143,8 +144,8 @@ public class GeyserImpl implements GeyserApi { private final PlatformType platformType; private final GeyserBootstrap bootstrap; - private final EventBus eventBus; - private final GeyserExtensionManager extensionManager; + private final EventBus eventBus; + private GeyserExtensionManager extensionManager; private Metrics metrics; @@ -162,9 +163,19 @@ 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(); + } + + public void initialize() { long startupTime = System.currentTimeMillis(); - GeyserLocale.finalizeDefaultLocale(this); GeyserLogger logger = bootstrap.getGeyserLogger(); logger.info("******************************************"); @@ -173,13 +184,8 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info(""); logger.info("******************************************"); - /* Initialize event bus */ - this.eventBus = new GeyserEventBus(); - - /* Load Extensions */ - this.extensionManager = new GeyserExtensionManager(); - this.extensionManager.init(); + /* Enable extensions */ this.extensionManager.enableExtensions(); this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); @@ -193,7 +199,7 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { MessageTranslator.init(); MinecraftLocale.init(); - start(); + startInstance(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -225,7 +231,7 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { } } - private void start() { + private void startInstance() { this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); GeyserLogger logger = bootstrap.getGeyserLogger(); @@ -589,7 +595,7 @@ public GeyserCommandManager commandManager() { @Override @NonNull - public EventBus eventBus() { + public EventBus eventBus() { return this.eventBus; } @@ -612,18 +618,26 @@ public int buildNumber() { return Integer.parseInt(BUILD_NUMBER); } - public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { + public static GeyserImpl load(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); } + return instance; + } + + public static void start() { + if (instance == null) { + throw new RuntimeException("Geyser has not been loaded yet!"); + } + // We've been reloaded if (instance.isShuttingDown()) { instance.shuttingDown = false; - instance.start(); + instance.startInstance(); + } else { + instance.initialize(); } - - return instance; } public GeyserLogger getLogger() { diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java index f634931f431..9593e327ebe 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventBus.java @@ -32,6 +32,7 @@ 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; @@ -40,11 +41,11 @@ import java.util.function.Consumer; @SuppressWarnings("unchecked") -public final class GeyserEventBus extends OwnedEventBusImpl> - implements EventBus { +public final class GeyserEventBus extends OwnedEventBusImpl> + implements EventBus { @Override - protected > B makeSubscription( - @NonNull Extension owner, + protected > B makeSubscription( + @NonNull EventRegistrar owner, @NonNull Class eventClass, @NonNull Subscribe subscribe, @NonNull L listener, @@ -55,8 +56,8 @@ protected > B makeSu } @Override - protected > B makeSubscription( - @NonNull Extension owner, + protected > B makeSubscription( + @NonNull EventRegistrar owner, @NonNull Class eventClass, @NonNull Consumer handler, @NonNull PostOrder postOrder) { @@ -65,7 +66,7 @@ protected > B makeSubsc @Override @NonNull - public Set> subscribers(@NonNull Class eventClass) { + public Set> subscribers(@NonNull Class eventClass) { return castGenericSet(super.subscribers(eventClass)); } } diff --git a/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.java new file mode 100644 index 00000000000..85c36a13212 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventRegistrar.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.event; + +import org.geysermc.geyser.api.event.EventRegistrar; + +public record GeyserEventRegistrar(Object owner) implements EventRegistrar { + + @Override + 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 index 5012037bbe1..d33de8cdd79 100644 --- a/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java +++ b/core/src/main/java/org/geysermc/geyser/event/GeyserEventSubscriber.java @@ -29,16 +29,17 @@ 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 +public final class GeyserEventSubscriber extends OwnedSubscriberImpl implements ExtensionEventSubscriber { GeyserEventSubscriber( - @NonNull Extension owner, + @NonNull R owner, @NonNull Class eventClass, @NonNull Consumer handler, @NonNull PostOrder postOrder) { @@ -46,7 +47,7 @@ public final class GeyserEventSubscriber extends OwnedSubscribe } GeyserEventSubscriber( - @NonNull Extension owner, + @NonNull R owner, @NonNull Class eventClass, @NonNull PostOrder postOrder, boolean ignoreCancelled, diff --git a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java index 7294d434582..f56b254a6c5 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -30,6 +30,7 @@ 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; @@ -37,10 +38,12 @@ import java.util.Set; import java.util.function.Consumer; -public record GeyserExtensionEventBus(EventBus eventBus, Extension extension) implements ExtensionEventBus { +public record GeyserExtensionEventBus(EventBus eventBus, Extension extension) implements ExtensionEventBus { + + @SuppressWarnings({"rawtypes", "unchecked"}) @Override - public void unsubscribe(@NonNull EventSubscriber subscription) { - eventBus.unsubscribe(subscription); + public void unsubscribe(@NonNull EventSubscriber subscription) { + eventBus.unsubscribe((EventSubscriber) subscription); } @Override @@ -49,7 +52,7 @@ public boolean fire(@NonNull Event event) { } @Override - public @NonNull Set> subscribers(@NonNull Class eventClass) { + public @NonNull Set> subscribers(@NonNull Class eventClass) { return eventBus.subscribers(eventClass); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index e7837688cbb..99a9213feb1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -26,11 +26,13 @@ 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; @@ -49,6 +51,7 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder()); providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder()); + providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0])); return providers; } From 896bf7c218919f42c1096fcb695dd7dc6d4facbf Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 4 Sep 2022 16:19:56 -0500 Subject: [PATCH 225/358] Fix bungeecord startup and move version checks to onLoad --- .../bungeecord/GeyserBungeePlugin.java | 70 ++++++++-------- .../platform/spigot/GeyserSpigotPlugin.java | 82 +++++++++---------- 2 files changed, 76 insertions(+), 76 deletions(-) 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 eb2a3b9c4f9..13604a3d497 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 @@ -72,6 +72,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override public void onLoad() { + // 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"); + } catch (NoSuchFieldException e) { + getLogger().warning(" / \\"); + getLogger().warning(" / \\"); + getLogger().warning(" / | \\"); + getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); + getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); + getLogger().warning(" / o \\"); + getLogger().warning("/_____________\\"); + } + GeyserLocale.init(this); if (!getDataFolder().exists()) @@ -89,26 +103,6 @@ public void onLoad() { 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().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("")) { - this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); - } - this.geyserConfig.getRemote().setPort(javaAddr.getPort()); - } - - if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(javaAddr.getPort()); - } - } - this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); @@ -117,20 +111,6 @@ public void onLoad() { @Override public void onEnable() { - // 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"); - } catch (NoSuchFieldException e) { - getLogger().warning(" / \\"); - getLogger().warning(" / \\"); - getLogger().warning(" / | \\"); - getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); - getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); - getLogger().warning(" / o \\"); - getLogger().warning("/_____________\\"); - } - // 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/")); @@ -148,6 +128,26 @@ public void onEnable() { geyserConfig.loadFloodgate(this); + 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().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("")) { + this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } + this.geyserConfig.getRemote().setPort(javaAddr.getPort()); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(javaAddr.getPort()); + } + } + // 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 @@ -171,7 +171,7 @@ private void awaitStartupCompletion(int tries) { listenersField.setAccessible(true); Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance()); - if (!listeners.isEmpty()) { + if (listeners.isEmpty()) { this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS); } else { this.awaitStartupCompletion(++tries); 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 2000dc114b8..4407af6c787 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 @@ -104,47 +104,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onLoad() { - GeyserLocale.init(this); - - // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed - try { - if (!getDataFolder().exists()) { - getDataFolder().mkdir(); - } - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); - } catch (IOException ex) { - getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - Bukkit.getPluginManager().disablePlugin(this); - return; - } - - // By default this should be localhost but may need to be changed in some circumstances - if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { - geyserConfig.setAutoconfiguredRemote(true); - // Don't use localhost if not listening on all interfaces - if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { - geyserConfig.getRemote().setAddress(Bukkit.getIp()); - } - geyserConfig.getRemote().setPort(Bukkit.getPort()); - } - - if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(Bukkit.getPort()); - } - - 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() { try { // AvailableCommandsSerializer_v291 complains otherwise ByteBuf.class.getMethod("writeShortLE", int.class); @@ -176,6 +135,33 @@ public void onEnable() { } } + GeyserLocale.init(this); + + // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed + try { + if (!getDataFolder().exists()) { + getDataFolder().mkdir(); + } + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + 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() { // 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)); @@ -192,6 +178,20 @@ public void onEnable() { geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } + // By default this should be localhost but may need to be changed in some circumstances + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { + geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { + geyserConfig.getRemote().setAddress(Bukkit.getIp()); + } + geyserConfig.getRemote().setPort(Bukkit.getPort()); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(Bukkit.getPort()); + } + geyserConfig.loadFloodgate(this); // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes From 770dfca32889b7d8e9c4fbc511966b3f0020a65f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 4 Sep 2022 16:35:50 -0500 Subject: [PATCH 226/358] Fix dumps --- .../java/org/geysermc/geyser/dump/DumpInfo.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 da52d064cae..4c2d24dd4bb 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -70,7 +70,7 @@ public class DumpInfo { private final String cpuName; private final Locale systemLocale; private final String systemEncoding; - private Properties gitInfo; + private final GitInfo gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; private final Object2IntMap userPlatforms; @@ -89,11 +89,7 @@ public 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.VERSION, GeyserImpl.BUILD_NUMBER, GeyserImpl.GIT_VERSION, GeyserImpl.BRANCH); this.config = GeyserImpl.getInstance().getConfig(); this.floodgate = new Floodgate(); @@ -300,4 +296,13 @@ public static class ExtensionInfo { public String main; public List authors; } + + @Getter + @AllArgsConstructor + public static class GitInfo { + private final String version; + private final String buildNumber; + private final String commitHash; + private final String branchName; + } } From 25f43f3152a2166abbb64936616299d9f0c9e3ed Mon Sep 17 00:00:00 2001 From: Typical <53472591+TypicalShavonne@users.noreply.github.com> Date: Mon, 5 Sep 2022 10:35:47 +0700 Subject: [PATCH 227/358] Updating README (#3268) --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 464e67d76da..d02d50d2ba0 100644 --- a/README.md +++ b/README.md @@ -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 From c97ff4f6124c7d36e19569b9b84db72d6f1ddf3b Mon Sep 17 00:00:00 2001 From: AnhNguyenlost13 <94160753+AnhNguyenlost13@users.noreply.github.com> Date: Wed, 7 Sep 2022 20:58:34 +0700 Subject: [PATCH 228/358] Add tooltips command (#65) Tooltips command should be there and enabled by default --- bootstrap/fabric/src/main/resources/permissions.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml index 8a995f8dc58..ae20447ed48 100644 --- a/bootstrap/fabric/src/main/resources/permissions.yml +++ b/bootstrap/fabric/src/main/resources/permissions.yml @@ -6,7 +6,8 @@ commands: - statistics - settings - offhand + - tooltips # - list # - reload # - version -# - dump \ No newline at end of file +# - dump From 2d7a463089698a8ed33d4261bf64ae3e1a892ee5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 11 Sep 2022 19:26:22 -0400 Subject: [PATCH 229/358] Make Geyser dumps backwards compatible --- core/build.gradle.kts | 6 +++++- .../src/main/java/org/geysermc/geyser/GeyserImpl.java | 2 ++ .../main/java/org/geysermc/geyser/dump/DumpInfo.java | 11 ++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 83591e7ad3e..41cf83538e6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -80,12 +80,16 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val gitVersion = "git-${branchName()}-${indra.commit()?.name?.substring(0, 7) ?: "0000000"}" + val commit = indra.commit() + val git = indra.git() + val gitVersion = "git-${branchName()}-${commit?.name?.substring(0, 7) ?: "0000000"}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) replaceToken("\${buildNumber}", buildNumber(), mainFile) replaceToken("\${branch}", branchName(), mainFile) + if (commit != null && commit.name != null) replaceToken("\${commit}", commit.name, mainFile) + if (git != null) replaceToken("\${repository}", git.repository.config.getString("remote", "origin", "url")) } fun Project.branchName(): String = diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 115a7245e03..ce2474d501c 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -116,6 +116,8 @@ public class GeyserImpl implements GeyserApi { public static final String BUILD_NUMBER = "${buildNumber}"; public static final String BRANCH = "${branch}"; + public static final String COMMIT = "${commit}"; + public static final String REPOSITORY = "${repository}"; /** * Oauth client ID for Microsoft authentication 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 4c2d24dd4bb..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; @@ -50,7 +51,6 @@ 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; @@ -89,7 +89,7 @@ public DumpInfo(boolean addLog) { this.systemLocale = Locale.getDefault(); this.systemEncoding = System.getProperty("file.encoding"); - this.gitInfo = new GitInfo(GeyserImpl.VERSION, GeyserImpl.BUILD_NUMBER, GeyserImpl.GIT_VERSION, GeyserImpl.BRANCH); + this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY); this.config = GeyserImpl.getInstance().getConfig(); this.floodgate = new Floodgate(); @@ -300,9 +300,14 @@ public static class ExtensionInfo { @Getter @AllArgsConstructor public static class GitInfo { - private final String version; 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; } } From a99afe44183b89b54b67993ffe0c23150a649d62 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:24:08 -0400 Subject: [PATCH 230/358] Remove usage of Fastutil Object2Reference maps These are only beneficial for containsValue checks. --- core/build.gradle.kts | 1 - .../geyser/extension/GeyserExtensionClassLoader.java | 6 +++--- .../geysermc/geyser/extension/GeyserExtensionLoader.java | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 41cf83538e6..5049c93ab1e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation("com.nukkitx.fastutil", "fastutil-int-boolean-maps", Versions.fastutilVersion) implementation("com.nukkitx.fastutil", "fastutil-object-int-maps", Versions.fastutilVersion) implementation("com.nukkitx.fastutil", "fastutil-object-object-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-reference-maps", Versions.fastutilVersion) // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index b220ab57662..b94e70ed0c1 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; @@ -39,7 +39,7 @@ public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; - private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); + private final Object2ObjectMap> classes = new Object2ObjectOpenHashMap<>(); public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, Path path) throws MalformedURLException { super(new URL[] { path.toUri().toURL() }, parent); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 44558e79882..7e998e41333 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,8 +25,8 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.Geyser; @@ -51,7 +51,7 @@ public class GeyserExtensionLoader extends ExtensionLoader { private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; - private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); + private final Object2ObjectMap> classes = new Object2ObjectOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); private final Path extensionsDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("extensions"); From 6df8740955f0060821e7e2c7b7fbcb0a25496cbc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:19:56 -0400 Subject: [PATCH 231/358] Only register commands on Spigot if the extension has commands --- .../geyser/api/extension/Extension.java | 2 +- .../geyser/api/extension/ExtensionLoader.java | 3 +- .../api/extension/ExtensionManager.java | 36 ++------------- .../platform/spigot/GeyserSpigotPlugin.java | 16 +++---- .../java/org/geysermc/geyser/GeyserImpl.java | 8 +--- .../geyser/command/GeyserCommand.java | 3 +- .../extension/GeyserExtensionManager.java | 45 +++---------------- 7 files changed, 21 insertions(+), 92 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 8d4e4da6473..33fc159de52 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -124,7 +124,7 @@ default ExtensionLogger logger() { */ @NonNull default ExtensionLoader extensionLoader() { - return Objects.requireNonNull(this.extensionManager().extensionLoader(this)); + return Objects.requireNonNull(this.extensionManager().extensionLoader()); } /** diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java index c84c37919b5..30414d500e4 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -34,7 +34,6 @@ * The extension loader is responsible for loading, unloading, enabling and disabling extensions */ public abstract class ExtensionLoader { - /** * Gets if the given {@link Extension} is enabled. * @@ -101,6 +100,6 @@ public abstract class ExtensionLoader { * @param extensionManager the extension manager */ protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { - extensionManager.register(extension, this); + extensionManager.register(extension); } } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java index 65d6c66da58..a9d0d7376d2 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -29,7 +29,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; -import java.util.Map; /** * Manages Geyser {@link Extension}s @@ -59,15 +58,6 @@ public abstract class ExtensionManager { */ public abstract void disable(@NonNull Extension extension); - /** - * Gets the {@link ExtensionLoader} responsible for loading - * the given {@link Extension}. - * - * @return the extension loader for loading the given extension - */ - @Nullable - public abstract ExtensionLoader extensionLoader(@NonNull Extension extension); - /** * Gets all the {@link Extension}s currently loaded. * @@ -77,37 +67,19 @@ public abstract class ExtensionManager { public abstract Collection extensions(); /** - * Gets the {@link ExtensionLoader} with the given identifier. + * Gets the {@link ExtensionLoader}. * - * @param identifier the identifier - * @return the extension loader at the given identifier + * @return the extension loader */ @Nullable - public abstract ExtensionLoader extensionLoader(@NonNull String identifier); - - /** - * Registers an {@link ExtensionLoader} with the given identifier. - * - * @param identifier the identifier - * @param extensionLoader the extension loader - */ - public abstract void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader); - - /** - * Gets all the currently registered {@link ExtensionLoader}s. - * - * @return all the currently registered extension loaders - */ - @NonNull - public abstract Map extensionLoaders(); + public abstract ExtensionLoader extensionLoader(); /** * Registers an {@link Extension} with the given {@link ExtensionLoader}. * * @param extension the extension - * @param loader the loader */ - public abstract void register(@NonNull Extension extension, @NonNull ExtensionLoader loader); + public abstract void register(@NonNull Extension extension); /** * Loads all extensions from the given {@link ExtensionLoader}. diff --git a/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 4407af6c787..60b1cfa218e 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 @@ -62,12 +62,7 @@ import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigot1_12NativeWorldManager; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigot1_12WorldManager; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotFallbackWorldManager; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager; -import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.*; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -204,12 +199,14 @@ public void onServerLoaded(ServerLoadEvent event) { } }, 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.geyser.extensionManager().extensions()) { - + for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) { // Thanks again, Bukkit try { Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); @@ -245,9 +242,6 @@ private void postStartup() { } geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); - this.geyserCommandManager.init(); - boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { try { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index ce2474d501c..a10e54f9049 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -147,7 +147,7 @@ public class GeyserImpl implements GeyserApi { private final GeyserBootstrap bootstrap; private final EventBus eventBus; - private GeyserExtensionManager extensionManager; + private final GeyserExtensionManager extensionManager; private Metrics metrics; @@ -173,6 +173,7 @@ private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { /* Load Extensions */ this.extensionManager = new GeyserExtensionManager(); this.extensionManager.init(); + this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); } public void initialize() { @@ -186,11 +187,6 @@ public void initialize() { logger.info(""); logger.info("******************************************"); - - /* Enable extensions */ - this.extensionManager.enableExtensions(); - this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); - /* Initialize registries */ Registries.init(); BlockRegistries.init(); 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 0d020ad08db..5808dbc2ce5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -33,7 +33,6 @@ import org.geysermc.geyser.session.GeyserSession; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,7 +48,7 @@ public abstract class GeyserCommand implements Command { protected final String description; protected final String permission; - private List aliases = new ArrayList<>(); + private List aliases = Collections.emptyList(); public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args); diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 7d80c2cf60a..5dd92430119 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.extension; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; @@ -39,34 +37,23 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Collectors; public class GeyserExtensionManager extends ExtensionManager { - private static final Key BASE_EXTENSION_LOADER_KEY = Key.key("geysermc", "base"); - - private final Map extensionLoaderTypes = new Object2ObjectOpenHashMap<>(); - + private final GeyserExtensionLoader extensionLoader = new GeyserExtensionLoader(); private final Map extensions = new LinkedHashMap<>(); - private final Map extensionsLoaders = new LinkedHashMap<>(); public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - extensionLoaderTypes.put(BASE_EXTENSION_LOADER_KEY, new GeyserExtensionLoader()); - for (ExtensionLoader loader : this.extensionLoaders().values()) { - this.loadAllExtensions(loader); - } + loadAllExtensions(this.extensionLoader); + enableExtensions(); GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); } @Override public Extension extension(@NonNull String name) { - if (this.extensions.containsKey(name)) { - return this.extensions.get(name); - } - - return null; + return this.extensions.get(name); } @Override @@ -121,37 +108,19 @@ public void disableExtensions() { } } - @Override - public ExtensionLoader extensionLoader(@NonNull Extension extension) { - return this.extensionsLoaders.get(extension); - } - @NonNull @Override public Collection extensions() { return Collections.unmodifiableCollection(this.extensions.values()); } - @Nullable - @Override - public ExtensionLoader extensionLoader(@NonNull String identifier) { - return this.extensionLoaderTypes.get(Key.key(identifier)); - } - - @Override - public void registerExtensionLoader(@NonNull String identifier, @NonNull ExtensionLoader extensionLoader) { - this.extensionLoaderTypes.put(Key.key(identifier), extensionLoader); - } - - @NonNull @Override - public Map extensionLoaders() { - return this.extensionLoaderTypes.entrySet().stream().collect(Collectors.toMap(key -> key.getKey().asString(), Map.Entry::getValue)); + public @Nullable ExtensionLoader extensionLoader() { + return this.extensionLoader; } @Override - public void register(@NonNull Extension extension, @NonNull ExtensionLoader loader) { - this.extensionsLoaders.put(extension, loader); + public void register(@NonNull Extension extension) { this.extensions.put(extension.name(), extension); } } From 77827d5cf5b81457f912570d1c4673e83c872813 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 14 Sep 2022 18:09:08 -0400 Subject: [PATCH 232/358] Return to using snapshot pinned Network version --- build-logic/src/main/kotlin/Versions.kt | 4 +--- settings.gradle.kts | 12 +----------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 44996cd3d90..2dac0f4c6db 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -32,9 +32,7 @@ object Versions { const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" const val protocolVersion = "0bd459f" - // Not pinned to specific version due to possible gradle bug - // See comment in settings.gradle.kts - const val raknetVersion = "1.6.28-SNAPSHOT" + const val raknetVersion = "1.6.28-20220125.214016-6" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "9f78bd5" const val packetlibVersion = "3.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index dd08f3922ad..3aa66d493ed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,17 +4,7 @@ dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { // Floodgate, Cumulus etc. - maven("https://repo.opencollab.dev/maven-releases") { - mavenContent { releasesOnly() } - } - maven("https://repo.opencollab.dev/maven-snapshots") { - mavenContent { - // This has the unintended side effect of not allowing snapshot version pinning. - // Likely a bug in Gradle's implementation of snapshot pinning - // See https://github.com/gradle/gradle/pull/406 - snapshotsOnly() - } - } + maven("https://repo.opencollab.dev/main") // Paper, Velocity maven("https://repo.papermc.io/repository/maven-public") From e64e12ff98886a338a3beee76f49a86010cc388f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 14 Sep 2022 21:17:08 -0400 Subject: [PATCH 233/358] Initial support for 1.19.30 Bedrock --- build-logic/src/main/kotlin/Versions.kt | 2 +- core/build.gradle.kts | 2 +- .../network/ConnectorServerEventHandler.java | 3 +- .../geysermc/geyser/network/GameProtocol.java | 2 + .../geyser/network/LoggingPacketHandler.java | 7 +++ .../geyser/network/UpstreamPacketHandler.java | 15 +++++ .../populator/RecipeRegistryPopulator.java | 7 ++- .../geyser/session/SessionManager.java | 3 +- .../java/JavaUpdateRecipesTranslator.java | 63 +++++++++---------- .../JavaContainerSetSlotTranslator.java | 3 +- 10 files changed, 66 insertions(+), 41 deletions(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 2dac0f4c6db..8d0185cc8ca 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -31,7 +31,7 @@ object Versions { const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" - const val protocolVersion = "0bd459f" + const val protocolVersion = "f0feacd" const val raknetVersion = "1.6.28-20220125.214016-6" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "9f78bd5" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5049c93ab1e..b0325e33c64 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) - api("com.github.CloudburstMC.Protocol", "bedrock-v544", Versions.protocolVersion) { + api("com.github.CloudburstMC.Protocol", "bedrock-v553", Versions.protocolVersion) { exclude("com.nukkitx.network", "raknet") exclude("com.nukkitx", "nbt") } 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 4ed077a7b98..bf655740f76 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -28,6 +28,7 @@ import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; +import com.nukkitx.protocol.bedrock.v553.Bedrock_v553; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.DefaultEventLoopGroup; @@ -171,7 +172,7 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { @Override public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { try { - bedrockServerSession.setPacketCodec(GameProtocol.DEFAULT_BEDROCK_CODEC); + bedrockServerSession.setPacketCodec(Bedrock_v553.V553_CODEC); // Has the RequestNetworkSettingsPacket bedrockServerSession.setLogging(true); bedrockServerSession.setCompressionLevel(geyser.getConfig().getBedrock().getCompressionLevel()); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(geyser, new GeyserSession(geyser, bedrockServerSession, eventLoopGroup.next()))); diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 006f40ace38..dde0b0c0448 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -31,6 +31,7 @@ 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.v553.Bedrock_v553; import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; @@ -71,6 +72,7 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() .minecraftVersion("1.19.21/1.19.22") .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v553.V553_CODEC); } /** 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..7edf560e8cd 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,11 @@ public boolean handle(ItemComponentPacket packet) { public boolean handle(FilterTextPacket packet) { return defaultHandler(packet); } + + // 1.19.30 new packet + + @Override + public boolean handle(RequestNetworkSettingsPacket packet) { + return defaultHandler(packet); + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 5a8fa301301..286a2eb9b30 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -28,6 +28,7 @@ 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; @@ -61,6 +62,20 @@ boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } + @Override + public boolean handle(RequestNetworkSettingsPacket packet) { + // 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()) { 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..920ada5fb43 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; @@ -171,7 +172,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 +192,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/session/SessionManager.java b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java index 2660f54b1f0..02940e00c27 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -62,7 +62,8 @@ 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); } 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 4c72a359a98..d11caf601b4 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.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -99,8 +100,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)); @@ -116,8 +117,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), @@ -141,14 +142,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++)); } } @@ -178,6 +179,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)) { @@ -188,7 +190,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 @@ -209,19 +211,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 -> new GroupedItem(((DefaultDescriptor) item.getDescriptor()).getItemId(), item.getCount()))); + Set optionSet = new HashSet<>(groupedByIds.size()); + for (Map.Entry> entry : groupedByIds.entrySet()) { if (entry.getValue().size() > 1) { GroupedItem groupedItem = entry.getKey(); int idCount = 0; @@ -234,42 +236,38 @@ 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(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) { @@ -288,6 +286,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/inventory/JavaContainerSetSlotTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java index 9a556939270..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; @@ -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", From 27b948a09b36d9bbaf01e75a129d6464f2ee54b9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 14 Sep 2022 21:22:34 -0400 Subject: [PATCH 234/358] Use Indra to get branch name --- core/build.gradle.kts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index b0325e33c64..f2225dbb8d2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -79,20 +79,18 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" + val branchName = indra.branchName() ?: "DEV" val commit = indra.commit() val git = indra.git() - val gitVersion = "git-${branchName()}-${commit?.name?.substring(0, 7) ?: "0000000"}" + val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) replaceToken("\${gitVersion}", gitVersion, mainFile) replaceToken("\${buildNumber}", buildNumber(), mainFile) - replaceToken("\${branch}", branchName(), mainFile) + replaceToken("\${branch}", branchName, mainFile) if (commit != null && commit.name != null) replaceToken("\${commit}", commit.name, mainFile) if (git != null) replaceToken("\${repository}", git.repository.config.getString("remote", "origin", "url")) } -fun Project.branchName(): String = - System.getenv("GIT_BRANCH") ?: "local/dev" - fun Project.buildNumber(): Int = Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") \ No newline at end of file From 83be01958f577ddeca9d9934a3deafea9e2bc020 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 15 Sep 2022 15:53:03 -0400 Subject: [PATCH 235/358] Yeet logging into Microsoft with password This has been broken for ages; we need to finally remove it. --- .../geyser/util/LoginEncryptionUtils.java | 41 ++----------------- 1 file changed, 3 insertions(+), 38 deletions(-) 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 c8d6e42d71e..8d832f8fad0 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -245,13 +245,7 @@ public 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; } @@ -315,39 +309,10 @@ public static void buildAndShowLoginDetailsWindow(GeyserSession session) { .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") .input("geyser.auth.login.form.details.pass", "123456", "") .invalidResultHandler(() -> buildAndShowLoginDetailsWindow(session)) - .closedResultHandler(() -> { - if (session.isMicrosoftAccount()) { - buildAndShowMicrosoftAuthenticationWindow(session); - } else { - buildAndShowLoginWindow(session); - } - }) + .closedResultHandler(() -> buildAndShowLoginWindow(session)) .validResultHandler((response) -> session.authenticate(response.next(), response.next()))); } - /** - * Prompts the user between either OAuth code login or manual password authentication - */ - public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { - session.sendForm( - SimpleForm.builder() - .translator(GeyserLocale::getPlayerLocaleString, session.locale()) - .title("geyser.auth.login.form.notice.btn_login.microsoft") - .button("geyser.auth.login.method.browser") - .button("geyser.auth.login.method.password") - .button("geyser.auth.login.form.notice.btn_disconnect") - .closedOrInvalidResultHandler(() -> buildAndShowLoginWindow(session)) - .validResultHandler((response) -> { - if (response.clickedButtonId() == 0) { - session.authenticateWithMicrosoftCode(); - } else if (response.clickedButtonId() == 1) { - buildAndShowLoginDetailsWindow(session); - } else { - session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.locale())); - } - })); - } - /** * Shows the code that a user must input into their browser */ @@ -374,7 +339,7 @@ public 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", locale)); From 0aa7411d02380223046a9b55ad38eee8c64b84f6 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 15 Sep 2022 20:32:38 -0400 Subject: [PATCH 236/358] Fix git branch on Jenkins (#3286) --- core/build.gradle.kts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f2225dbb8d2..0d1c1682552 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -79,7 +79,8 @@ configure { val indra = the() val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" - val branchName = indra.branchName() ?: "DEV" + // On Jenkins, a detached head is checked out, so indra cannot determine the branch. Fortunately, this environment variable is available. + val branchName = indra.branchName() ?: System.getenv("GIT_BRANCH") ?: "DEV" val commit = indra.commit() val git = indra.git() val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" @@ -93,4 +94,4 @@ configure { } fun Project.buildNumber(): Int = - Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") \ No newline at end of file + Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") From c8a51d783468d18f6d61d8f219277c598020420d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 17 Sep 2022 16:37:30 -0400 Subject: [PATCH 237/358] Update to release protocol 1.19.30 --- build-logic/src/main/kotlin/Versions.kt | 2 +- core/build.gradle.kts | 2 +- .../geysermc/geyser/network/ConnectorServerEventHandler.java | 4 ++-- .../main/java/org/geysermc/geyser/network/GameProtocol.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 8d0185cc8ca..1aa91aee046 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -31,7 +31,7 @@ object Versions { const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" - const val protocolVersion = "f0feacd" + const val protocolVersion = "96a4daf" const val raknetVersion = "1.6.28-20220125.214016-6" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "9f78bd5" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 0d1c1682552..62e8f1c1762 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { // Network libraries implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) - api("com.github.CloudburstMC.Protocol", "bedrock-v553", Versions.protocolVersion) { + api("com.github.CloudburstMC.Protocol", "bedrock-v554", Versions.protocolVersion) { exclude("com.nukkitx.network", "raknet") exclude("com.nukkitx", "nbt") } 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 bf655740f76..c9a3201c1f1 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -28,7 +28,7 @@ import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; -import com.nukkitx.protocol.bedrock.v553.Bedrock_v553; +import com.nukkitx.protocol.bedrock.v554.Bedrock_v554; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.DefaultEventLoopGroup; @@ -172,7 +172,7 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { @Override public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { try { - bedrockServerSession.setPacketCodec(Bedrock_v553.V553_CODEC); // Has the RequestNetworkSettingsPacket + bedrockServerSession.setPacketCodec(Bedrock_v554.V554_CODEC); // Has the RequestNetworkSettingsPacket bedrockServerSession.setLogging(true); bedrockServerSession.setCompressionLevel(geyser.getConfig().getBedrock().getCompressionLevel()); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(geyser, new GeyserSession(geyser, bedrockServerSession, eventLoopGroup.next()))); diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index dde0b0c0448..ab931e9012e 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -31,7 +31,7 @@ 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.v553.Bedrock_v553; +import com.nukkitx.protocol.bedrock.v554.Bedrock_v554; import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; @@ -72,7 +72,7 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() .minecraftVersion("1.19.21/1.19.22") .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v553.V553_CODEC); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v554.V554_CODEC); } /** From 9fdbfdb0ab249f1763800cc9c3c88814831be10a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 17 Sep 2022 21:55:30 -0400 Subject: [PATCH 238/358] Fix Adventure version --- build-logic/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 1aa91aee046..edb9f82135e 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -36,7 +36,7 @@ object Versions { const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "9f78bd5" const val packetlibVersion = "3.0" - const val adventureVersion = "4.9.3" + const val adventureVersion = "4.12.0-20220629.025215-9" const val adventurePlatformVersion = "4.1.2" const val junitVersion = "4.13.1" const val checkerQualVersion = "3.19.0" From 64c03b9610e74d2c1912572b29cabbd48e148232 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 12:18:32 -0400 Subject: [PATCH 239/358] Correctly detect flying in 1.19.30 --- .../geyser/network/LoggingPacketHandler.java | 7 ++ .../BedrockAdventureSettingsTranslator.java | 18 +---- .../BedrockRequestAbilityTranslator.java | 67 +++++++++++++++++++ 3 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java 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 7edf560e8cd..8d2db081aca 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -857,6 +857,13 @@ 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 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 { + + @Override + public void translate(GeyserSession session, RequestAbilityPacket packet) { + 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); + } +} From 4e2d750791a4a892c9850c92de41f8386beba726 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 12:20:54 -0400 Subject: [PATCH 240/358] Gatekeep RequestAbilityPackets to 1.19.30+ --- .../main/java/org/geysermc/geyser/network/GameProtocol.java | 4 ++++ .../protocol/bedrock/BedrockRequestAbilityTranslator.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index ab931e9012e..f31d800c708 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -95,6 +95,10 @@ 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(); + } + /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * 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 index 1ac75079c22..fe8150d406b 100644 --- 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 @@ -30,6 +30,7 @@ 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; @@ -42,6 +43,11 @@ public class BedrockRequestAbilityTranslator extends PacketTranslator Date: Sun, 18 Sep 2022 13:27:16 -0400 Subject: [PATCH 241/358] Bump Protocol to fix 1.19.2x crafting --- build-logic/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index edb9f82135e..47f35726d92 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -31,7 +31,7 @@ object Versions { const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" - const val protocolVersion = "96a4daf" + const val protocolVersion = "fed46166" const val raknetVersion = "1.6.28-20220125.214016-6" const val mcauthlibVersion = "d9d773e" const val mcprotocollibversion = "9f78bd5" From bb2f4644beadd701a370c8bc80866707e233e4f2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:11:18 -0400 Subject: [PATCH 242/358] Fix IO_Uring being included in builds --- core/build.gradle.kts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 62e8f1c1762..d5f1c7a72d5 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -43,8 +43,6 @@ dependencies { api("com.github.steveice10", "packetlib", Versions.packetlibVersion) { exclude("io.netty", "netty-all") - // This is still experimental - additionally, it could only really benefit standalone - exclude("io.netty.incubator", "netty-incubator-transport-native-io_uring") } implementation("com.nukkitx.network", "raknet", Versions.raknetVersion) { @@ -63,6 +61,7 @@ dependencies { implementation("io.netty", "netty-transport-native-kqueue", Versions.nettyVersion, null, "osx-x86_64") // Adventure text serialization + implementation("net.kyori", "adventure-text-serializer-gson", Versions.adventureVersion) // Remove when we remove our Adventure bump implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) implementation("net.kyori", "adventure-text-serializer-plain", Versions.adventureVersion) @@ -75,6 +74,11 @@ dependencies { 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") +} + configure { val indra = the() From f71fa9ccacec8b56e49fa2037877b704898111c6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:18:48 -0400 Subject: [PATCH 243/358] Only check for correct protocol version once --- .../geyser/network/UpstreamPacketHandler.java | 39 +++++++++++++------ settings.gradle.kts | 3 ++ 2 files changed, 31 insertions(+), 11 deletions(-) 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 286a2eb9b30..c2a91fd759a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -62,8 +62,34 @@ 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; @@ -84,21 +110,12 @@ public boolean handle(LoginPacket loginPacket) { return true; } - BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); - if (packetCodec == null) { - String supportedVersions = GameProtocol.getAllSupportedBedrockVersions(); - if (loginPacket.getProtocolVersion() > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - // Too early to determine session locale - session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); - return true; - } else if (loginPacket.getProtocolVersion() < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); + if (!newProtocol) { + if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20 return true; } } - session.getUpstream().getSession().setPacketCodec(packetCodec); - // Set the block translation based off of version session.setBlockMappings(BlockRegistries.BLOCKS.forVersion(loginPacket.getProtocolVersion())); session.setItemMappings(Registries.ITEMS.forVersion(loginPacket.getProtocolVersion())); diff --git a/settings.gradle.kts b/settings.gradle.kts index 3aa66d493ed..f6d13ad0d38 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,9 @@ dependencyResolutionManagement { maven("https://jitpack.io") { content { includeGroupByRegex("com\\.github\\..*") } } + + // For Adventure snapshots + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") } } From 44e60b7ad83ae36777af7d4b0920230cf74d465d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:40:44 -0400 Subject: [PATCH 244/358] An educated guess to fix Git branch --- api/base/build.gradle.kts | 1 + .../org/geysermc/geyser/api/command/CommandExecutor.java | 5 +++-- .../java/org/geysermc/geyser/api/command/CommandSource.java | 6 ++++-- .../java/org/geysermc/geyser/api/event/EventRegistrar.java | 4 +++- .../org/geysermc/geyser/api/network/BedrockListener.java | 3 +++ .../java/org/geysermc/geyser/api/network/RemoteServer.java | 3 +++ core/build.gradle.kts | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index a6fa608cc76..c6cebe0ba92 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -4,4 +4,5 @@ dependencies { exclude(group = "com.google.guava", module = "guava") exclude(group = "org.lanternpowered", module = "lmbda") } + compileOnlyApi("org.jetbrains", "annotations", "23.0.0") } \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java index d384d097c99..12a54ee9082 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java @@ -25,13 +25,14 @@ package org.geysermc.geyser.api.command; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * Handles executing a command. * * @param the command source */ public interface CommandExecutor { - /** * Executes the given {@link Command} with the given * {@link CommandSource}. @@ -40,5 +41,5 @@ public interface CommandExecutor { * @param command the command * @param args the arguments */ - void execute(T source, Command command, String[] args); + void execute(@NonNull T source, @NonNull Command command, @NonNull String[] args); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index aabf0c4e8d9..45276e2c466 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.command; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * Represents an instance capable of sending commands. */ @@ -42,7 +44,7 @@ public interface CommandSource { * * @param message the message to send */ - void sendMessage(String message); + void sendMessage(@NonNull String message); /** * Sends the given messages to the command source @@ -58,7 +60,7 @@ default void sendMessage(String[] messages) { /** * If this source is the console. * - * @return true if this source is the console + * @return true if this source is the console */ boolean isConsole(); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java index 7a2cc0071e3..064dd55f60f 100644 --- 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 @@ -25,6 +25,7 @@ package org.geysermc.geyser.api.event; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; /** @@ -39,7 +40,8 @@ public interface EventRegistrar { * @param object the object to wrap around * @return an event registrar instance */ - static EventRegistrar of(Object object) { + @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/network/BedrockListener.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java index 58a597eb63e..61fe286aa13 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.network; +import org.checkerframework.checker.nullness.qual.NonNull; + /** * The listener that handles connections from Minecraft: * Bedrock Edition. @@ -37,6 +39,7 @@ public interface BedrockListener { * * @return the listening address */ + @NonNull String address(); /** diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java index b13ae593055..d0ac6ee7e44 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.network; +import org.jetbrains.annotations.NotNull; + /** * Represents the Java server that Geyser is connecting to. */ @@ -63,5 +65,6 @@ public interface RemoteServer { * * @return the auth type required by the remote server */ + @NotNull AuthType authType(); } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d5f1c7a72d5..98ddd99b82f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -84,7 +84,7 @@ configure { val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" // On Jenkins, a detached head is checked out, so indra cannot determine the branch. Fortunately, this environment variable is available. - val branchName = indra.branchName() ?: System.getenv("GIT_BRANCH") ?: "DEV" + val branchName = indra.branchName() ?: System.getenv("BRANCH") ?: "DEV" val commit = indra.commit() val git = indra.git() val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" From 8605f0a91cdd46b5b29bebe4f807d8b04be690a5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:56:30 -0400 Subject: [PATCH 245/358] Use master languages branch --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index d9290402706..51d6f5ba7d8 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit d92904027061856248ece8382face369e9cc5d67 +Subproject commit 51d6f5ba7d85bfda318879dad34481d9ef4d488d From f11dc6d03d6234d111b27983b3003137f2f74f1d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:56:46 -0400 Subject: [PATCH 246/358] A better educated guess to fix the git branch --- core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 98ddd99b82f..89d292bed66 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -84,7 +84,7 @@ configure { val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" // On Jenkins, a detached head is checked out, so indra cannot determine the branch. Fortunately, this environment variable is available. - val branchName = indra.branchName() ?: System.getenv("BRANCH") ?: "DEV" + val branchName = indra.branchName() ?: System.getenv("GIT_LOCAL_BRANCH") ?: "DEV" val commit = indra.commit() val git = indra.git() val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" From d4ab388258cb220e9fc7d534a31312bd53493fc6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:56:59 -0400 Subject: [PATCH 247/358] Remove unused annotation dependency --- api/base/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index c6cebe0ba92..a6fa608cc76 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -4,5 +4,4 @@ dependencies { exclude(group = "com.google.guava", module = "guava") exclude(group = "org.lanternpowered", module = "lmbda") } - compileOnlyApi("org.jetbrains", "annotations", "23.0.0") } \ No newline at end of file From fd2c24223078190d52176ec92cc637f73cd9b1cb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:59:49 -0400 Subject: [PATCH 248/358] oops --- .../java/org/geysermc/geyser/api/network/RemoteServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java index d0ac6ee7e44..8ac5d8a03ad 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.api.network; -import org.jetbrains.annotations.NotNull; +import org.checkerframework.checker.nullness.qual.NonNull; /** * Represents the Java server that Geyser is connecting to. @@ -65,6 +65,6 @@ public interface RemoteServer { * * @return the auth type required by the remote server */ - @NotNull + @NonNull AuthType authType(); } From 9791e7b544ddbc6d1236b2b62a8f64bfab462ec5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Sep 2022 16:29:44 -0400 Subject: [PATCH 249/358] One more try on branch name --- core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 89d292bed66..f5c41ed121e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -84,7 +84,7 @@ configure { val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" // On Jenkins, a detached head is checked out, so indra cannot determine the branch. Fortunately, this environment variable is available. - val branchName = indra.branchName() ?: System.getenv("GIT_LOCAL_BRANCH") ?: "DEV" + val branchName = indra.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV" val commit = indra.commit() val git = indra.git() val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" From c84d53c827914e1985b319bcc59a167730d9f246 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:22:09 -0400 Subject: [PATCH 250/358] Re-add git.properties (#3287) Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- core/build.gradle.kts | 70 ++++++++++++++++++++------ core/src/main/resources/git.properties | 7 +++ 2 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 core/src/main/resources/git.properties diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f5c41ed121e..2825ead1f24 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -79,23 +79,65 @@ configurations.api { exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring") } -configure { - val indra = the() +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" - // On Jenkins, a detached head is checked out, so indra cannot determine the branch. Fortunately, this environment variable is available. - val branchName = indra.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV" - val commit = indra.commit() - val git = indra.git() - val gitVersion = "git-${branchName}-${commit?.name?.substring(0, 7) ?: "0000000"}" - - replaceToken("\${version}", "${project.version} ($gitVersion)", mainFile) - replaceToken("\${gitVersion}", gitVersion, mainFile) - replaceToken("\${buildNumber}", buildNumber(), mainFile) - replaceToken("\${branch}", branchName, mainFile) - if (commit != null && commit.name != null) replaceToken("\${commit}", commit.name, mainFile) - if (git != null) replaceToken("\${repository}", git.repository.config.getString("remote", "origin", "url")) + val info = GitInfo() + + replaceToken("\${version}", "${project.version} (${info.gitVersion})", mainFile) + replaceToken("\${gitVersion}", info.gitVersion, mainFile) + replaceToken("\${buildNumber}", info.buildNumber, mainFile) + replaceToken("\${branch}", info.branch, mainFile) + replaceToken("\${commit}", info.commit, mainFile) + replaceToken("\${repository}", info.repository, mainFile) } fun Project.buildNumber(): Int = Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") + +inner class GitInfo { + val branch: String + val commit: String + val commitAbbrev: String + + val gitVersion: String + val version: String + val buildNumber: Int + + val commitMessage: String + val repository: String + + init { + // On Jenkins, a detached head is checked out, so indra cannot determine the branch. + // Fortunately, this environment variable is available. + branch = indraGit.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV" + + val commit = indraGit.commit() + this.commit = commit?.name ?: "0".repeat(40) + commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7) + + gitVersion = "git-${branch}-${commitAbbrev}" + version = "${project.version} ($gitVersion)" + buildNumber = buildNumber() + + val git = indraGit.git() + commitMessage = git?.commit()?.message ?: "" + repository = git?.repository?.config?.getString("remote", "origin", "url") ?: "" + } +} diff --git a/core/src/main/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} From f31b183a3363792cda5f6cb476beadeb9b319f23 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:26:47 -0400 Subject: [PATCH 251/358] Rename Geyser standalone jar to Geyser-Standalone.jar --- bootstrap/standalone/build.gradle.kts | 2 +- core/build.gradle.kts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index d49c7c49047..3c1a10b0973 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -27,7 +27,7 @@ application { } tasks.withType { - archiveBaseName.set("Geyser") + archiveBaseName.set("Geyser-Standalone") transform(Log4j2PluginsCacheFileTransformer()) } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2825ead1f24..49ce2fbffd8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,5 +1,4 @@ import net.kyori.blossom.BlossomExtension -import net.kyori.indra.git.IndraGitExtension plugins { id("net.kyori.blossom") @@ -109,7 +108,7 @@ configure { } fun Project.buildNumber(): Int = - Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") + System.getenv("BUILD_NUMBER")?.let { Integer.parseInt(it) } ?: -1 inner class GitInfo { val branch: String From 7cd71f570fd5cc314e9c581d5f78dbe093f45c92 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 20 Sep 2022 13:18:41 -0400 Subject: [PATCH 252/358] Fix some ordering that regressed some behavior --- .../bungeecord/GeyserBungeePlugin.java | 26 +++++++++---------- .../platform/spigot/GeyserSpigotPlugin.java | 18 ++++++------- .../velocity/GeyserVelocityPlugin.java | 18 ++++++------- 3 files changed, 31 insertions(+), 31 deletions(-) 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 13604a3d497..d6b2524732c 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 @@ -72,6 +72,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override 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 { @@ -86,8 +88,6 @@ public void onLoad() { getLogger().warning("/_____________\\"); } - GeyserLocale.init(this); - if (!getDataFolder().exists()) getDataFolder().mkdir(); @@ -117,17 +117,6 @@ public void onEnable() { return; } - 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) { - // Floodgate installed means that the user wants Floodgate authentication - geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); - } - - geyserConfig.loadFloodgate(this); - if (getProxy().getConfig().getListeners().size() == 1) { ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0]; @@ -148,6 +137,17 @@ public void onEnable() { } } + 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) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + } + + geyserConfig.loadFloodgate(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 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 60b1cfa218e..e6bc8d984e1 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 @@ -164,15 +164,6 @@ public void onEnable() { return; } - if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); - this.getPluginLoader().disablePlugin(this); - } 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."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); - } - // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); @@ -187,6 +178,15 @@ public void onEnable() { geyserConfig.getBedrock().setPort(Bukkit.getPort()); } + 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); + } 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."); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + } + geyserConfig.loadFloodgate(this); // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes 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 dc31b3fdd21..e92bb6cf245 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 @@ -88,6 +88,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public void onEnable() { + try { + Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); + } catch (NoSuchMethodException e) { + // velocitypowered.com has a build that is very outdated + logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + + "that has likely been downloaded is very outdated and does not support 1.19."); + return; + } + GeyserLocale.init(this); try { @@ -124,15 +133,6 @@ public void onEnable() { this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); - try { - Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); - } catch (NoSuchMethodException e) { - // velocitypowered.com has a build that is very outdated - logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + - "that has likely been downloaded is very outdated and does not support 1.19."); - return; - } - // Remove this in like a year try { // Should only exist on 1.0 From 98cfdb0b339213cf2558db525762a00199c77da2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 20 Sep 2022 14:25:39 -0400 Subject: [PATCH 253/358] Fix epoll on at least standalone --- core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 49ce2fbffd8..ef1df3deae1 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { api("com.github.GeyserMC", "MCAuthLib", Versions.mcauthlibVersion) api("com.github.GeyserMC", "MCProtocolLib", Versions.mcprotocollibversion) { + exclude("io.netty", "netty-all") exclude("com.github.GeyserMC", "packetlib") exclude("com.github.GeyserMC", "mcauthlib") } From 8beae31cee80e94e773c37c15a2956034f1d3e69 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:02:38 -0400 Subject: [PATCH 254/358] Copy what Floodgate does for artifact publishing --- Jenkinsfile | 3 +-- .../geyser.publish-conventions.gradle.kts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 28f9e7a374a..0123a277180 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -29,7 +29,6 @@ pipeline { when { anyOf { branch "master" - branch "feature/extensions" } } @@ -50,7 +49,7 @@ pipeline { rootDir: "", useWrapper: true, buildFile: 'build.gradle.kts', - tasks: 'build artifactoryPublish', + tasks: 'artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) diff --git a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts index f1cb8b13905..68ab5933733 100644 --- a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -5,24 +5,27 @@ plugins { } publishing { - publications.create("mavenJava") { - groupId = project.group as String - artifactId = project.name - version = project.version as String + publications { + create("mavenJava") { + groupId = project.group as String + artifactId = project.name + version = project.version as String - artifact(tasks["shadowJar"]) - artifact(tasks["sourcesJar"]) + artifact(tasks["shadowJar"]) + artifact(tasks["sourcesJar"]) + } } } artifactory { + setContextUrl("https://repo.opencollab.dev/artifactory") publish { repository { setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases") setMavenCompatible(true) } defaults { - publishConfigs("archives") + publications("mavenJava") setPublishArtifacts(true) setPublishPom(true) setPublishIvy(false) From 55f7253a98d6d76e3c7d8bc40a705c3d7533d89b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 12:27:16 -0400 Subject: [PATCH 255/358] Let biomes NBT be parsed by 1.19.30 --- .../java/org/geysermc/geyser/registry/Registries.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 4b361ba4faa..2c1c51baf0a 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; @@ -179,5 +180,15 @@ 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); + + // TEMPORARY FIX TO MAKE OLD BIOMES NBT WORK WITH 1.19.30 + NbtMapBuilder biomesNbt = NbtMap.builder(); + for (Map.Entry entry : BIOMES_NBT.get().entrySet()) { + String key = entry.getKey(); + NbtMapBuilder value = ((NbtMap) entry.getValue()).toBuilder(); + value.put("name_hash", key); + biomesNbt.put(key, value.build()); + } + BIOMES_NBT.set(biomesNbt.build()); } } \ No newline at end of file From 5206bc3b993950e323aaac15a01324ff285a6131 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 12:49:38 -0400 Subject: [PATCH 256/358] Elaborate if secure profiles need to be disabled --- .../java/JavaLoginDisconnectTranslator.java | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index 356fe645b83..0720963fb74 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -46,10 +46,30 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator children = component.children(); for (int i = 0; i < children.size(); i++) { if (children.get(i) instanceof TextComponent child && child.content().startsWith("Outdated server!")) { // Reproduced on Paper 1.17.1 - isOutdatedMessage = true; - break; + return true; } } } } } + return false; + } - String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale()); - String disconnectMessage; - if (isOutdatedMessage) { - String locale = session.locale(); - PlatformType platform = session.getGeyser().getPlatformType(); - String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY) ? - "geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server"; - disconnectMessage = GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n' - + GeyserLocale.getPlayerLocaleString("geyser.network.remote.original_disconnect_message", locale, serverDisconnectMessage); - } else { - disconnectMessage = serverDisconnectMessage; - } - - // The client doesn't manually get disconnected so we have to do it ourselves - session.disconnect(disconnectMessage); + private boolean testForMissingProfilePublicKey(Component disconnectReason) { + return disconnectReason instanceof TranslatableComponent component && "multiplayer.disconnect.missing_public_key".equals(component.key()); } @Override From 6df9081d6e3abe9dd743e052959c3c009a41d8fb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 12:52:29 -0400 Subject: [PATCH 257/358] Possibly fix recipe class cast errors --- .../protocol/java/JavaUpdateRecipesTranslator.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 d11caf601b4..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 @@ -39,10 +39,7 @@ 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.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 lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; @@ -221,7 +218,7 @@ private ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredie Ingredient ingredient = ingredients[i]; Map> groupedByIds = Arrays.stream(ingredient.getOptions()) .map(item -> ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, item))) - .collect(Collectors.groupingBy(item -> new GroupedItem(((DefaultDescriptor) item.getDescriptor()).getItemId(), item.getCount()))); + .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) { @@ -236,7 +233,7 @@ private ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredie if (entry.getValue().size() < idCount) { optionSet.addAll(entry.getValue()); } else { - optionSet.add(new ItemDescriptorWithCount(new DefaultDescriptor(groupedItem.id, Short.MAX_VALUE), groupedItem.count)); + optionSet.add(groupedItem.id == 0 ? ItemDescriptorWithCount.EMPTY : new ItemDescriptorWithCount(new DefaultDescriptor(groupedItem.id, Short.MAX_VALUE), groupedItem.count)); } } else { ItemDescriptorWithCount item = entry.getValue().get(0); @@ -270,8 +267,8 @@ private ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredie 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++; From 50b99d70a176de09097b138e570d33e52e716a1c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:39:06 -0400 Subject: [PATCH 258/358] Return to using NBT dependency provided by Protocol --- core/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ef1df3deae1..4e8b92fef4e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { api("com.github.CloudburstMC.Protocol", "bedrock-v554", Versions.protocolVersion) { exclude("com.nukkitx.network", "raknet") - exclude("com.nukkitx", "nbt") } api("com.github.GeyserMC", "MCAuthLib", Versions.mcauthlibVersion) From c997dffea4c1ccacbd113298ef01949e6f0b2fb4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:29:43 -0400 Subject: [PATCH 259/358] Indicate 1.19.30 Bedrock support on the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d02d50d2ba0..997bbff58b7 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.1x/1.19.20/1.19.21/1.19.22 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.30 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. From d2b7b8c39216d2a6fe6350929c74e8b9e6388b32 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:33:33 -0400 Subject: [PATCH 260/358] Set the default Bedrock version to 1.19.30 --- .../java/org/geysermc/geyser/network/GameProtocol.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index f31d800c708..4c5c099f033 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -31,6 +31,7 @@ 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 org.geysermc.geyser.session.GeyserSession; @@ -46,10 +47,7 @@ public final class GameProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v544.V544_CODEC.toBuilder() - .minecraftVersion("1.19.22") - .protocolVersion(545) - .build(); + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v554.V554_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -69,10 +67,10 @@ public final class GameProtocol { .minecraftVersion("1.19.10/1.19.11") .build()); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + 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); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** From b794569388a717cd8b746a965da1edfd9fd63554 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 22 Sep 2022 19:49:35 -0400 Subject: [PATCH 261/358] 1.19.30 support probably --- bootstrap/fabric/build.gradle | 4 ++-- bootstrap/fabric/gradle.properties | 2 +- .../platform/fabric/GeyserFabricMod.java | 22 ++++++++++--------- .../fabric/command/FabricCommandSender.java | 6 ++--- .../command/GeyserFabricCommandExecutor.java | 12 +++++----- .../command/GeyserFabricCommandManager.java | 6 ++--- .../world/GeyserFabricWorldManager.java | 3 +-- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle index 96f3ee8b12a..2ea9fd6868d 100644 --- a/bootstrap/fabric/build.gradle +++ b/bootstrap/fabric/build.gradle @@ -27,8 +27,8 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - implementation "org.geysermc:core:${project.mod_version}" - shadow("org.geysermc:core:${project.mod_version}") { + api "org.geysermc.geyser:core:${project.mod_version}" + shadow("org.geysermc.geyser:core:${project.mod_version}") { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j' diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties index 9c79b7141d0..341d6bbe40d 100644 --- a/bootstrap/fabric/gradle.properties +++ b/bootstrap/fabric/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.19.1 yarn_mappings=1.19.1+build.1 loader_version=0.14.8 # Mod Properties -mod_version=2.0.7-SNAPSHOT +mod_version=2.1.0-SNAPSHOT maven_group=org.geysermc.platform archives_base_name=Geyser-Fabric # Dependencies diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index f6a657bba25..5a0b2527a1b 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -37,11 +37,12 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.command.CommandManager; +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.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -142,7 +143,7 @@ public void onEnable() { public void startGeyser(MinecraftServer server) { this.server = server; - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); String ip = server.getServerIp(); int port = ((GeyserServerPortGetter) server).geyser$getServerPort(); @@ -153,12 +154,12 @@ public void startGeyser(MinecraftServer server) { } if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().port()); } Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); boolean floodgatePresent = floodgate.isPresent(); - if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && !floodgatePresent) { + 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) { @@ -169,7 +170,8 @@ public void startGeyser(MinecraftServer server) { geyserConfig.loadFloodgate(this, floodgate.orElse(null)); - this.connector = GeyserImpl.start(PlatformType.FABRIC, this); + this.connector = GeyserImpl.load(PlatformType.FABRIC, this); + GeyserImpl.start(); // shrug this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); @@ -180,13 +182,13 @@ public void startGeyser(MinecraftServer server) { // Start command building // Set just "geyser" as the help command GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(connector, - connector.getCommandManager().getCommands().get("help"), !playerCommands.contains("help")); + (GeyserCommand) connector.commandManager().getCommands().get("help"), !playerCommands.contains("help")); commandExecutors.add(helpExecutor); LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser").executes(helpExecutor); // Register all subcommands as valid - for (Map.Entry command : connector.getCommandManager().getCommands().entrySet()) { - GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, command.getValue(), + for (Map.Entry command : connector.commandManager().getCommands().entrySet()) { + GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, (GeyserCommand) command.getValue(), !playerCommands.contains(command.getKey())); commandExecutors.add(executor); builder.then(net.minecraft.server.command.CommandManager.literal(command.getKey()).executes(executor)); @@ -216,7 +218,7 @@ public GeyserLogger getGeyserLogger() { } @Override - public CommandManager getGeyserCommandManager() { + public GeyserCommandManager getGeyserCommandManager() { return geyserCommandManager; } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java index 57f877637ec..20dee1b217a 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java @@ -29,11 +29,11 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; import org.geysermc.platform.fabric.GeyserFabricMod; -public class FabricCommandSender implements CommandSender { +public class FabricCommandSender implements GeyserCommandSource { private final ServerCommandSource source; @@ -66,7 +66,7 @@ public boolean hasPermission(String s) { // Workaround for our commands because fabric doesn't have native permissions for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { - if (executor.getCommand().getPermission().equals(s)) { + if (executor.getCommand().permission().equals(s)) { return executor.canRun(source); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java index c1d8ebc29ec..07b8bd51949 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -29,15 +29,17 @@ import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.command.ServerCommandSource; 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.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.platform.fabric.GeyserFabricMod; import org.geysermc.platform.fabric.GeyserFabricPermissions; -public class GeyserFabricCommandExecutor extends CommandExecutor implements Command { +import java.util.Collections; + +public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { private final GeyserCommand command; /** @@ -46,7 +48,7 @@ public class GeyserFabricCommandExecutor extends CommandExecutor implements Comm private final boolean requiresPermission; public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, boolean requiresPermission) { - super(connector); + super(connector, Collections.singletonMap(command.name(), command)); this.command = command; this.requiresPermission = requiresPermission; } @@ -70,12 +72,12 @@ public int run(CommandContext context) { sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); return 0; } - if (this.command.getName().equals("reload")) { + 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.getLocale())); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); return 0; } command.execute(session, sender, new String[0]); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java index d548aa823c1..feaf401302c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java @@ -26,16 +26,16 @@ package org.geysermc.platform.fabric.command; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommandManager; -public class GeyserFabricCommandManager extends CommandManager { +public class GeyserFabricCommandManager extends GeyserCommandManager { public GeyserFabricCommandManager(GeyserImpl connector) { super(connector); } @Override - public String getDescription(String command) { + public String description(String command) { return ""; } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java index 5a66f6ae812..40c7fd302ab 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java @@ -124,10 +124,9 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole @Override public boolean hasPermission(GeyserSession session, String permission) { - // Workaround for our commands because fabric doesn't have native permissions for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { - if (executor.getCommand().getPermission().equals(permission)) { + if (executor.getCommand().permission().equals(permission)) { return executor.canRun(getPlayer(session).getCommandSource()); } } From 91ad2e58fc15233f015037acfec7d5a5f2ed4f2b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 22 Sep 2022 21:25:46 -0400 Subject: [PATCH 262/358] Should fix the command NPE --- .../main/java/org/geysermc/platform/fabric/GeyserFabricMod.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java index 5a0b2527a1b..cdd7d358a49 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java @@ -176,6 +176,7 @@ public void startGeyser(MinecraftServer server) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); this.geyserCommandManager = new GeyserFabricCommandManager(connector); + this.geyserCommandManager.init(); this.geyserWorldManager = new GeyserFabricWorldManager(server); From 2c5c72f85fd1f15beefb26ce1a5794e593f98ca7 Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Fri, 23 Sep 2022 16:04:15 +0200 Subject: [PATCH 263/358] Replace particle explosion with particle block explosion in JavaExplodePacket (#3301) --- build-logic/src/main/kotlin/Versions.kt | 1 - core/build.gradle.kts | 2 -- .../java/level/JavaExplodeTranslator.java | 26 +++++++++++++------ .../java/level/JavaLevelEventTranslator.java | 6 ++--- .../level/JavaLevelParticlesTranslator.java | 2 +- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 47f35726d92..d492022baf6 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -29,7 +29,6 @@ object Versions { const val nettyVersion = "4.1.80.Final" const val guavaVersion = "29.0-jre" const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 - const val nbtVersion = "2.1.0" const val websocketVersion = "1.5.1" const val protocolVersion = "fed46166" const val raknetVersion = "1.6.28-20220125.214016-6" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 4e8b92fef4e..1bf227e58d1 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -16,8 +16,6 @@ dependencies { api("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", Versions.jacksonVersion) api("com.google.guava", "guava", Versions.guavaVersion) - api("com.nukkitx", "nbt", Versions.nbtVersion) - // Fastutil Maps implementation("com.nukkitx.fastutil", "fastutil-int-int-maps", Versions.fastutilVersion) implementation("com.nukkitx.fastutil", "fastutil-int-long-maps", Versions.fastutilVersion) 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..51c508e578f 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,11 @@ 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.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.LevelEventType; 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 +46,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/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index b9fccc80aa4..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 @@ -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 c77caadeeb4..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 @@ -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)) From e491cf8a17d8e49d215834671205ada10e293dcc Mon Sep 17 00:00:00 2001 From: SupremeMortal Date: Mon, 26 Sep 2022 16:43:17 +0100 Subject: [PATCH 264/358] Use Gradle's dependency catalogue feature (#3305) Move all of our dependencies to a single catalogue file to make maintenance of them easier. --- api/base/build.gradle.kts | 4 +- bootstrap/bungeecord/build.gradle.kts | 6 +- bootstrap/spigot/build.gradle.kts | 10 +-- bootstrap/sponge/build.gradle.kts | 4 +- bootstrap/standalone/build.gradle.kts | 10 +-- bootstrap/velocity/build.gradle.kts | 6 +- build-logic/src/main/kotlin/Versions.kt | 45 ---------- build-logic/src/main/kotlin/extensions.kt | 8 ++ .../kotlin/geyser.base-conventions.gradle.kts | 2 +- common/build.gradle.kts | 4 +- core/build.gradle.kts | 45 ++++------ gradle/libs.versions.toml | 89 +++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- 13 files changed, 134 insertions(+), 101 deletions(-) delete mode 100644 build-logic/src/main/kotlin/Versions.kt create mode 100644 gradle/libs.versions.toml diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts index a6fa608cc76..6b6fb8f46b2 100644 --- a/api/base/build.gradle.kts +++ b/api/base/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { - api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) - api("org.geysermc.event", "events", Versions.eventsVersion) { + api(libs.cumulus) + api(libs.events) { exclude(group = "com.google.guava", module = "guava") exclude(group = "org.lanternpowered", module = "lmbda") } diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 9f3b49b67d1..3e0e9c147f3 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -1,9 +1,7 @@ -val bungeeVersion = "a7c6ede"; - dependencies { api(projects.core) - implementation("net.kyori", "adventure-text-serializer-bungeecord", Versions.adventurePlatformVersion) + implementation(libs.adventure.text.serializer.bungeecord) } platformRelocate("net.md_5.bungee.jni") @@ -12,7 +10,7 @@ platformRelocate("io.netty.channel.kqueue") // This is not used because relocati platformRelocate("net.kyori") // These dependencies are already present on the platform -provided("com.github.SpigotMC.BungeeCord", "bungeecord-proxy", bungeeVersion) +provided(libs.bungeecord.proxy) application { mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain") diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 5a459a09b53..31e68ed92ee 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -6,19 +6,19 @@ val commodoreVersion = "2.2" dependencies { api(projects.core) - implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion) + implementation(libs.adapters.spigot) - implementation("me.lucko", "commodore", commodoreVersion) + implementation(libs.commodore) - implementation("net.kyori", "adventure-text-serializer-bungeecord", Versions.adventurePlatformVersion) + implementation(libs.adventure.text.serializer.bungeecord) // Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 - compileOnly("io.papermc.paper", "paper-api", paperVersion) { + compileOnly(libs.paper.api) { attributes { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } } - compileOnly("io.papermc.paper", "paper-mojangapi", paperVersion) { + compileOnly(libs.paper.mojangapi) { attributes { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } diff --git a/bootstrap/sponge/build.gradle.kts b/bootstrap/sponge/build.gradle.kts index 2850b2c5e69..8765c439072 100644 --- a/bootstrap/sponge/build.gradle.kts +++ b/bootstrap/sponge/build.gradle.kts @@ -1,5 +1,3 @@ -val spongeVersion = "7.1.0" - dependencies { api(projects.core) } @@ -18,7 +16,7 @@ exclude("org.slf4j:*") exclude("org.ow2.asm:*") // These dependencies are already present on the platform -provided("org.spongepowered", "spongeapi", spongeVersion) +provided(libs.sponge.api) application { mainClass.set("org.geysermc.geyser.platform.sponge.GeyserSpongeMain") diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index 3c1a10b0973..9c2194445e5 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -6,20 +6,16 @@ val jlineVersion = "3.21.0" dependencies { api(projects.core) - implementation("net.minecrell", "terminalconsoleappender", terminalConsoleVersion) { + implementation(libs.terminalconsoleappender) { exclude("org.apache.logging.log4j", "log4j-core") exclude("org.jline", "jline-reader") exclude("org.jline", "jline-terminal") exclude("org.jline", "jline-terminal-jna") } - implementation("org.jline", "jline-terminal", jlineVersion) - implementation("org.jline", "jline-terminal-jna", jlineVersion) - implementation("org.jline", "jline-reader", jlineVersion) + implementation(libs.bundles.jline) - implementation("org.apache.logging.log4j", "log4j-api", Versions.log4jVersion) - implementation("org.apache.logging.log4j", "log4j-core", Versions.log4jVersion) - implementation("org.apache.logging.log4j", "log4j-slf4j18-impl", Versions.log4jVersion) + implementation(libs.bundles.log4j) } application { diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index ab2f85b857f..8908b2afd4a 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -1,7 +1,5 @@ -val velocityVersion = "3.0.0" - dependencies { - annotationProcessor("com.velocitypowered", "velocity-api", velocityVersion) + annotationProcessor(libs.velocity.api) api(projects.core) } @@ -34,7 +32,7 @@ exclude("net.kyori:adventure-text-serializer-legacy:*") exclude("net.kyori:adventure-nbt:*") // These dependencies are already present on the platform -provided("com.velocitypowered", "velocity-api", velocityVersion) +provided(libs.velocity.api) application { mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain") diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt deleted file mode 100644 index d492022baf6..00000000000 --- a/build-logic/src/main/kotlin/Versions.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -object Versions { - const val jacksonVersion = "2.13.2" - const val fastutilVersion = "8.5.2" - const val nettyVersion = "4.1.80.Final" - const val guavaVersion = "29.0-jre" - const val gsonVersion = "2.3.1" // Provided by Spigot 1.8.8 - const val websocketVersion = "1.5.1" - const val protocolVersion = "fed46166" - const val raknetVersion = "1.6.28-20220125.214016-6" - const val mcauthlibVersion = "d9d773e" - const val mcprotocollibversion = "9f78bd5" - const val packetlibVersion = "3.0" - const val adventureVersion = "4.12.0-20220629.025215-9" - const val adventurePlatformVersion = "4.1.2" - const val junitVersion = "4.13.1" - const val checkerQualVersion = "3.19.0" - const val cumulusVersion = "1.1.1" - const val eventsVersion = "1.0-SNAPSHOT" - const val log4jVersion = "2.17.1" -} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 43cdafdccc3..0c01913d248 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -25,7 +25,9 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.named fun Project.isSnapshot(): Boolean = @@ -64,5 +66,11 @@ fun Project.provided(pattern: String, name: String, version: String, excludedOn: fun Project.provided(dependency: ProjectDependency) = provided(dependency.group!!, dependency.name, dependency.version!!) +fun Project.provided(dependency: MinimalExternalModuleDependency) = + provided(dependency.module.group, dependency.module.name, dependency.versionConstraint.requiredVersion) + +fun Project.provided(provider: Provider) = + provided(provider.get()) + private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = if (excludedOn and bit > 0) section else "" \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 2ea5d88a479..ac652764bd1 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - compileOnly("org.checkerframework", "checker-qual", Versions.checkerQualVersion) + compileOnly("org.checkerframework", "checker-qual", "3.19.0") } tasks { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 6c14141054b..44f97b9faa4 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,4 +1,4 @@ dependencies { - api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion) - api("com.google.code.gson", "gson", Versions.gsonVersion) + api(libs.cumulus) + api(libs.gson) } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1bf227e58d1..1ab99990aa7 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -11,59 +11,50 @@ dependencies { api(projects.common) // Jackson JSON and YAML serialization - api("com.fasterxml.jackson.core", "jackson-annotations", Versions.jacksonVersion) - api("com.fasterxml.jackson.core", "jackson-databind", Versions.jacksonVersion + ".1") // Extra .1 as databind is a slightly different version - api("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", Versions.jacksonVersion) - api("com.google.guava", "guava", Versions.guavaVersion) + api(libs.bundles.jackson) + api(libs.guava) // Fastutil Maps - implementation("com.nukkitx.fastutil", "fastutil-int-int-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-long-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-byte-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-int-boolean-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-int-maps", Versions.fastutilVersion) - implementation("com.nukkitx.fastutil", "fastutil-object-object-maps", Versions.fastutilVersion) + implementation(libs.bundles.fastutil) // Network libraries - implementation("org.java-websocket", "Java-WebSocket", Versions.websocketVersion) + implementation(libs.websocket) - api("com.github.CloudburstMC.Protocol", "bedrock-v554", Versions.protocolVersion) { + api(libs.protocol) { exclude("com.nukkitx.network", "raknet") } - api("com.github.GeyserMC", "MCAuthLib", Versions.mcauthlibVersion) - api("com.github.GeyserMC", "MCProtocolLib", Versions.mcprotocollibversion) { + api(libs.mcauthlib) + api(libs.mcprotocollib) { exclude("io.netty", "netty-all") exclude("com.github.GeyserMC", "packetlib") exclude("com.github.GeyserMC", "mcauthlib") } - api("com.github.steveice10", "packetlib", Versions.packetlibVersion) { + api(libs.packetlib) { exclude("io.netty", "netty-all") } - implementation("com.nukkitx.network", "raknet", Versions.raknetVersion) { + implementation(libs.raknet) { exclude("io.netty", "*"); } - implementation("io.netty", "netty-resolver-dns", Versions.nettyVersion) - implementation("io.netty", "netty-resolver-dns-native-macos", Versions.nettyVersion, null, "osx-x86_64") - implementation("io.netty", "netty-codec-haproxy", Versions.nettyVersion) + implementation(libs.netty.resolver.dns) + implementation(libs.netty.resolver.dns.native.macos) { artifact { classifier = "osx-x86_64" } } + implementation(libs.netty.codec.haproxy) // Network dependencies we are updating ourselves - api("io.netty", "netty-handler", Versions.nettyVersion) + api(libs.netty.handler) - implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-x86_64") - implementation("io.netty", "netty-transport-native-epoll", Versions.nettyVersion, null, "linux-aarch_64") - implementation("io.netty", "netty-transport-native-kqueue", Versions.nettyVersion, null, "osx-x86_64") + implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } } + implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } } + implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } } // Adventure text serialization - implementation("net.kyori", "adventure-text-serializer-gson", Versions.adventureVersion) // Remove when we remove our Adventure bump - implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureVersion) - implementation("net.kyori", "adventure-text-serializer-plain", Versions.adventureVersion) + implementation(libs.bundles.adventure) // Test - testImplementation("junit", "junit", Versions.junitVersion) + testImplementation(libs.junit) // Annotation Processors compileOnly(projects.ap) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000000..d5d5fab30a2 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,89 @@ +[versions] +jackson = "2.13.4" +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 = "fed46166" +raknet = "1.6.28-20220125.214016-6" +mcauthlib = "d9d773e" +mcprotocollib = "9f78bd5" +packetlib = "3.0" +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.5-SNAPSHOT" +commodore = "2.2" +bungeecord = "a7c6ede" +velocity = "3.0.0" +sponge = "7.1.0" + +[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" } + +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.GeyserMC", name = "MCProtocolLib", version.ref = "mcprotocollib" } +packetlib = { group = "com.github.steveice10", name = "packetlib", version.ref = "packetlib" } +protocol = { group = "com.github.CloudburstMC.Protocol", name = "bedrock-v554", 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" } +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" ] \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87909a..8049c684f04 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a84362af2cd6acc5bb40e39c1263471512ab2f51 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:54:46 -0400 Subject: [PATCH 265/358] Bump Protocol to fix cartographer tables --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5d5fab30a2..5f9d72d574b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ netty = "4.1.80.Final" guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" -protocol = "fed46166" +protocol = "2.9.12-20220926.095446-6" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "9f78bd5" @@ -74,7 +74,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" } mcprotocollib = { group = "com.github.GeyserMC", name = "MCProtocolLib", version.ref = "mcprotocollib" } packetlib = { group = "com.github.steveice10", name = "packetlib", version.ref = "packetlib" } -protocol = { group = "com.github.CloudburstMC.Protocol", name = "bedrock-v554", version.ref = "protocol" } +protocol = { group = "com.nukkitx.protocol", name = "bedrock-v554", 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" } From 1b6cfad5ad7e6da03288890ad1f1e8fe91e6d091 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:24:50 -0400 Subject: [PATCH 266/358] Fix ghost blocks when insta-mining on 1.19+ Fixes #3113 --- .../command/defaults/OffhandCommand.java | 2 +- .../populator/BlockRegistryPopulator.java | 2 +- .../geyser/registry/type/BlockMapping.java | 2 +- .../geyser/session/GeyserSession.java | 2 +- .../geyser/session/cache/WorldCache.java | 61 ++++++------------- ...BedrockInventoryTransactionTranslator.java | 13 ++-- .../player/BedrockActionTranslator.java | 7 ++- .../entity/player/BedrockEmoteTranslator.java | 2 +- .../java/level/JavaBlockUpdateTranslator.java | 3 +- .../JavaSectionBlocksUpdateTranslator.java | 3 +- .../org/geysermc/geyser/util/ChunkUtils.java | 1 - 11 files changed, 36 insertions(+), 62 deletions(-) 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 bba2e8d2173..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 @@ -47,7 +47,7 @@ public void execute(GeyserSession session, GeyserCommandSource sender, String[] } 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/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index ce8f05ed8a1..afc79082a3b 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 @@ -234,7 +234,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/type/BlockMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java index cd91f64d1c0..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 @@ -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/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 2fd1edd4480..67aedec153d 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1311,7 +1311,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; 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 30f8c3ba85b..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,17 +28,17 @@ 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.registry.BlockRegistries; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.ChunkUtils; import java.util.Iterator; -import java.util.Map; public final class WorldCache { private final GeyserSession session; @@ -59,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; @@ -135,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) { @@ -166,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/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 49eb30973fb..436f26cb97a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -127,7 +127,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, Vector3i.ZERO, Direction.DOWN, - session.getWorldCache().nextPredictionSequence() + 0 ); session.sendDownstreamPacket(dropPacket); @@ -408,13 +408,10 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } int sequence = session.getWorldCache().nextPredictionSequence(); - if (blockState != -1) { - session.getWorldCache().addServerCorrectBlockState(packet.getBlockPosition(), blockState); - } else { + session.getWorldCache().markPositionInSequence(packet.getBlockPosition()); + // -1 means we don't know what block they're breaking + if (blockState == -1) { blockState = BlockStateValues.JAVA_AIR_ID; - // Client will desync here anyway - session.getWorldCache().addServerCorrectBlockState(packet.getBlockPosition(), - session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition())); } LevelEventPacket blockBreakPacket = new LevelEventPacket(); @@ -442,7 +439,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) if (packet.getActionType() == 0) { // Followed to the Minecraft Protocol specification outlined at wiki.vg ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, Vector3i.ZERO, - Direction.DOWN, session.getWorldCache().nextPredictionSequence()); + Direction.DOWN, 0); session.sendDownstreamPacket(releaseItemPacket); } break; 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..72f064a55d4 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; @@ -128,7 +129,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 +172,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 +203,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/java/level/JavaBlockUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockUpdateTranslator.java index 564a807b49c..cd965e128e3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockUpdateTranslator.java @@ -35,7 +35,6 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.sound.BlockSoundInteractionTranslator; -import org.geysermc.geyser.util.ChunkUtils; @Translator(packet = ClientboundBlockUpdatePacket.class) public class JavaBlockUpdateTranslator 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/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 Date: Thu, 29 Sep 2022 23:52:51 +0700 Subject: [PATCH 267/358] Remove sonarcloud workflow (#3314) --- .github/workflows/sonarcloud.yml | 36 -------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/sonarcloud.yml 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 From c3c2e18f5074d6c57e443d76e2e1fa4323e527dd Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 29 Sep 2022 20:57:45 -0400 Subject: [PATCH 268/358] Fix Geyser Standalone archive on github workflows (#3325) --- .github/workflows/pullrequest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9d925c4dc43..cb55c2675a4 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -24,7 +24,7 @@ jobs: if: success() with: name: Geyser Standalone - path: bootstrap/standalone/build/libs/Geyser.jar + path: bootstrap/standalone/build/libs/Geyser-Standalone.jar - name: Archive artifacts (Geyser Spigot) uses: actions/upload-artifact@v2 if: success() From 47d14e12eb7696cf42202240854dde9f07a95c9d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:58:09 -0400 Subject: [PATCH 269/358] Geyser now requires 1.13.2+ on Spigot --- .../platform/spigot/GeyserSpigotPlugin.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) 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 e6bc8d984e1..65de068afdc 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,6 +32,7 @@ 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; @@ -99,18 +100,22 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onLoad() { + GeyserLocale.init(this); + try { - // AvailableCommandsSerializer_v291 complains otherwise + // AvailableCommandsSerializer_v291 complains otherwise - affects at least 1.8 ByteBuf.class.getMethod("writeShortLE", int.class); - } catch (NoSuchMethodException e) { + // 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.12.2")); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); getLogger().severe(""); getLogger().severe("*********************************************"); - - Bukkit.getPluginManager().disablePlugin(this); return; } @@ -124,14 +129,10 @@ public void onLoad() { getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); getLogger().severe(""); getLogger().severe("*********************************************"); - - Bukkit.getPluginManager().disablePlugin(this); return; } } - GeyserLocale.init(this); - // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed try { if (!getDataFolder().exists()) { @@ -157,6 +158,12 @@ public void onLoad() { @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)); From c31bd456f6bd93c0da3cc4db1119bb94bf493d1f Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:12:27 +0200 Subject: [PATCH 270/358] Fix entity motion (arrows now rotate correctly) (#3307) --- .../geysermc/geyser/entity/type/Entity.java | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) 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 1db2e611743..c4046bcf346 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 @@ -216,19 +216,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); } From cb864b3c98a9df66fef4f9319a5257eaa1c44616 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 30 Sep 2022 21:21:37 -0400 Subject: [PATCH 271/358] Fix NPE with furnace minecart with NBT data --- .../geyser/registry/populator/ItemRegistryPopulator.java | 1 + 1 file changed, 1 insertion(+) 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 60a16245c02..6c01ee9d264 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 @@ -567,6 +567,7 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) + .customItemOptions(Object2IntMaps.emptyMap()) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() From 7653a626afe8ad6320299ef5a39437ad271846fd Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 2 Oct 2022 16:43:14 -0400 Subject: [PATCH 272/358] Update Sponge to API 8 (#2611) --- .../bungeecord/GeyserBungeeDumpInfo.java | 6 +- .../command/GeyserBungeeCommandExecutor.java | 3 + .../platform/spigot/GeyserSpigotDumpInfo.java | 5 +- .../command/GeyserSpigotCommandExecutor.java | 3 + bootstrap/sponge/build.gradle.kts | 18 +- .../platform/sponge/GeyserSpongeDumpInfo.java | 35 ++-- .../platform/sponge/GeyserSpongeLogger.java | 2 +- .../sponge/GeyserSpongePingPassthrough.java | 47 +++-- .../platform/sponge/GeyserSpongePlugin.java | 177 +++++++++++------- .../command/GeyserSpongeCommandExecutor.java | 65 ++++--- .../command/GeyserSpongeCommandManager.java | 30 ++- .../sponge/command/SpongeCommandSource.java | 17 +- .../resources/META-INF/sponge_plugins.json | 30 +++ .../sponge/src/main/resources/pack.mcmeta | 6 + .../velocity/GeyserVelocityDumpInfo.java | 5 +- .../GeyserVelocityCommandExecutor.java | 3 + .../kotlin/geyser.base-conventions.gradle.kts | 2 +- core/src/main/resources/languages | 2 +- core/src/main/resources/mappings | 2 +- gradle/libs.versions.toml | 2 +- 20 files changed, 294 insertions(+), 166 deletions(-) create mode 100644 bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json create mode 100644 bootstrap/sponge/src/main/resources/pack.mcmeta 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 938e2fc3ad1..2278d99d9b8 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 @@ -52,10 +52,8 @@ 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 { + String hostname = listener.getHost().getHostString(); + if (!AsteriskSerializer.showSensitive && !(hostname.equals("") || hostname.equals("0.0.0.0"))) { hostname = "***"; } this.listeners.add(new ListenerInfo(hostname, listener.getHost().getPort())); 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 6575f047c6c..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 @@ -69,6 +69,9 @@ public void execute(CommandSender sender, String[] args) { return; } command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); + commandSender.sendMessage(ChatColor.RED + message); } } else { this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]); diff --git a/bootstrap/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 8055a375f32..92c2fe16b4e 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 @@ -51,8 +51,9 @@ 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(); + String ip = Bukkit.getIp(); + if (AsteriskSerializer.showSensitive || (ip.equals("") || ip.equals("0.0.0.0"))) { + this.serverIP = ip; } else { this.serverIP = "***"; } 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 52779db23b3..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 @@ -66,6 +66,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); + commandSender.sendMessage(ChatColor.RED + message); } } else { getCommand("help").execute(session, commandSender, new String[0]); diff --git a/bootstrap/sponge/build.gradle.kts b/bootstrap/sponge/build.gradle.kts index 8765c439072..3d89e8649a7 100644 --- a/bootstrap/sponge/build.gradle.kts +++ b/bootstrap/sponge/build.gradle.kts @@ -7,13 +7,8 @@ platformRelocate("io.netty") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("com.google.common") platformRelocate("com.google.guava") -platformRelocate("net.kyori") - -// Exclude these dependencies -exclude("com.google.code.gson:*") -exclude("org.yaml:*") -exclude("org.slf4j:*") -exclude("org.ow2.asm:*") +platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") +platformRelocate("net.kyori.adventure.nbt") // These dependencies are already present on the platform provided(libs.sponge.api) @@ -30,5 +25,14 @@ tasks.withType { exclude(dependency("org.yaml:.*")) exclude(dependency("org.slf4j:.*")) exclude(dependency("org.ow2.asm:.*")) + + // Exclude all Kyori dependencies except the legacy NBT serializer and NBT + exclude(dependency("net.kyori:adventure-api:.*")) + exclude(dependency("net.kyori:examination-api:.*")) + exclude(dependency("net.kyori:examination-string:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-gson:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*")) + exclude(dependency("net.kyori:adventure-text-serializer-plain:.*")) + exclude(dependency("net.kyori:adventure-key:.*")) } } \ No newline at end of file diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java index e65684af2f9..fc1bff3efe3 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,12 +27,18 @@ 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 { @@ -44,18 +50,25 @@ public class GeyserSpongeDumpInfo extends BootstrapDumpInfo { 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(); + String hostString = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown"); + if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0") || hostString.equals("unknown"))) { + this.serverIP = hostString; + } else { + this.serverIP = "***"; + } + 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 a661061e2e1..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 @@ -28,11 +28,12 @@ import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.EventContext; import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.cause.Cause; -import org.spongepowered.api.event.cause.EventContext; import org.spongepowered.api.event.server.ClientPingServerEvent; import org.spongepowered.api.network.status.StatusClient; import org.spongepowered.api.profile.GameProfile; @@ -43,7 +44,7 @@ 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(), + 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 42040f6abf1..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,6 +26,7 @@ package org.geysermc.geyser.platform.sponge; import com.google.inject.Inject; +import org.apache.logging.log4j.Logger; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -36,18 +37,23 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; -import org.slf4j.Logger; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; +import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.game.state.GameStartedServerEvent; -import org.spongepowered.api.event.game.state.GameStoppedEvent; -import org.spongepowered.api.plugin.Plugin; - +import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; +import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; @@ -55,54 +61,134 @@ 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; - public void onLoad() { + /** + * Only to be used for reloading + */ + @Override + public void onEnable() { + enabled = true; + onConstruction(null); + // new commands cannot be registered, and geyser's command manager does not need be reloaded + onStartedEngine(null); + } + + @Override + public void onDisable() { + enabled = false; + if (geyser != null) { + geyser.shutdown(); + geyser = null; + } + } + + /** + * Construct the configuration, logger, and command manager. command manager will only be filled with commands once + * the connector is started, but it allows us to register events in sponge. + * + * @param event Not used. + */ + @Listener + public void onConstruction(@Nullable ConstructPluginEvent event) { GeyserLocale.init(this); - if (!configDir.exists()) + File configDir = configPath.toFile(); + if (!configDir.exists()) { configDir.mkdirs(); + } File configFile; try { configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); } catch (IOException ex) { logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); + onDisable(); return; } - try { - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); - } catch (IOException ex) { - logger.warn(GeyserLocale.getLocaleStringLog("geyser.config.failed")); - ex.printStackTrace(); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); + + this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); + + this.geyserCommandManager = new GeyserSpongeCommandManager(geyser); + this.geyserCommandManager.init(); + } + + /** + * Construct the {@link GeyserSpongeCommandManager} and register the commands + * + * @param event required to register the commands + */ + @Listener + public void onRegisterCommands(@Nonnull RegisterCommandEvent event) { + if (enabled) { + event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); + + for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { + Map commands = entry.getValue(); + if (commands.isEmpty()) { + continue; + } + + event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id()); + } + } + } + + /** + * Configure the config properly if remote address is auto. Start connector and ping passthrough, and register subcommands of /geyser + * + * @param event not required + */ + @Listener + public void onStartedEngine(@Nullable StartedEngineEvent event) { + if (!enabled) { return; } - if (Sponge.getServer().getBoundAddress().isPresent()) { - InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); + if (Sponge.server().boundAddress().isPresent()) { + InetSocketAddress javaAddr = Sponge.server().boundAddress().get(); - // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); + // Don't change the ip if its listening on all interfaces + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { + this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } geyserConfig.getRemote().setPort(javaAddr.getPort()); } } @@ -111,14 +197,6 @@ public void onLoad() { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().port()); } - this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - - this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); - } - - @Override - public void onEnable() { GeyserImpl.start(); if (geyserConfig.isLegacyPingPassthrough()) { @@ -126,25 +204,11 @@ public void onEnable() { } else { this.geyserSpongePingPassthrough = new GeyserSpongePingPassthrough(); } - - this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); - this.geyserCommandManager.init(); - - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); - - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id()); - } } - @Override - public void onDisable() { - geyser.shutdown(); + @Listener + public void onEngineStopping(StoppingEngineEvent event) { + onDisable(); } @Override @@ -159,7 +223,7 @@ public GeyserSpongeLogger getGeyserLogger() { @Override public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + return geyserCommandManager; } @Override @@ -169,22 +233,7 @@ public IGeyserPingPassthrough getGeyserPingPassthrough() { @Override public Path getConfigFolder() { - return configDir.toPath(); - } - - @Listener - public void onServerStarting() { - onLoad(); - } - - @Listener - public void onServerStart(GameStartedServerEvent event) { - onEnable(); - } - - @Listener - public void onServerStop(GameStoppedEvent event) { - onDisable(); + return configPath; } @Override @@ -194,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 3598ea8c2fc..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,81 +25,88 @@ package org.geysermc.geyser.platform.sponge.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; -import org.spongepowered.api.command.CommandCallable; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.text.Text; -import org.spongepowered.api.world.Location; -import org.spongepowered.api.world.World; +import org.spongepowered.api.command.parameter.ArgumentReader; -import javax.annotation.Nullable; import java.util.*; +import java.util.stream.Collectors; -public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements org.spongepowered.api.command.Command.Raw { public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map commands) { super(geyser, commands); } @Override - public CommandResult process(CommandSource source, String arguments) { - GeyserCommandSource commandSender = new SpongeCommandSource(source); - GeyserSession session = getGeyserSession(commandSender); + public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { + GeyserCommandSource commandSource = new SpongeCommandSource(cause); + GeyserSession session = getGeyserSession(commandSource); - String[] args = arguments.split(" "); - if (args.length > 0) { + String[] args = arguments.input().split(" "); + // This split operation results in an array of length 1, containing a zero length string, if the input string is empty + if (args.length > 0 && !args[0].isEmpty()) { GeyserCommand command = getCommand(args[0]); if (command != null) { - if (!source.hasPermission(command.permission())) { - // Not ideal to use log here but we dont get a session - source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + if (!cause.hasPermission(command.permission())) { + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")).color(NamedTextColor.RED)); return CommandResult.success(); } if (command.isBedrockOnly() && session == null) { - source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"))); + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")).color(NamedTextColor.RED)); return CommandResult.success(); } - getCommand(args[0]).execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + command.execute(session, commandSource, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + } else { + cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.not_found")).color(NamedTextColor.RED)); } } else { - getCommand("help").execute(session, commandSender, new String[0]); + getCommand("help").execute(session, commandSource, new String[0]); } return CommandResult.success(); } @Override - public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { - if (arguments.split(" ").length == 1) { - return tabComplete(new SpongeCommandSource(source)); + public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { + if (arguments.input().split(" ").length == 1) { + return tabComplete(new SpongeCommandSource(cause)).stream().map(CommandCompletion::of).collect(Collectors.toList()); } return Collections.emptyList(); } @Override - public boolean testPermission(CommandSource source) { + public boolean canExecute(CommandCause cause) { return true; } @Override - public Optional getShortDescription(CommandSource source) { - return Optional.of(Text.of("The main command for Geyser.")); + public Optional shortDescription(CommandCause cause) { + return Optional.of(Component.text("The main command for Geyser.")); } @Override - public Optional getHelp(CommandSource source) { - return Optional.of(Text.of("/geyser help")); + public Optional extendedDescription(CommandCause cause) { + return shortDescription(cause); } @Override - public Text getUsage(CommandSource source) { - return Text.of("/geyser help"); + public Optional help(@NotNull CommandCause cause) { + return Optional.of(Component.text("/geyser help")); + } + + @Override + public Component usage(CommandCause cause) { + return Component.text("/geyser help"); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java index 8e981f72a33..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.GeyserCommandManager; +import org.geysermc.geyser.translator.text.MessageTranslator; import org.spongepowered.api.Sponge; -import org.spongepowered.api.command.CommandMapping; -import org.spongepowered.api.text.Text; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.manager.CommandMapping; + +import java.util.Optional; public class GeyserSpongeCommandManager extends GeyserCommandManager { - private final org.spongepowered.api.command.CommandManager handle; - public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserImpl geyser) { + public GeyserSpongeCommandManager(GeyserImpl geyser) { super(geyser); - - this.handle = handle; } @Override public String description(String command) { - return handle.get(command).map(CommandMapping::getCallable) - .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) - .orElse(Text.EMPTY).toPlain(); + if (!Sponge.isServerAvailable()) { + return ""; + } + + // Note: The command manager may be replaced at any point during the game lifecycle + return Sponge.server().commandManager().commandMapping(command) + .map(this::description) + .map(Optional::get) + .map(MessageTranslator::convertMessage) + .orElse(""); + } + + public Optional description(CommandMapping mapping) { + return mapping.registrar().shortDescription(CommandCause.create(), mapping); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java index 12fdcb989e7..31dccc1fbcc 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java @@ -26,29 +26,30 @@ package org.geysermc.geyser.platform.sponge.command; import lombok.AllArgsConstructor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; -import org.spongepowered.api.command.CommandSource; -import org.spongepowered.api.command.source.ConsoleSource; -import org.spongepowered.api.text.Text; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; @AllArgsConstructor public class SpongeCommandSource implements GeyserCommandSource { - private CommandSource handle; + private final CommandCause handle; @Override public String name() { - return handle.getName(); + return handle.friendlyIdentifier().orElse(handle.identifier()); } @Override - public void sendMessage(String message) { - handle.sendMessage(Text.of(message)); + public void sendMessage(@NonNull String message) { + handle.audience().sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); } @Override public boolean isConsole() { - return handle instanceof ConsoleSource; + return !(handle.cause().root() instanceof ServerPlayer); } @Override diff --git a/bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json b/bootstrap/sponge/src/main/resources/META-INF/sponge_plugins.json new file mode 100644 index 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/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 9f429cc8326..b2765d3b22b 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 @@ -51,8 +51,9 @@ 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(); + String hostString = proxy.getBoundAddress().getHostString(); + if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0"))) { + this.serverIP = hostString; } else { this.serverIP = "***"; } 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 c77a3daef92..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 @@ -63,6 +63,9 @@ public void execute(Invocation invocation) { return; } command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); + } else { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale()); + sender.sendMessage(ChatColor.RED + message); } } else { getCommand("help").execute(session, sender, new String[0]); diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index ac652764bd1..9414655bc5b 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -9,7 +9,7 @@ dependencies { tasks { processResources { - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) { + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json")) { expand( "id" to "Geyser", "name" to "Geyser", diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 51d6f5ba7d8..a9cf5999af6 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 51d6f5ba7d85bfda318879dad34481d9ef4d488d +Subproject commit a9cf5999af605902b18dd5c77d3562481f8d7f3d diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 2c68dab9d75..f1c9c2fbba0 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 2c68dab9d751f78b2f5b0298da5e338ad6bc07ca +Subproject commit f1c9c2fbba0e102dc4f8c96dd9485f7ec9768174 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f9d72d574b..28b687f9ba7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ adapters = "1.5-SNAPSHOT" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.0.0" -sponge = "7.1.0" +sponge = "8.0.0" [libraries] jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" } From fc25592df60030667fae60e501559fc45b2b21b9 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 2 Oct 2022 18:25:49 -0400 Subject: [PATCH 273/358] Changed the id in resource processing to be lowercase (#3329) --- .../src/main/kotlin/geyser.base-conventions.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 9414655bc5b..bd50c6ea0c5 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -11,7 +11,7 @@ tasks { processResources { filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json")) { expand( - "id" to "Geyser", + "id" to "geyser", "name" to "Geyser", "version" to project.version, "description" to project.description, @@ -30,4 +30,4 @@ java { targetCompatibility = JavaVersion.VERSION_16 withSourcesJar() -} \ No newline at end of file +} From 82249763936f403ae90a06ef8c66297ceabd3dd5 Mon Sep 17 00:00:00 2001 From: Luke Chambers Date: Mon, 3 Oct 2022 10:37:22 -0400 Subject: [PATCH 274/358] Publish entire java component and common (#3331) --- .../src/main/kotlin/geyser.publish-conventions.gradle.kts | 5 ++--- common/build.gradle.kts | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts index 68ab5933733..7525f97fab0 100644 --- a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts @@ -11,8 +11,7 @@ publishing { artifactId = project.name version = project.version as String - artifact(tasks["shadowJar"]) - artifact(tasks["sourcesJar"]) + from(components["java"]) } } } @@ -31,4 +30,4 @@ artifactory { setPublishIvy(false) } } -} \ No newline at end of file +} diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 44f97b9faa4..db3fe3a7742 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,4 +1,8 @@ +plugins { + id("geyser.publish-conventions") +} + dependencies { api(libs.cumulus) api(libs.gson) -} \ No newline at end of file +} From 7f05ab9d223a53ea45697458725296ad5dbb4bda Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:15:30 -0400 Subject: [PATCH 275/358] Fix reloading on Spigot Fixes #3319 --- .../platform/spigot/GeyserSpigotPlugin.java | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) 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 65de068afdc..d7a4da7c87e 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 @@ -196,36 +196,43 @@ public void onEnable() { geyserConfig.loadFloodgate(this); - // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes - Bukkit.getPluginManager().registerEvents(new Listener() { + 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(); + @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); + } } - }, 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(); } } From 07c7b2f7f8166fd1adde6551c2e1ab7cbc0c2501 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:33:24 -0400 Subject: [PATCH 276/358] Clean up on legacy code that will no longer trigger --- .../platform/spigot/GeyserSpigotPlugin.java | 30 +---- .../spigot/command/SpigotCommandSource.java | 61 +-------- .../spigot/world/GeyserPistonListener.java | 7 +- .../GeyserSpigot1_12NativeWorldManager.java | 61 --------- .../manager/GeyserSpigot1_12WorldManager.java | 125 ------------------ .../GeyserSpigotFallbackWorldManager.java | 56 -------- .../manager/GeyserSpigotWorldManager.java | 13 +- 7 files changed, 13 insertions(+), 340 deletions(-) delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java 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 d7a4da7c87e..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 @@ -60,7 +60,6 @@ import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; -import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.*; @@ -269,14 +268,6 @@ private void postStartup() { } } } - // Used to determine if Block.getBlockData() is present. - boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); - if (isLegacy) - geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - - boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); - // Set if we need to use a different method for getting a player's locale - SpigotCommandSource.setUseLegacyLocaleMethod(isPre1_12); // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib // To do their job injecting, then connect into *that* @@ -289,13 +280,7 @@ private void postStartup() { 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); @@ -312,17 +297,8 @@ private void postStartup() { } if (this.geyserWorldManager == null) { // No NMS adapter - if (isLegacy && isViaVersion) { - // Use ViaVersion for converting pre-1.13 block states - this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this); - } else if (isLegacy) { - // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air - this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); - } else { - // Post-1.13 - this.geyserWorldManager = new GeyserSpigotWorldManager(this); - } - geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + this.geyserWorldManager = new GeyserSpigotWorldManager(this); + geyserLogger.debug("Using default world manager."); } PluginCommand geyserCommand = this.getCommand("geyser"); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index 8deddd8e65e..95fba707fe1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -29,31 +29,17 @@ import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - public class SpigotCommandSource implements GeyserCommandSource { - - /** - * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. - * 1.12 or greater should not use the legacy method. - */ - private static boolean USE_LEGACY_METHOD = false; - private static Method LOCALE_METHOD; - private final org.bukkit.command.CommandSender handle; - private final String locale; public SpigotCommandSource(org.bukkit.command.CommandSender handle) { this.handle = handle; - this.locale = getSpigotLocale(); // Ensure even Java players' languages are loaded - GeyserLocale.loadGeyserLocale(locale); + GeyserLocale.loadGeyserLocale(locale()); } @Override @@ -84,50 +70,15 @@ public boolean isConsole() { @Override public String locale() { - return locale; + if (this.handle instanceof Player player) { + return player.getLocale(); + } + + return GeyserLocale.getDefaultLocale(); } @Override public boolean hasPermission(String permission) { return handle.hasPermission(permission); } - - /** - * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get - * {@code player.spigot().getLocale()}. - * - * @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale - */ - public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) { - USE_LEGACY_METHOD = useLegacyMethod; - if (USE_LEGACY_METHOD) { - try { - //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it - LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); - } catch (NoSuchMethodException e) { - GeyserImpl.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); - } - } - } - - /** - * So we only have to do nasty reflection stuff once per command - * - * @return the locale of the Spigot player - */ - private String getSpigotLocale() { - if (handle instanceof Player player) { - if (USE_LEGACY_METHOD) { - try { - // sigh - // This was the only option on older Spigot instances and now it's gone - return (String) LOCALE_METHOD.invoke(player.spigot()); - } catch (IllegalAccessException | InvocationTargetException ignored) { - } - } else { - return player.getLocale(); - } - } - return GeyserLocale.getDefaultLocale(); - } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java index 8be1cb84efa..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 @@ -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/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 0ac8d6856cf..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.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; - -/** - * Used with ViaVersion and pre-1.13. - */ -public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { - private final SpigotWorldAdapter adapter; - - public GeyserSpigot1_12NativeWorldManager(Plugin plugin) { - super(plugin); - this.adapter = SpigotAdapters.getWorldAdapter(); - // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return BlockStateValues.JAVA_AIR_ID; - } - // Get block entity storage - BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); - int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); - return getLegacyBlock(storage, blockId, x, y, z); - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java deleted file mode 100644 index 2ca024abf20..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.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; - -import java.util.List; - -/** - * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. - * - * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. - */ -public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { - /** - * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. - * (Block IDs did not change between server versions until 1.13 and after) - */ - private final MappingData mappingData1_12to1_13; - - /** - * The list of all protocols from the client's version to 1.13. - */ - private final List protocolList; - - public GeyserSpigot1_12WorldManager(Plugin plugin) { - super(plugin); - this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getVersion()); - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return BlockStateValues.JAVA_AIR_ID; - } - if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) { - // Prevent nasty async errors if a player is loading in - return BlockStateValues.JAVA_AIR_ID; - } - - Block block = player.getWorld().getBlockAt(x, y, z); - return getBlockNetworkId(player, block, x, y, z); - } - - @Override - @SuppressWarnings("deprecation") - public int getBlockNetworkId(Player player, Block block, int x, int y, int z) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); - // Black magic that gets the old block state ID - int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - return getLegacyBlock(storage, oldBlockId, x, y, z); - } - - /** - * - * @param storage ViaVersion's block entity storage (used to fix block entity state differences) - * @param blockId the pre-1.13 block id - * @param x X coordinate of block - * @param y Y coordinate of block - * @param z Z coordinate of block - * @return the block state updated to the latest Minecraft version - */ - public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = mappingData1_12to1_13.getNewBlockId(blockId); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; - } - - @Override - public boolean isLegacy() { - return true; - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java deleted file mode 100644 index fa78a671ca2..00000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ /dev/null @@ -1,56 +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 org.bukkit.plugin.Plugin; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.session.GeyserSession; - -/** - * 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); - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - return BlockStateValues.JAVA_AIR_ID; - } - - @Override - public boolean hasOwnChunkCache() { - return false; - } - - @Override - public boolean isLegacy() { - return true; - } -} 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 093e287945b..7bb8f1666ee 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,7 +33,6 @@ 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; @@ -41,7 +40,6 @@ import org.geysermc.geyser.level.GameRule; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; @@ -54,11 +52,6 @@ * The base world manager to use when there is no supported NMS revision */ public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** - * The current client protocol version for ViaVersion usage. - */ - protected static final int CLIENT_PROTOCOL_VERSION = GameProtocol.getJavaProtocolVersion(); - 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); } @@ -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. * From 7edde43141c727302dcf4a1e357e7e294e2ced30 Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Mon, 3 Oct 2022 21:11:07 +0100 Subject: [PATCH 277/358] Initial fabric merger --- bootstrap/fabric/.gitignore | 240 ------------------ bootstrap/fabric/.idea/copyright/Geyser.xml | 6 - .../.idea/copyright/profiles_settings.xml | 7 - bootstrap/fabric/Jenkinsfile | 105 -------- bootstrap/fabric/LICENSE | 21 -- bootstrap/fabric/README.md | 10 - bootstrap/fabric/build.gradle | 120 --------- bootstrap/fabric/build.gradle.kts | 95 +++++++ bootstrap/fabric/gradle.properties | 14 - .../fabric/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - bootstrap/fabric/gradlew | 234 ----------------- bootstrap/fabric/gradlew.bat | 89 ------- bootstrap/fabric/settings.gradle | 10 - bootstrap/spigot/build.gradle.kts | 7 +- build-logic/build.gradle.kts | 11 +- gradle.properties | 20 +- gradle/libs.versions.toml | 1 + settings.gradle.kts | 5 +- 19 files changed, 130 insertions(+), 870 deletions(-) delete mode 100644 bootstrap/fabric/.gitignore delete mode 100644 bootstrap/fabric/.idea/copyright/Geyser.xml delete mode 100644 bootstrap/fabric/.idea/copyright/profiles_settings.xml delete mode 100644 bootstrap/fabric/Jenkinsfile delete mode 100644 bootstrap/fabric/LICENSE delete mode 100644 bootstrap/fabric/README.md delete mode 100644 bootstrap/fabric/build.gradle create mode 100644 bootstrap/fabric/build.gradle.kts delete mode 100644 bootstrap/fabric/gradle.properties delete mode 100644 bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar delete mode 100644 bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties delete mode 100755 bootstrap/fabric/gradlew delete mode 100644 bootstrap/fabric/gradlew.bat delete mode 100644 bootstrap/fabric/settings.gradle diff --git a/bootstrap/fabric/.gitignore b/bootstrap/fabric/.gitignore deleted file mode 100644 index 06b9cccc4f9..00000000000 --- a/bootstrap/fabric/.gitignore +++ /dev/null @@ -1,240 +0,0 @@ - -# Created by https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode -# Edit at https://www.gitignore.io/gitignore?templates=git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode - -### Eclipse ### -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.settings/ -.loadpath -.recommenders - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# PyDev specific (Python IDE for Eclipse) -*.pydevproject - -# CDT-specific (C/C++ Development Tooling) -.cproject - -# CDT- autotools -.autotools - -# Java annotation processor (APT) -.factorypath - -# PDT-specific (PHP Development Tools) -.buildpath - -# sbteclipse plugin -.target - -# Tern plugin -.tern-project - -# TeXlipse plugin -.texlipse - -# STS (Spring Tool Suite) -.springBeans - -# Code Recommenders -.recommenders/ - -# Annotation Processing -.apt_generated/ -.apt_generated_test/ - -# Scala IDE specific (Scala & Java development for Eclipse) -.cache-main -.scala_dependencies -.worksheet - -# Uncomment this line if you wish to ignore the project description file. -# Typically, this file would be tracked if it contains build/dependency configurations: -.project - -### Eclipse Patch ### -# Spring Boot Tooling -.sts4-cache/ - -### Git ### -# Created by git for backups. To disable backups in Git: -# $ git config --global mergetool.keepBackup false -*.orig - -# Created by git when using merge tools for conflicts -*.BACKUP.* -*.BASE.* -*.LOCAL.* -*.REMOTE.* -*_BACKUP_*.txt -*_BASE_*.txt -*_LOCAL_*.txt -*_REMOTE_*.txt - -### Java ### -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### JetBrains+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries -/.gradle/ - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### JetBrains+all Patch ### -# Ignores the whole .idea folder and all .iml files -# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 - -.idea/ - -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 - -*.iml -modules.xml -.idea/misc.xml -*.ipr - -# Sonarlint plugin -.idea/sonarlint - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar - -### NetBeans ### -**/nbproject/private/ -**/nbproject/Makefile-*.mk -**/nbproject/Package-*.bash -build/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ - -### VisualStudioCode ### -# Note: Manually edited to remove settings files -.vscode/* -# !.vscode/settings.json -# !.vscode/tasks.json -# !.vscode/launch.json -# !.vscode/extensions.json -# *.code-workspace - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history - -# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all,visualstudiocode - -### Geyser ### -run/ \ No newline at end of file diff --git a/bootstrap/fabric/.idea/copyright/Geyser.xml b/bootstrap/fabric/.idea/copyright/Geyser.xml deleted file mode 100644 index bdffbbbd662..00000000000 --- a/bootstrap/fabric/.idea/copyright/Geyser.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/bootstrap/fabric/.idea/copyright/profiles_settings.xml b/bootstrap/fabric/.idea/copyright/profiles_settings.xml deleted file mode 100644 index f2d5911c9eb..00000000000 --- a/bootstrap/fabric/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/bootstrap/fabric/Jenkinsfile b/bootstrap/fabric/Jenkinsfile deleted file mode 100644 index c3632a8ce0c..00000000000 --- a/bootstrap/fabric/Jenkinsfile +++ /dev/null @@ -1,105 +0,0 @@ -pipeline { - agent any - tools { - gradle 'Gradle 7' - jdk 'Java 17' - } - - parameters { - booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') - } - - options { - buildDiscarder(logRotator(artifactNumToKeepStr: '20')) - } - - stages { - stage ('Build') { - steps { - sh './gradlew clean build --refresh-dependencies' - } - post { - success { - archiveArtifacts artifacts: 'build/libs/*.jar', excludes: 'build/libs/Geyser-Fabric-*-sources*.jar,build/libs/Geyser-Fabric-*-all*.jar', fingerprint: true - } - } - } - - stage ('Deploy') { - when { - anyOf { - branch "java-1.18" - } - } - - steps { - rtGradleDeployer( - id: "GRADLE_DEPLOYER", - serverId: "opencollab-artifactory", - releaseRepo: "maven-releases", - snapshotRepo: "maven-snapshots" - ) - rtGradleResolver( - id: "GRADLE_RESOLVER", - serverId: "opencollab-artifactory" - ) - rtGradleRun ( - usesPlugin: false, - tool: 'Gradle 7', - rootDir: "", - buildFile: 'build.gradle', - tasks: 'build artifactoryPublish', - deployerId: "GRADLE_DEPLOYER", - resolverId: "GRADLE_RESOLVER" - ) - rtPublishBuildInfo( - serverId: "opencollab-artifactory" - ) - } - } - } - - post { - always { - script { - def changeLogSets = currentBuild.changeSets - def message = "**Changes:**" - - if (changeLogSets.size() == 0) { - message += "\n*No changes.*" - } else { - def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") - def count = 0; - def extra = 0; - for (int i = 0; i < changeLogSets.size(); i++) { - def entries = changeLogSets[i].items - for (int j = 0; j < entries.length; j++) { - if (count <= 10) { - def entry = entries[j] - def commitId = entry.commitId.substring(0, 6) - message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" - count++ - } else { - extra++; - } - } - } - - if (extra != 0) { - message += "\n - ${extra} more commits" - } - } - - env.changes = message - } - deleteDir() - script { - if(!params.SKIP_DISCORD) { - withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK - } - } - } - } - } -} diff --git a/bootstrap/fabric/LICENSE b/bootstrap/fabric/LICENSE deleted file mode 100644 index d8ee99620db..00000000000 --- a/bootstrap/fabric/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2020 GeyserMC - -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. diff --git a/bootstrap/fabric/README.md b/bootstrap/fabric/README.md deleted file mode 100644 index 883d032895d..00000000000 --- a/bootstrap/fabric/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Geyser-Fabric - -[![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/) - -Download: [![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/Geyser-Fabric/job/master/badge/icon)](https://ci.opencollab.dev//job/GeyserMC/job/Geyser-Fabric/job/master/) - -[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) - -Geyser-Fabric is Geyser specifically built for the Fabric modding platform. For more details, see [here](https://github.com/GeyserMC/Geyser/wiki/Geyser-Fabric). diff --git a/bootstrap/fabric/build.gradle b/bootstrap/fabric/build.gradle deleted file mode 100644 index 2ea9fd6868d..00000000000 --- a/bootstrap/fabric/build.gradle +++ /dev/null @@ -1,120 +0,0 @@ -plugins { - id 'fabric-loom' version '0.12-SNAPSHOT' - id 'maven-publish' - id 'com.github.johnrengelman.shadow' version '7.0.0' - id 'java' -} - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'java' - -sourceCompatibility = JavaVersion.VERSION_17 -targetCompatibility = JavaVersion.VERSION_17 - -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group - -dependencies { - //to change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - // 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 "org.geysermc.geyser:core:${project.mod_version}" - shadow("org.geysermc.geyser:core:${project.mod_version}") { - 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 { - url = uri('https://repo.opencollab.dev/maven-releases/') - } - - maven { - url = uri('https://repo.opencollab.dev/maven-snapshots/') - } - - maven { - url = uri('https://jitpack.io') - } - - maven { - url = uri('https://oss.sonatype.org/content/repositories/snapshots/') - } -} - -processResources { - inputs.property "version", project.version - - filesMatching("fabric.mod.json") { - expand "version": project.version - } -} - -// ensure that the encoding is set to UTF-8, no matter what the system default is -// this fixes some edge cases with special characters not displaying correctly -// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -// 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. -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" - from sourceSets.main.allSource -} - -shadowJar { - configurations = [project.configurations.shadow] - 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") -} - -jar { - from "LICENSE" -} - -remapJar { - dependsOn(shadowJar) - input = tasks.shadowJar.archiveFile - archiveName = "Geyser-Fabric.jar" -} - -// configure the maven publication -publishing { - publications { - mavenJava(MavenPublication) { - // add all the jars that should be included when publishing to maven - artifact(remapJar) { - builtBy remapJar - } - artifact(sourcesJar) { - builtBy remapSourcesJar - } - } - } - - // select the repositories you want to publish to - repositories { - // uncomment to publish to the local maven - mavenLocal() - } -} diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts new file mode 100644 index 00000000000..ef526aa0047 --- /dev/null +++ b/bootstrap/fabric/build.gradle.kts @@ -0,0 +1,95 @@ +plugins { + id("fabric-loom") version "0.12-SNAPSHOT" + id("maven-publish") + id("com.github.johnrengelman.shadow") + id("java") +} + +java { + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 +} + +//archivesBaseName = project.archives_base_name +//version = project.mod_version +//group = project.maven_group + +val minecraftVersion = project.property("minecraft_version") as String +val yarnVersion = project.property("yarn_mappings") as String +val loaderVersion = project.property("loader_version") as String +val fabricVersion = project.property("fabric_version") as String + +dependencies { + //to change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:$minecraftVersion") + mappings("net.fabricmc:yarn:$yarnVersion:v2") + modImplementation("net.fabricmc:fabric-loader:$loaderVersion") + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion") + + // 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/") +} + +tasks { + processResources { +// inputs.property "version", project.version +// +// filesMatching("fabric.mod.json") { +// expand "version": project.version +// } + } + + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + compileJava { + options.encoding = "UTF-8" + } + + // 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 { + 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") + } + + jar { + from("LICENSE") + } + + remapJar { + dependsOn(shadowJar) + inputs.file(shadowJar.get().archiveFile) + archiveBaseName.set("Geyser-Fabric") + archiveClassifier.set("") + archiveVersion.set("") + } +} \ No newline at end of file diff --git a/bootstrap/fabric/gradle.properties b/bootstrap/fabric/gradle.properties deleted file mode 100644 index 341d6bbe40d..00000000000 --- a/bootstrap/fabric/gradle.properties +++ /dev/null @@ -1,14 +0,0 @@ -# Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx2G -# Fabric Properties -# check these on https://modmuss50.me/fabric.html -minecraft_version=1.19.1 -yarn_mappings=1.19.1+build.1 -loader_version=0.14.8 -# Mod Properties -mod_version=2.1.0-SNAPSHOT -maven_group=org.geysermc.platform -archives_base_name=Geyser-Fabric -# Dependencies -# check this on https://modmuss50.me/fabric.html -fabric_version=0.58.5+1.19.1 diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties b/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e750102e092..00000000000 --- a/bootstrap/fabric/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/bootstrap/fabric/gradlew b/bootstrap/fabric/gradlew deleted file mode 100755 index 1b6c787337f..00000000000 --- a/bootstrap/fabric/gradlew +++ /dev/null @@ -1,234 +0,0 @@ -#!/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/bootstrap/fabric/gradlew.bat b/bootstrap/fabric/gradlew.bat deleted file mode 100644 index ac1b06f9382..00000000000 --- a/bootstrap/fabric/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@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/bootstrap/fabric/settings.gradle b/bootstrap/fabric/settings.gradle deleted file mode 100644 index 5b60df3d25f..00000000000 --- a/bootstrap/fabric/settings.gradle +++ /dev/null @@ -1,10 +0,0 @@ -pluginManagement { - repositories { - jcenter() - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } -} diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 31e68ed92ee..b5ef4e69e55 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -1,8 +1,3 @@ -val paperVersion = "1.19-R0.1-SNAPSHOT" -val viaVersion = "4.0.0" -val adaptersVersion = "1.5-SNAPSHOT" -val commodoreVersion = "2.2" - dependencies { api(projects.core) @@ -34,7 +29,7 @@ platformRelocate("me.lucko.commodore") platformRelocate("io.netty.channel.kqueue") // These dependencies are already present on the platform -provided("com.viaversion", "viaversion", viaVersion) +provided(libs.viaversion) application { mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 25cbfe9de90..6f4647c0fc0 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -11,7 +11,16 @@ repositories { dependencies { implementation("net.kyori", "indra-common", "2.0.6") implementation("org.jfrog.buildinfo", "build-info-extractor-gradle", "4.26.1") - implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.1") + implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.2") { + exclude("org.ow2.asm", "*") + } + + // 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.13.3") + + // Use a newer version of ObjectWeb ASM than the one provided by loom. + implementation("org.ow2.asm:asm-commons:9.4") } tasks.withType { diff --git a/gradle.properties b/gradle.properties index 8f6ac8e85b0..35cb2153783 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,24 @@ +# 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 +org.gradle.vfs.watch=false + +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.19.1 +yarn_mappings=1.19.1+build.1 +loader_version=0.14.8 +# Mod Properties +maven_group=org.geysermc.platform +archives_base_name=Geyser-Fabric +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.58.5+1.19.1 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 28b687f9ba7..bf4688d7f4e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -79,6 +79,7 @@ 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] diff --git a/settings.gradle.kts b/settings.gradle.kts index f6d13ad0d38..b61fefc31b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) +// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { // Floodgate, Cumulus etc. maven("https://repo.opencollab.dev/main") @@ -47,6 +47,7 @@ dependencyResolutionManagement { pluginManagement { repositories { gradlePluginPortal() + maven("https://maven.fabricmc.net/") } plugins { id("net.kyori.blossom") version "1.2.0" @@ -62,6 +63,7 @@ include(":ap") include(":api") include(":geyser-api") include(":bungeecord") +include(":fabric") include(":spigot") include(":sponge") include(":standalone") @@ -73,6 +75,7 @@ include(":core") project(":api").projectDir = file("api/base") project(":geyser-api").projectDir = file("api/geyser") project(":bungeecord").projectDir = file("bootstrap/bungeecord") +project(":fabric").projectDir = file("bootstrap/fabric") project(":spigot").projectDir = file("bootstrap/spigot") project(":sponge").projectDir = file("bootstrap/sponge") project(":standalone").projectDir = file("bootstrap/standalone") From 97ec5fec3357e317a118708cab5e866e2ab79bf4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 3 Oct 2022 18:27:42 -0400 Subject: [PATCH 278/358] Force-disable query if it would interfere with Geyser's startup. Fixes #3312 --- .../bungeecord/GeyserBungeePlugin.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 d6b2524732c..ab5531ba3a4 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 @@ -137,6 +137,24 @@ public void onEnable() { } } + // Force-disable query if enabled, or else Geyser won't enable + for (ListenerInfo info : getProxy().getConfig().getListeners()) { + if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) { + try { + Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled"); + queryField.setAccessible(true); + queryField.setBoolean(info, false); + geyserLogger.warning("We force-disabled query on port " + info.getQueryPort() + " in order for Geyser to boot up successfully. " + + "To remove this message, disable query in your proxy's config."); + } catch (NoSuchFieldException | IllegalAccessException e) { + geyserLogger.warning("Could not force-disable query. Geyser may not start correctly!"); + if (geyserLogger.isDebug()) { + e.printStackTrace(); + } + } + } + } + if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; From 5a805bc688f564a0dad8f35913903f257706b362 Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:18:53 +0100 Subject: [PATCH 279/358] Fix building --- bootstrap/fabric/build.gradle.kts | 5 +++-- build-logic/build.gradle.kts | 8 ++------ settings.gradle.kts | 1 + 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index ef526aa0047..5c2409be895 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "0.12-SNAPSHOT" + id("fabric-loom") version "1.0-SNAPSHOT" id("maven-publish") id("com.github.johnrengelman.shadow") id("java") @@ -79,6 +79,7 @@ tasks { 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") + archiveClassifier.set("unshaded") // We don't want it included in the archived artifacts } jar { @@ -87,7 +88,7 @@ tasks { remapJar { dependsOn(shadowJar) - inputs.file(shadowJar.get().archiveFile) + inputFile.set(shadowJar.get().archiveFile) archiveBaseName.set("Geyser-Fabric") archiveClassifier.set("") archiveVersion.set("") diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 6f4647c0fc0..c992a3ca9e8 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -6,21 +6,17 @@ plugins { repositories { gradlePluginPortal() + maven("https://repo.opencollab.dev/maven-snapshots") } dependencies { implementation("net.kyori", "indra-common", "2.0.6") implementation("org.jfrog.buildinfo", "build-info-extractor-gradle", "4.26.1") - implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.2") { - exclude("org.ow2.asm", "*") - } + 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.13.3") - - // Use a newer version of ObjectWeb ASM than the one provided by loom. - implementation("org.ow2.asm:asm-commons:9.4") } tasks.withType { diff --git a/settings.gradle.kts b/settings.gradle.kts index b61fefc31b8..6f212d3acc7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -48,6 +48,7 @@ pluginManagement { repositories { gradlePluginPortal() maven("https://maven.fabricmc.net/") + maven("https://repo.opencollab.dev/maven-snapshots") } plugins { id("net.kyori.blossom") version "1.2.0" From f4b810534b28027a9f828444ba14bb28776bdd18 Mon Sep 17 00:00:00 2001 From: Jens Collaert <63231928+Jens-Co@users.noreply.github.com> Date: Fri, 7 Oct 2022 19:14:42 +0200 Subject: [PATCH 280/358] Remove Windows version number from UWP. (#3339) --- .../src/main/java/org/geysermc/api/util/BedrockPlatform.java | 2 +- common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index e486f73bc1e..15d0da02701 100644 --- a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java +++ b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java @@ -35,7 +35,7 @@ public enum BedrockPlatform { AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - UWP("Windows 10"), + UWP("Windows"), WIN32("Windows x86"), DEDICATED("Dedicated"), TVOS("Apple TV"), 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"), From f59e33d749c7e8fa3df69dc004e795a8ed4c7230 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:40:07 -0400 Subject: [PATCH 281/358] Fix behavior of matching custom item predicates Huge thanks to Kastle for helping me disect this behavior. - The Unbreakable NBT tag is not the only source for determining if an item should be treated as unbreakable. The damage NBT is also taken into account. - Custom item options must be processed in an ascending order. - Multiple conditions may be necessary for an item to be selected. - Conditions do not have to be exact. See the comments in CustomItemTranslator for an explanation. - Added a test so we don't break this behavior in the future. --- .../CustomItemRegistryPopulator.java | 4 +- .../populator/ItemRegistryPopulator.java | 20 +- .../geyser/registry/type/ItemMapping.java | 15 +- .../geysermc/geyser/text/GeyserLocale.java | 22 +++ .../inventory/item/CustomItemTranslator.java | 105 +++++++++++ .../inventory/item/ItemTranslator.java | 171 +++--------------- .../inventory/item/CustomItemsTest.java | 116 ++++++++++++ 7 files changed, 293 insertions(+), 160 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java create mode 100644 core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 64543272e9a..017ede61ec3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -30,7 +30,6 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import it.unimi.dsi.fastutil.objects.Object2IntMaps; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; @@ -43,6 +42,7 @@ 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; @@ -85,7 +85,7 @@ public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItem .maxDamage(customItemData.maxDamage()) .repairMaterials(customItemData.repairMaterials()) .hasSuspiciousStewEffect(false) - .customItemOptions(Object2IntMaps.emptyMap()) + .customItemOptions(Collections.emptyList()) .build(); NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, 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 6c01ee9d264..f928361cc82 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 @@ -95,7 +95,10 @@ public static void populate() { boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); - Multimap customItems = MultimapBuilder.hashKeys().hashSetValues().build(); + // List values here is important compared to HashSet - we need to preserve the order of what's given to us + // (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom + // of the list first, then ascends. + Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); List nonVanillaCustomItems; MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); @@ -468,10 +471,10 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { } // Add the custom item properties, if applicable - Object2IntMap customItemOptions; + List> customItemOptions; Collection customItemsToLoad = customItems.get(javaIdentifier); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { - customItemOptions = new Object2IntOpenHashMap<>(customItemsToLoad.size()); + customItemOptions = new ObjectArrayList<>(customItemsToLoad.size()); for (CustomItemData customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; @@ -491,12 +494,15 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry()); // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemOptions.put(customItem.customItemOptions(), customProtocolId); + customItemOptions.add(ObjectIntPair.of(customItem.customItemOptions(), customProtocolId)); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } + + // Important for later to find the best match and accurately replicate Java behavior + Collections.reverse(customItemOptions); } else { - customItemOptions = Object2IntMaps.emptyMap(); + customItemOptions = Collections.emptyList(); } mappingBuilder.customItemOptions(customItemOptions); @@ -550,7 +556,7 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) - .customItemOptions(Object2IntMaps.emptyMap()) + .customItemOptions(Collections.emptyList()) .build(); if (customItemsAllowed) { @@ -567,7 +573,7 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) - .customItemOptions(Object2IntMaps.emptyMap()) // TODO check for custom items with furnace minecart + .customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index 12ba7d2085b..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,15 +25,15 @@ package org.geysermc.geyser.registry.type; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.CustomItemOptions; -import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.registry.BlockRegistries; +import java.util.Collections; +import java.util.List; import java.util.Set; @Value @@ -41,8 +41,8 @@ @EqualsAndHashCode public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, - BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, Object2IntMaps.emptyMap(), 0, null, false); + 0, // Air is never sent in full over the network for this to serialize. + 64, null, null, null, Collections.emptyList(), 0, null, false); String javaIdentifier; String bedrockIdentifier; @@ -62,7 +62,8 @@ public class ItemMapping { String translationString; - Object2IntMap customItemOptions; + @NonNull + List> customItemOptions; int maxDamage; diff --git a/core/src/main/java/org/geysermc/geyser/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/translator/inventory/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java new file mode 100644 index 00000000000..29aa2f74853 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.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.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; + int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; + boolean unbreakable = !isDamaged(mapping, 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 (unbreakable && options.unbreakable() != TriState.TRUE) { + continue; + } + + OptionalInt customModelDataOption = options.customModelData(); + if (customModelDataOption.isPresent() && customModelData < customModelDataOption.getAsInt()) { + continue; + } + + OptionalInt damagePredicate = options.damagePredicate(); + if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { + continue; + } + + return mappingTypes.valueInt(); + } + return -1; + } + + /* These two functions are based off their Mojmap equivalents from 1.19.2 */ + + private static boolean isDamaged(ItemMapping mapping, CompoundTag nbt, int damage) { + return isDamagableItem(mapping, nbt) && damage > 0; + } + + private static boolean isDamagableItem(ItemMapping mapping, CompoundTag nbt) { + if (mapping.getMaxDamage() > 0) { + Tag unbreakableTag = nbt.get("Unbreakable"); + return unbreakableTag != null && unbreakableTag.getValue() instanceof Number number && number.byteValue() == 0; + } + return false; + } + + private CustomItemTranslator() { + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index b36833cb165..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 @@ -34,12 +34,9 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.item.custom.CustomItemOptions; -import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; @@ -173,18 +170,18 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } - translateCustomItem(nbt, builder, bedrockItem); - 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(); @@ -246,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(); @@ -261,8 +257,9 @@ 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; } /** @@ -276,7 +273,7 @@ public static int getBedrockItemId(GeyserSession session, @Nonnull GeyserItemSta ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) .getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings()); - int customItemId = getCustomItem(itemStack.getNbt(), mapping); + int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping); if (customItemId == -1) { // No custom item return mapping.getBedrockId(); @@ -329,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)); @@ -400,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) { @@ -544,87 +504,10 @@ public static CompoundTag translateDisplayProperties(GeyserSession session, Comp * Translates the custom model data of an item */ private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) { - int bedrockId = getCustomItem(nbt, mapping); + int bedrockId = CustomItemTranslator.getCustomItem(nbt, mapping); if (bedrockId != -1) { builder.id(bedrockId); } } - private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { - if (nbt == null) { - return -1; - } - Object2IntMap customMappings = mapping.getCustomItemOptions(); - if (customMappings.isEmpty()) { - return -1; - } - int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; - TriState unbreakable = TriState.fromBoolean(nbt.get("Unbreakable") instanceof ByteTag unbreakableTag && unbreakableTag.getValue() == 1); - int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; - for (Object2IntMap.Entry mappingTypes : customMappings.object2IntEntrySet()) { - CustomItemOptions options = mappingTypes.getKey(); - - TriState unbreakableOption = options.unbreakable(); - if (unbreakableOption == unbreakable) { // Implementation note: if the option is NOT_SET then this comparison will always be false because of how the item unbreaking TriState is created - return mappingTypes.getIntValue(); - } - - OptionalInt customModelDataOption = options.customModelData(); - if (customModelDataOption.isPresent() && customModelDataOption.getAsInt() == customModelData) { - return mappingTypes.getIntValue(); - } - - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damagePredicate.getAsInt() == damage) { - return mappingTypes.getIntValue(); - } - } - return -1; - } - - /** - * Checks if an {@link ItemStack} is equal to another item stack - * - * @param itemStack the item stack to check - * @param equalsItemStack the item stack to check if equal to - * @param checkAmount if the amount should be taken into account - * @param trueIfAmountIsGreater if this should return true if the amount of the - * first item stack is greater than that of the second - * @param checkNbt if NBT data should be checked - * @return if an item stack is equal to another item stack - */ - public boolean equals(ItemStack itemStack, ItemStack equalsItemStack, boolean checkAmount, boolean trueIfAmountIsGreater, boolean checkNbt) { - if (itemStack.getId() != equalsItemStack.getId()) { - return false; - } - if (checkAmount) { - if (trueIfAmountIsGreater) { - if (itemStack.getAmount() < equalsItemStack.getAmount()) { - return false; - } - } else { - if (itemStack.getAmount() != equalsItemStack.getAmount()) { - return false; - } - } - } - - 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; - } - - if (itemStack.getNbt() != null && equalsItemStack.getNbt() != null) { - return itemStack.getNbt().equals(equalsItemStack.getNbt()); - } - - return true; - } - } 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..900feef7207 --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java @@ -0,0 +1,116 @@ +/* + * 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.OptionalInt; + +public class CustomItemsTest { + private ItemMapping testMappingWithDamage; + private Object2IntMap tagToCustomItemWithDamage; + + @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)); + + Object2IntMap optionsToId = new Object2IntArrayMap<>(); + // Order here is important, hence why we're using an array map + 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(""); + tag.put(new IntTag("CustomModelData", 6)); + // Test item with no damage should be treated as unbreakable + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 3)); + tag.put(new ByteTag("Unbreakable", (byte) 1)); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + tag.put(new IntTag("Damage", 16)); + tag.put(new ByteTag("Unbreakable", (byte) 0)); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e)); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 7)); + tag.put(new IntTag("Damage", 6)); + tag.put(new ByteTag("Unbreakable", (byte) 0)); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c)); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 8)); + tag.put(new IntTag("Damage", 6)); + tag.put(new ByteTag("Unbreakable", (byte) 1)); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 9)); + tag.put(new IntTag("Damage", 6)); + tag.put(new ByteTag("Unbreakable", (byte) 0)); + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(f)); + + testMappingWithDamage = ItemMapping.builder() + .customItemOptions(optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList()) + .maxDamage(100) + .build(); + // Later, possibly add a condition with a mapping with no damage + } + + @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); + } + } +} From 8bf8b22d6bf5beba316cbceb333a1d4b199e9585 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:21:58 -0400 Subject: [PATCH 282/358] Fix some regressions in custom item handling --- .../inventory/item/CustomItemTranslator.java | 42 ++++++----- .../inventory/item/CustomItemsTest.java | 72 +++++++++++++++---- 2 files changed, 82 insertions(+), 32 deletions(-) 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 index 29aa2f74853..82a8c9de152 100644 --- 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 @@ -51,8 +51,9 @@ static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { } int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; - int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; - boolean unbreakable = !isDamaged(mapping, nbt, damage); + 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(); @@ -67,8 +68,21 @@ static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { // 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 (unbreakable && options.unbreakable() != TriState.TRUE) { - continue; + 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(); @@ -76,11 +90,6 @@ static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { continue; } - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { - continue; - } - return mappingTypes.valueInt(); } return -1; @@ -88,16 +97,15 @@ static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { /* These two functions are based off their Mojmap equivalents from 1.19.2 */ - private static boolean isDamaged(ItemMapping mapping, CompoundTag nbt, int damage) { - return isDamagableItem(mapping, nbt) && damage > 0; + private static boolean isDamaged(CompoundTag nbt, int damage) { + return isDamagableItem(nbt) && damage > 0; } - private static boolean isDamagableItem(ItemMapping mapping, CompoundTag nbt) { - if (mapping.getMaxDamage() > 0) { - Tag unbreakableTag = nbt.get("Unbreakable"); - return unbreakableTag != null && unbreakableTag.getValue() instanceof Number number && number.byteValue() == 0; - } - return false; + 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/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java index 900feef7207..6870558d002 100644 --- 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 @@ -40,11 +40,14 @@ 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() { @@ -54,9 +57,11 @@ public void setup() { 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); @@ -72,38 +77,70 @@ public void setup() { tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 3)); - tag.put(new ByteTag("Unbreakable", (byte) 1)); + 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(""); - tag.put(new IntTag("Damage", 16)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + addDamage(16, tag); + setUnbreakable(false, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 7)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + addCustomModelData(7, tag); + addDamage(6, tag); + setUnbreakable(false, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 8)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 1)); + addCustomModelData(9, tag); + addDamage(6, tag); + setUnbreakable(true, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 9)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + 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(optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList()) + .customItemOptions(customItemOptions) .maxDamage(100) .build(); - // Later, possibly add a condition with a mapping with no damage + + // Test differences with items with no max damage + + tagToCustomItemWithNoDamage = new Object2IntOpenHashMap<>(); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 2)); + // 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 @@ -112,5 +149,10 @@ public void testCustomItems() { 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); + } } } From bd613987cea936a5f7e4a00b5b79a4115630acb8 Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Sun, 16 Oct 2022 02:26:02 +0200 Subject: [PATCH 283/358] Fix empty chunk encoding --- .../protocol/java/JavaLoginTranslator.java | 5 +-- .../protocol/java/JavaRespawnTranslator.java | 4 -- .../JavaLevelChunkWithLightTranslator.java | 10 ++--- .../org/geysermc/geyser/util/ChunkUtils.java | 42 ++++++++++--------- .../geysermc/geyser/util/DimensionUtils.java | 2 + 5 files changed, 31 insertions(+), 32 deletions(-) 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 6aa613b2486..978d4b6fb4e 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 @@ -99,6 +99,8 @@ 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); + session.setDimensionType(dimensions.get(newDimension)); + ChunkUtils.loadDimension(session); session.connect(); // It is now safe to send these packets @@ -145,8 +147,5 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { // If the player is spawning into the "fake" nether, send them some fog 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/JavaRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java index 3471ce57628..ed429b87dbf 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 @@ -36,7 +36,6 @@ 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.util.ChunkUtils; import org.geysermc.geyser.util.DimensionUtils; @Translator(packet = ClientboundRespawnPacket.class) @@ -93,9 +92,6 @@ public void translate(GeyserSession session, ClientboundRespawnPacket packet) { } 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/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/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index 2d25dd55586..679d7a6584b 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) { @@ -185,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); 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..59f5ed55dc9 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -94,6 +94,8 @@ public static void switchDimension(GeyserSession session, String javaDimension) changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); session.setDimension(javaDimension); + session.setDimensionType(session.getDimensions().get(javaDimension)); + ChunkUtils.loadDimension(session); player.setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); From 7bf9b92cbb60ca37262f8efd2d089a686a531273 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 16 Oct 2022 20:21:29 -0400 Subject: [PATCH 284/358] Cleanup buildscript, add github actions --- .github/workflows/pullrequest.yml | 7 +++++++ bootstrap/fabric/build.gradle.kts | 20 ------------------- .../fabric/src/main/resources/fabric.mod.json | 8 ++++---- .../kotlin/geyser.base-conventions.gradle.kts | 3 ++- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index cb55c2675a4..39e9fe1881a 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -19,6 +19,13 @@ jobs: uses: snickerbockers/submodules-init@v4 - name: Build with Gradle run: ./gradlew build + + - name: Archive artifacts (Geyser Fabric) + uses: actions/upload-artifact@v2 + if: success() + with: + name: Geyser Fabric + path: bootstrap/fabric/build/libs/Geyser-Fabric.jar - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@v2 if: success() diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 5c2409be895..7e767afe0f5 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -51,21 +51,6 @@ repositories { } tasks { - processResources { -// inputs.property "version", project.version -// -// filesMatching("fabric.mod.json") { -// expand "version": project.version -// } - } - - // ensure that the encoding is set to UTF-8, no matter what the system default is - // this fixes some edge cases with special characters not displaying correctly - // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html - compileJava { - options.encoding = "UTF-8" - } - // 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. @@ -79,11 +64,6 @@ tasks { 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") - archiveClassifier.set("unshaded") // We don't want it included in the archived artifacts - } - - jar { - from("LICENSE") } remapJar { diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 9d66ca08c53..4460e277b9d 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -1,14 +1,14 @@ { "schemaVersion": 1, - "id": "geyser-fabric", + "id": "${id}-fabric", "version": "${version}", - "name": "Geyser-Fabric", + "name": "${name}-Fabric", "description": "A bridge/proxy allowing you to connect to Minecraft: Java Edition servers with Minecraft: Bedrock Edition. ", "authors": [ - "GeyserMC" + "${author}" ], "contact": { - "website": "https://geysermc.org", + "website": "${url}", "repo": "https://github.com/GeyserMC/Geyser-Fabric" }, "license": "MIT", diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index bd50c6ea0c5..44a74db3dc8 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -9,7 +9,8 @@ dependencies { tasks { processResources { - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json")) { + // Spigot, BungeeCord, Velocity, Sponge, Fabric + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json", "fabric.mod.json")) { expand( "id" to "geyser", "name" to "Geyser", From 675e70be6673050dc52e0b470960f965566e1f92 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:47:26 -0400 Subject: [PATCH 285/358] Fix shaded jar --- Jenkinsfile | 2 +- bootstrap/fabric/build.gradle.kts | 10 +++++ .../platform/fabric/GeyserFabricMain.java | 45 +++++++++++++++++++ build.gradle.kts | 1 + 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java diff --git a/Jenkinsfile b/Jenkinsfile index 0123a277180..065ca48c4e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'bootstrap/**/build/libs/*.jar', excludes: 'bootstrap/**/build/libs/*-sources.jar,bootstrap/**/build/libs/*-unshaded.jar', fingerprint: true + archiveArtifacts artifacts: 'bootstrap/**/build/libs/Geyser-*.jar', fingerprint: true } } } diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 7e767afe0f5..8729e671c92 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -50,6 +50,10 @@ repositories { 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. @@ -60,6 +64,12 @@ tasks { } 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") diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java new file mode 100644 index 00000000000..354dbbf77f9 --- /dev/null +++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.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.platform.fabric; + +import org.geysermc.geyser.GeyserMain; + +public class GeyserFabricMain extends GeyserMain { + + public static void main(String[] args) { + new GeyserFabricMain().displayMessage(); + } + + @Override + public String getPluginType() { + return "Fabric"; + } + + @Override + public String getPluginFolder() { + return "mods"; + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 7371978d391..06c2e987b00 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ allprojects { } val platforms = setOf( + projects.fabric, projects.bungeecord, projects.spigot, projects.sponge, From 379453191009f381744ff0d2b2e6a5bd6925f10e Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 17 Oct 2022 00:25:54 -0400 Subject: [PATCH 286/358] Fix application main --- bootstrap/fabric/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 8729e671c92..36d1163bd84 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -51,7 +51,7 @@ repositories { } application { - mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") + mainClass.set("org.geysermc.platform.fabric.GeyserFabricMain") } tasks { From 237a69d3c28d1d77a6dd85e8513444e288b3e1cc Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:08:52 +0100 Subject: [PATCH 287/358] Refactor package and cleanup dependencies for Fabric --- bootstrap/fabric/build.gradle.kts | 19 +++++-------------- .../fabric/GeyserFabricConfiguration.java | 4 ++-- .../platform/fabric/GeyserFabricDumpInfo.java | 4 ++-- .../platform/fabric/GeyserFabricLogger.java | 4 ++-- .../platform/fabric/GeyserFabricMain.java | 2 +- .../platform/fabric/GeyserFabricMod.java | 10 +++++----- .../fabric/GeyserFabricPermissions.java | 4 ++-- .../fabric/GeyserServerPortGetter.java | 4 ++-- .../{ => geyser}/platform/fabric/ModInfo.java | 4 ++-- .../fabric/command/FabricCommandSender.java | 6 +++--- .../command/GeyserFabricCommandExecutor.java | 8 ++++---- .../command/GeyserFabricCommandManager.java | 4 ++-- .../mixin/client/IntegratedServerMixin.java | 8 ++++---- .../server/MinecraftDedicatedServerMixin.java | 6 +++--- .../world/GeyserFabricWorldManager.java | 8 ++++---- .../fabric/src/main/resources/fabric.mod.json | 2 +- .../main/resources/geyser-fabric.mixins.json | 2 +- gradle.properties | 14 +------------- gradle/libs.versions.toml | 6 ++++++ 19 files changed, 52 insertions(+), 67 deletions(-) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricConfiguration.java (95%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricDumpInfo.java (96%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricLogger.java (95%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricMain.java (97%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricMod.java (97%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserFabricPermissions.java (94%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/GeyserServerPortGetter.java (95%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/ModInfo.java (95%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/command/FabricCommandSender.java (94%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/command/GeyserFabricCommandExecutor.java (94%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/command/GeyserFabricCommandManager.java (93%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/mixin/client/IntegratedServerMixin.java (92%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java (92%) rename bootstrap/fabric/src/main/java/org/geysermc/{ => geyser}/platform/fabric/world/GeyserFabricWorldManager.java (96%) diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 36d1163bd84..2a6d1422c66 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -10,23 +10,14 @@ java { sourceCompatibility = JavaVersion.VERSION_17 } -//archivesBaseName = project.archives_base_name -//version = project.mod_version -//group = project.maven_group - -val minecraftVersion = project.property("minecraft_version") as String -val yarnVersion = project.property("yarn_mappings") as String -val loaderVersion = project.property("loader_version") as String -val fabricVersion = project.property("fabric_version") as String - dependencies { //to change the versions see the gradle.properties file - minecraft("com.mojang:minecraft:$minecraftVersion") - mappings("net.fabricmc:yarn:$yarnVersion:v2") - modImplementation("net.fabricmc:fabric-loader:$loaderVersion") + minecraft(libs.fabric.minecraft) + mappings(libs.fabric.yarn) { artifact { classifier = "v2" } } + modImplementation(libs.fabric.loader) // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion") + modImplementation(libs.fabric.api) // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. @@ -51,7 +42,7 @@ repositories { } application { - mainClass.set("org.geysermc.platform.fabric.GeyserFabricMain") + mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") } tasks { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java similarity index 95% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java index b4970787167..f557d16c0ea 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricConfiguration.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import com.fasterxml.jackson.annotation.JsonIgnore; import net.fabricmc.loader.api.FabricLoader; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java similarity index 96% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java index e3997da51cc..e09129fc19c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java similarity index 95% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java index 9c85a21f2c5..a6ee77f4165 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricLogger.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java similarity index 97% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java index 354dbbf77f9..f3f63324a9b 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMain.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import org.geysermc.geyser.GeyserMain; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java similarity index 97% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index cdd7d358a49..f565daea381 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.fabricmc.api.EnvType; @@ -49,9 +49,9 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.util.FileUtils; -import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; -import org.geysermc.platform.fabric.command.GeyserFabricCommandManager; -import org.geysermc.platform.fabric.world.GeyserFabricWorldManager; +import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; +import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandManager; +import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; import org.jetbrains.annotations.Nullable; import java.io.File; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java similarity index 94% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java index fc08b052aba..a625f6d1fe7 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java similarity index 95% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java index 5af7775a818..7e856c5cea1 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserServerPortGetter.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import net.minecraft.server.MinecraftServer; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java similarity index 95% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java index da753c44f3b..b9137c43880 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric; +package org.geysermc.geyser.platform.fabric; import net.fabricmc.loader.api.ModContainer; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java similarity index 94% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java index 20dee1b217a..1a5e700f85c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.command; +package org.geysermc.geyser.platform.fabric.command; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.text.ChatColor; -import org.geysermc.platform.fabric.GeyserFabricMod; public class FabricCommandSender implements GeyserCommandSource { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java similarity index 94% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java index 07b8bd51949..7ef77e8560c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.command; +package org.geysermc.geyser.platform.fabric.command; import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; @@ -31,11 +31,11 @@ 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.platform.fabric.GeyserFabricPermissions; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.platform.fabric.GeyserFabricMod; -import org.geysermc.platform.fabric.GeyserFabricPermissions; import java.util.Collections; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java similarity index 93% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java index feaf401302c..d21dae31988 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.command; +package org.geysermc.geyser.platform.fabric.command; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandManager; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java similarity index 92% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java index 6a6d3e0e611..d329c189486 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.mixin.client; +package org.geysermc.geyser.platform.fabric.mixin.client; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -32,9 +32,9 @@ import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.text.Text; import net.minecraft.world.GameMode; +import org.geysermc.geyser.platform.fabric.GeyserFabricMod; +import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.platform.fabric.GeyserFabricMod; -import org.geysermc.platform.fabric.GeyserServerPortGetter; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java similarity index 92% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java index a41a083423d..799d94917be 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.mixin.server; +package org.geysermc.geyser.platform.fabric.mixin.server; import com.mojang.datafixers.DataFixer; import net.minecraft.resource.ResourcePackManager; @@ -33,7 +33,7 @@ import net.minecraft.server.dedicated.MinecraftDedicatedServer; import net.minecraft.util.ApiServices; import net.minecraft.world.level.storage.LevelStorage; -import org.geysermc.platform.fabric.GeyserServerPortGetter; +import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; import org.spongepowered.asm.mixin.Mixin; import java.net.Proxy; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java similarity index 96% rename from bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java index 40c7fd302ab..be68cefb975 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.fabric.world; +package org.geysermc.geyser.platform.fabric.world; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; @@ -39,11 +39,11 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.platform.fabric.GeyserFabricMod; +import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.util.BlockEntityUtils; -import org.geysermc.platform.fabric.GeyserFabricMod; -import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor; import java.util.ArrayList; import java.util.List; diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 4460e277b9d..ee23dd06dd8 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ "environment": "*", "entrypoints": { "main": [ - "org.geysermc.platform.fabric.GeyserFabricMod" + "org.geysermc.geyser.platform.fabric.GeyserFabricMod" ] }, "mixins": [ diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json index 6081bee937c..c688ace364c 100644 --- a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json +++ b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json @@ -1,6 +1,6 @@ { "required": true, - "package": "org.geysermc.platform.fabric.mixin", + "package": "org.geysermc.geyser.platform.fabric.mixin", "compatibilityLevel": "JAVA_16", "client": [ "client.IntegratedServerMixin" diff --git a/gradle.properties b/gradle.properties index 35cb2153783..4fb72e2fc2c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,16 +9,4 @@ version=2.1.0-SNAPSHOT org.gradle.caching=true org.gradle.parallel=true -org.gradle.vfs.watch=false - -# Fabric Properties -# check these on https://modmuss50.me/fabric.html -minecraft_version=1.19.1 -yarn_mappings=1.19.1+build.1 -loader_version=0.14.8 -# Mod Properties -maven_group=org.geysermc.platform -archives_base_name=Geyser-Fabric -# Dependencies -# check this on https://modmuss50.me/fabric.html -fabric_version=0.58.5+1.19.1 \ No newline at end of file +org.gradle.vfs.watch=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf4688d7f4e..b4d7430f1de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,6 +62,12 @@ jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jlin 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 = "1.19.1" } +fabric-yarn = { group = "net.fabricmc", name = "yarn", version = "1.19.1+build.1" } +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.14.8" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.58.5+1.19.1" } + 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" } From 657968f87242cff668d7c289397af8c3a7798a76 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:35:16 -0400 Subject: [PATCH 288/358] Indicate 1.19.31 Bedrock support --- README.md | 2 +- .../java/org/geysermc/geyser/network/GameProtocol.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 997bbff58b7..d91672eb4b4 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.30 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.30/1.19.31 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 4c5c099f033..844fd0f5141 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -47,7 +47,9 @@ 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_v554.V554_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v554.V554_CODEC.toBuilder() + .minecraftVersion("1.19.31") + .build(); /** * A list of all supported Bedrock versions that can join Geyser */ @@ -70,7 +72,9 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v545.V545_CODEC.toBuilder() .minecraftVersion("1.19.21/1.19.22") .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.19.30/1.19.31") + .build()); } /** From 0e07991edf4762975859d5541d56a3f997c95a3d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:36:46 -0400 Subject: [PATCH 289/358] Changes as I was randomly staring at the code --- .../entity/type/DefaultBlockMinecartEntity.java | 2 +- .../geyser/level/chunk/bitarray/BitArrayVersion.java | 2 +- .../level/chunk/bitarray/SingletonBitArray.java | 7 +++---- .../geyser/registry/AbstractMappedRegistry.java | 11 +++++------ .../populator/CustomItemRegistryPopulator.java | 4 +++- .../org/geysermc/geyser/session/SessionManager.java | 4 ++-- .../inventory/item/nbt/CrossbowTranslator.java | 4 ++-- .../inventory/item/nbt/LeatherArmorTranslator.java | 6 ++---- .../block/entity/CampfireBlockEntityTranslator.java | 3 ++- .../translator/inventory/item/CustomItemsTest.java | 4 ++-- 10 files changed, 23 insertions(+), 24 deletions(-) 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/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/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/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 017ede61ec3..1daa6d28daf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -34,6 +34,7 @@ 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; @@ -171,7 +172,8 @@ private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean isT itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); itemProperties.putBoolean("hand_equipped", isTool); itemProperties.putInt("max_stack_size", stackSize); - if (maxDamage > 0) { + // Ignore durability if the item's predicate requires that it be unbreakable + if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) { componentBuilder.putCompound("minecraft:durability", NbtMap.builder() .putCompound("damage_chance", NbtMap.builder() .putInt("max", 1) 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 02940e00c27..7e5982ab593 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableList; import lombok.AccessLevel; import lombok.Getter; -import lombok.NonNull; import org.geysermc.geyser.text.GeyserLocale; +import javax.annotation.Nonnull; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -69,7 +69,7 @@ public void removeSession(GeyserSession session) { } } - public GeyserSession sessionByXuid(@NonNull String xuid) { + public GeyserSession sessionByXuid(@Nonnull String xuid) { Objects.requireNonNull(xuid); for (GeyserSession session : sessions.values()) { if (session.xuid().equals(xuid)) { 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 0a4ca06864d..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 @@ -39,8 +39,8 @@ public class CrossbowTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { - if (itemTag.get("ChargedProjectiles") != null) { - ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); + ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); + if (chargedProjectiles != null) { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java index 2fb5ec6cbb5..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 @@ -32,13 +32,12 @@ import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import java.util.Arrays; import java.util.List; @ItemRemapper public class LeatherArmorTranslator extends NbtItemStackTranslator { - private static final List ITEMS = Arrays.asList("minecraft:leather_helmet", "minecraft:leather_chestplate", + private static final List ITEMS = List.of("minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots", "minecraft:leather_horse_armor"); @Override @@ -47,10 +46,9 @@ public 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/level/block/entity/CampfireBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java index 6ec0effcafe..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,6 +28,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.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.geyser.network.GameProtocol; @@ -40,7 +41,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { ListTag items = tag.get("Items"); int i = 1; - for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { + for (Tag itemTag : items.getValue()) { builder.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } diff --git a/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java index 6870558d002..145f4636910 100644 --- 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 @@ -72,7 +72,7 @@ public void setup() { tagToCustomItemWithDamage = new Object2IntOpenHashMap<>(); CompoundTag tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 6)); + addCustomModelData(6, tag); // Test item with no damage should be treated as unbreakable tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); @@ -121,7 +121,7 @@ public void setup() { tagToCustomItemWithNoDamage = new Object2IntOpenHashMap<>(); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 2)); + addCustomModelData(2, tag); // Damage predicates existing mean an item will never match if the item mapping has no max damage tagToCustomItemWithNoDamage.put(tag, -1); From 144d7b000a938c179f6649e3b693dd61c0ab756a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 18 Oct 2022 12:06:18 -0400 Subject: [PATCH 290/358] Use Mojang mappings for Geyser-Fabric --- Jenkinsfile | 1 - bootstrap/fabric/build.gradle.kts | 2 +- .../platform/fabric/GeyserFabricDumpInfo.java | 6 ++-- .../platform/fabric/GeyserFabricMod.java | 23 ++++++------- .../fabric/GeyserServerPortGetter.java | 2 +- .../fabric/command/FabricCommandSender.java | 22 +++++++------ .../command/GeyserFabricCommandExecutor.java | 10 +++--- .../mixin/client/IntegratedServerMixin.java | 24 +++++++------- .../server/MinecraftDedicatedServerMixin.java | 20 ++++++------ .../world/GeyserFabricWorldManager.java | 32 +++++++++---------- gradle/libs.versions.toml | 1 - 11 files changed, 72 insertions(+), 71 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 065ca48c4e4..072f991540c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -101,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/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 2a6d1422c66..dac79117361 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -13,7 +13,7 @@ java { dependencies { //to change the versions see the gradle.properties file minecraft(libs.fabric.minecraft) - mappings(libs.fabric.yarn) { artifact { classifier = "v2" } } + mappings(loom.officialMojangMappings()) modImplementation(libs.fabric.loader) // Fabric API. This is technically optional, but you probably want it anyway. 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 index e09129fc19c..2db6a372925 100644 --- 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 @@ -54,12 +54,12 @@ public GeyserFabricDumpInfo(MinecraftServer server) { } } this.environmentType = FabricLoader.getInstance().getEnvironmentType(); - if (AsteriskSerializer.showSensitive || (server.getServerIp() == null || server.getServerIp().equals("") || server.getServerIp().equals("0.0.0.0"))) { - this.serverIP = server.getServerIp(); + if (AsteriskSerializer.showSensitive || (server.getLocalIp() == null || server.getLocalIp().equals("") || server.getLocalIp().equals("0.0.0.0"))) { + this.serverIP = server.getLocalIp(); } else { this.serverIP = "***"; } - this.serverPort = server.getServerPort(); + this.serverPort = server.getPort(); this.mods = new ArrayList<>(); for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { 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 index f565daea381..333a3d81043 100644 --- 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 @@ -31,27 +31,28 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 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 net.minecraft.server.command.ServerCommandSource; 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.dump.BootstrapDumpInfo; -import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.GeyserBootstrap; 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.util.FileUtils; import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandManager; 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; @@ -145,7 +146,7 @@ public void startGeyser(MinecraftServer server) { if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); - String ip = server.getServerIp(); + 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); @@ -185,16 +186,16 @@ public void startGeyser(MinecraftServer server) { GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(connector, (GeyserCommand) connector.commandManager().getCommands().get("help"), !playerCommands.contains("help")); commandExecutors.add(helpExecutor); - LiteralArgumentBuilder builder = net.minecraft.server.command.CommandManager.literal("geyser").executes(helpExecutor); + LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor); // Register all subcommands as valid for (Map.Entry command : connector.commandManager().getCommands().entrySet()) { GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, (GeyserCommand) command.getValue(), !playerCommands.contains(command.getKey())); commandExecutors.add(executor); - builder.then(net.minecraft.server.command.CommandManager.literal(command.getKey()).executes(executor)); + builder.then(Commands.literal(command.getKey()).executes(executor)); } - server.getCommandManager().getDispatcher().register(builder); + server.getCommands().getDispatcher().register(builder); } @Override @@ -245,7 +246,7 @@ public BootstrapDumpInfo getDumpInfo() { @Override public String getMinecraftServerVersion() { - return this.server.getVersion(); + return this.server.getServerVersion(); } @Nullable 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 index 7e856c5cea1..4f1c8b638f7 100644 --- 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 @@ -39,7 +39,7 @@ public interface GeyserServerPortGetter { *
  • If it's an integrated server, it will return the LAN port if opened, else -1.
  • * * - * The reason is that {@link MinecraftServer#getServerPort()} doesn't return the LAN port if it's the integrated server, + * 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. 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 index 1a5e700f85c..0bb171e6ba3 100644 --- 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 @@ -25,31 +25,33 @@ package org.geysermc.geyser.platform.fabric.command; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; +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.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.text.ChatColor; +import javax.annotation.Nonnull; + public class FabricCommandSender implements GeyserCommandSource { - private final ServerCommandSource source; + private final CommandSourceStack source; - public FabricCommandSender(ServerCommandSource source) { + public FabricCommandSender(CommandSourceStack source) { this.source = source; } @Override public String name() { - return source.getName(); + return source.getTextName(); } @Override - public void sendMessage(String message) { - if (source.getEntity() instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) source.getEntity()).sendMessage(Text.literal(message), false); + 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)); } @@ -57,7 +59,7 @@ public void sendMessage(String message) { @Override public boolean isConsole() { - return !(source.getEntity() instanceof ServerPlayerEntity); + return !(source.getEntity() instanceof ServerPlayer); } @Override 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 index 7ef77e8560c..f691cd49e1c 100644 --- 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 @@ -27,7 +27,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; -import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.commands.CommandSourceStack; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; @@ -39,7 +39,7 @@ import java.util.Collections; -public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { +public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { private final GeyserCommand command; /** @@ -59,13 +59,13 @@ public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, * @param source The command source attempting to run the command * @return True if the command source is allowed to */ - public boolean canRun(ServerCommandSource source) { - return !requiresPermission() || source.hasPermissionLevel(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL); + public boolean canRun(CommandSourceStack source) { + return !requiresPermission() || source.hasPermission(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL); } @Override public int run(CommandContext context) { - ServerCommandSource source = (ServerCommandSource) context.getSource(); + CommandSourceStack source = (CommandSourceStack) context.getSource(); FabricCommandSender sender = new FabricCommandSender(source); GeyserSession session = getGeyserSession(sender); if (!canRun(source)) { 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 index d329c189486..94290906888 100644 --- 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 @@ -27,11 +27,11 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; +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.server.integrated.IntegratedServer; -import net.minecraft.text.Text; -import net.minecraft.world.GameMode; +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; @@ -46,25 +46,25 @@ @Mixin(IntegratedServer.class) public class IntegratedServerMixin implements GeyserServerPortGetter { @Shadow - private int lanPort; + private int publishedPort; - @Shadow @Final private MinecraftClient client; + @Shadow @Final private Minecraft minecraft; - @Inject(method = "openToLan", at = @At("RETURN")) - private void onOpenToLan(GameMode gameMode, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) { + @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.client.options.language); + GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); // Give indication that Geyser is loaded - this.client.player.sendMessage(Text.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start", - this.client.options.language, "localhost", String.valueOf(this.lanPort))), false); + 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.lanPort; + 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 index 799d94917be..23e148775df 100644 --- 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 @@ -26,26 +26,26 @@ package org.geysermc.geyser.platform.fabric.mixin.server; import com.mojang.datafixers.DataFixer; -import net.minecraft.resource.ResourcePackManager; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.SaveLoader; -import net.minecraft.server.WorldGenerationProgressListenerFactory; -import net.minecraft.server.dedicated.MinecraftDedicatedServer; -import net.minecraft.util.ApiServices; -import net.minecraft.world.level.storage.LevelStorage; +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(MinecraftDedicatedServer.class) +@Mixin(DedicatedServer.class) public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { - public MinecraftDedicatedServerMixin(Thread serverThread, LevelStorage.Session session, ResourcePackManager dataPackManager, SaveLoader saveLoader, Proxy proxy, DataFixer dataFixer, ApiServices apiServices, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) { - super(serverThread, session, dataPackManager, saveLoader, proxy, dataFixer, apiServices, worldGenerationProgressListenerFactory); + 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.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 index be68cefb975..0746198f372 100644 --- 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 @@ -29,15 +29,15 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.block.entity.LecternBlockEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.item.WritableBookItem; -import net.minecraft.item.WrittenBookItem; -import net.minecraft.nbt.NbtList; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.ListTag; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.math.BlockPos; +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.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; @@ -65,9 +65,9 @@ public boolean shouldExpectLecternHandled() { public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { Runnable lecternGet = () -> { // Mostly a reimplementation of Spigot lectern support - PlayerEntity player = getPlayer(session); + ServerPlayer player = getPlayer(session); if (player != null) { - BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z)); + BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z)); if (!(blockEntity instanceof LecternBlockEntity lectern)) { return; } @@ -83,14 +83,14 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole int pageCount = WrittenBookItem.getPageCount(book); boolean hasBookPages = pageCount > 0; NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1); - lecternTag.putInt("page", lectern.getCurrentPage() / 2); + 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.isValid(book.getNbt())) { - NbtList listTag = book.getNbt().getList("pages", 8); + 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); @@ -127,14 +127,14 @@ public boolean hasPermission(GeyserSession session, String permission) { // Workaround for our commands because fabric doesn't have native permissions for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { if (executor.getCommand().permission().equals(permission)) { - return executor.canRun(getPlayer(session).getCommandSource()); + return executor.canRun(getPlayer(session).createCommandSourceStack()); } } return false; } - private PlayerEntity getPlayer(GeyserSession session) { - return server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid()); + private ServerPlayer getPlayer(GeyserSession session) { + return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid()); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4d7430f1de..040ddbe62b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,6 @@ paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", versio # check these on https://modmuss50.me/fabric.html fabric-minecraft = { group = "com.mojang", name = "minecraft", version = "1.19.1" } -fabric-yarn = { group = "net.fabricmc", name = "yarn", version = "1.19.1+build.1" } fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.14.8" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.58.5+1.19.1" } From 730b0beb012a2890e76a70135ca9855ed6fa6f1b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:05:55 -0400 Subject: [PATCH 291/358] Consolidate CommandManager implementations A lot of these just implemented the class overrode the `description` method returning nothing. --- .../bungeecord/GeyserBungeePlugin.java | 5 +-- .../command/GeyserBungeeCommandManager.java | 41 ------------------- .../platform/fabric/GeyserFabricMod.java | 5 +-- .../command/GeyserFabricCommandManager.java | 41 ------------------- .../standalone/GeyserStandaloneBootstrap.java | 5 +-- .../GeyserStandaloneCommandManager.java | 41 ------------------- .../standalone/gui/GeyserStandaloneGUI.java | 4 +- .../velocity/GeyserVelocityPlugin.java | 5 +-- .../command/GeyserVelocityCommandManager.java | 41 ------------------- .../geyser/command/GeyserCommandManager.java | 6 ++- 10 files changed, 14 insertions(+), 180 deletions(-) delete mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java delete mode 100644 bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java delete mode 100644 bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java delete mode 100644 bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java 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 ab5531ba3a4..1c460f4dea5 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 @@ -43,7 +43,6 @@ import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; -import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -62,7 +61,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { - private GeyserBungeeCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeInjector geyserInjector; private GeyserBungeeLogger geyserLogger; @@ -205,7 +204,7 @@ private void postStartup() { 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())); diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java deleted file mode 100644 index e0fd7a4acc6..00000000000 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.platform.bungeecord.command; - -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; - -public class GeyserBungeeCommandManager extends GeyserCommandManager { - - public GeyserBungeeCommandManager(GeyserImpl geyser) { - super(geyser); - } - - @Override - public String description(String command) { - return ""; // no support for command descriptions in bungee - } -} 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 index 333a3d81043..792e167889b 100644 --- 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 @@ -49,7 +49,6 @@ 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.command.GeyserFabricCommandManager; import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -79,7 +78,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private List playerCommands; private final List commandExecutors = new ArrayList<>(); - private GeyserFabricCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -176,7 +175,7 @@ public void startGeyser(MinecraftServer server) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); - this.geyserCommandManager = new GeyserFabricCommandManager(connector); + this.geyserCommandManager = new GeyserCommandManager(connector); this.geyserCommandManager.init(); this.geyserWorldManager = new GeyserFabricWorldManager(server); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java deleted file mode 100644 index d21dae31988..00000000000 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandManager.java +++ /dev/null @@ -1,41 +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.fabric.command; - -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; - -public class GeyserFabricCommandManager extends GeyserCommandManager { - - public GeyserFabricCommandManager(GeyserImpl connector) { - super(connector); - } - - @Override - public String description(String command) { - return ""; - } -} 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 80d17f6a7eb..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 @@ -47,7 +47,6 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandManager; import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -64,7 +63,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { - private GeyserStandaloneCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -220,7 +219,7 @@ public void onEnable() { geyser = GeyserImpl.load(PlatformType.STANDALONE, this); GeyserImpl.start(); - geyserCommandManager = new GeyserStandaloneCommandManager(geyser); + geyserCommandManager = new GeyserCommandManager(geyser); geyserCommandManager.init(); if (gui != null) { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java deleted file mode 100644 index e7b4cbe3799..00000000000 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserStandaloneCommandManager.java +++ /dev/null @@ -1,41 +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.standalone.command; - -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; - -public class GeyserStandaloneCommandManager extends GeyserCommandManager { - - public GeyserStandaloneCommandManager(GeyserImpl geyser) { - super(geyser); - } - - @Override - public String description(String command) { - return ""; // this is not sent over the protocol, so we return none - } -} 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 a8bce303f29..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 @@ -28,8 +28,8 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger; -import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -256,7 +256,7 @@ public void write(byte[] b) { * @param geyserStandaloneLogger The current logger * @param geyserCommandManager The commands manager */ - public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserStandaloneCommandManager geyserCommandManager) { + public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) { commandsMenu.removeAll(); optionsMenu.removeAll(); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index e92bb6cf245..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 @@ -48,7 +48,6 @@ import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor; -import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.Nullable; @@ -75,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; @@ -163,7 +162,7 @@ private void postStartup() { this.geyserInjector = new GeyserVelocityInjector(proxyServer); // Will be initialized after the proxy has been bound - this.geyserCommandManager = new GeyserVelocityCommandManager(geyser); + this.geyserCommandManager = new GeyserCommandManager(geyser); this.geyserCommandManager.init(); this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java deleted file mode 100644 index 6f9faba8fdd..00000000000 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.platform.velocity.command; - -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; - -public class GeyserVelocityCommandManager extends GeyserCommandManager { - - public GeyserVelocityCommandManager(GeyserImpl geyser) { - super(geyser); - } - - @Override - public String description(String command) { - return ""; // no support for command descriptions in velocity - } -} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 7c5bd65807b..d28f9d24edc 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -63,7 +63,7 @@ import java.util.Map; @RequiredArgsConstructor -public abstract class GeyserCommandManager { +public class GeyserCommandManager { @Getter private final Map commands = new Object2ObjectOpenHashMap<>(12); @@ -198,7 +198,9 @@ public boolean runCommand(GeyserCommandSource sender, String command) { * @param command Command to get the description for * @return Command description */ - public abstract String description(String command); + public String description(String command) { + return ""; + } @RequiredArgsConstructor public static class CommandBuilder implements Command.Builder { From 94a810b68346cd3986e9380cb528aba815c0e7dd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:04:02 -0400 Subject: [PATCH 292/358] Initial 1.19.40.24 Bedrock support --- .../geysermc/geyser/level/JavaDimension.java | 4 +- .../geysermc/geyser/network/GameProtocol.java | 2 + .../translator/level/BiomeTranslator.java | 4 +- .../protocol/java/JavaLoginTranslator.java | 4 +- ...JavaCodecEntry.java => JavaCodecUtil.java} | 5 ++- .../geyser/util/StatisticFormatters.java | 9 +++-- .../geysermc/geyser/util/StatisticsUtils.java | 39 +++++++++---------- 7 files changed, 37 insertions(+), 30 deletions(-) rename core/src/main/java/org/geysermc/geyser/util/{JavaCodecEntry.java => JavaCodecUtil.java} (96%) 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/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 844fd0f5141..5870b92b64c 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -33,6 +33,7 @@ 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 org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; @@ -75,6 +76,7 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() .minecraftVersion("1.19.30/1.19.31") .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.V557_CODEC); } /** 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 3e47bfc370c..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 @@ -45,7 +45,7 @@ import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.util.JavaCodecEntry; +import org.geysermc.geyser.util.JavaCodecUtil; import org.geysermc.geyser.util.MathUtils; // Array index formula by https://wiki.vg/Chunk_Format @@ -59,7 +59,7 @@ public 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/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 978d4b6fb4e..60382e17aea 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 @@ -46,7 +46,7 @@ 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"); 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/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 f0167010618..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; @@ -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; From ece1b2a8ce2010fa947a32fde29b50910f08db26 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:04:45 -0400 Subject: [PATCH 293/358] Include versions TOML file change --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 040ddbe62b5..b16812b6f4f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ 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.12-20220926.095446-6" +protocol = "2.9.13-20221018.122404-1" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "9f78bd5" @@ -79,7 +79,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" } mcprotocollib = { group = "com.github.GeyserMC", name = "MCProtocolLib", version.ref = "mcprotocollib" } packetlib = { group = "com.github.steveice10", name = "packetlib", version.ref = "packetlib" } -protocol = { group = "com.nukkitx.protocol", name = "bedrock-v554", version.ref = "protocol" } +protocol = { group = "com.nukkitx.protocol", name = "bedrock-v557", 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" } From e8764c6a817cebde1e508e3ffe613d459d1702fe Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Thu, 20 Oct 2022 20:17:08 +0200 Subject: [PATCH 294/358] Fix rare NPE in skin handling code (#3357) --- .../org/geysermc/geyser/skin/SkinManager.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) 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 992835a2bbd..730d4690828 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -258,21 +258,26 @@ 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); } /** From a612be60aada1f4e931420d9823bc02df40dcbd3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 21 Oct 2022 14:09:17 -0400 Subject: [PATCH 295/358] Warn when custom item name begins with a digit --- .../registry/populator/CustomItemRegistryPopulator.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 1daa6d28daf..94e04e97265 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -66,6 +66,13 @@ static boolean initialCheck(String identifier, CustomItemData item, Map Date: Mon, 24 Oct 2022 13:21:02 -0400 Subject: [PATCH 296/358] Fabric improvements Mainly in commands - the old permissions file no longer needs to exist. --- bootstrap/fabric/build.gradle.kts | 3 + .../platform/fabric/GeyserFabricLogger.java | 14 +++- .../platform/fabric/GeyserFabricMod.java | 74 +++++-------------- ...s.java => GeyserFabricUpdateListener.java} | 31 +++----- .../fabric/command/FabricCommandSender.java | 26 ++++--- .../command/GeyserFabricCommandExecutor.java | 35 ++------- .../world/GeyserFabricWorldManager.java | 13 +--- .../fabric/src/main/resources/fabric.mod.json | 3 +- .../fabric/src/main/resources/permissions.yml | 13 ---- .../org/geysermc/geyser/text/ChatColor.java | 18 +---- gradle/libs.versions.toml | 9 ++- 11 files changed, 81 insertions(+), 158 deletions(-) rename bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/{GeyserFabricPermissions.java => GeyserFabricUpdateListener.java} (65%) delete mode 100644 bootstrap/fabric/src/main/resources/permissions.yml diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index dac79117361..02f24bba569 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -19,6 +19,9 @@ dependencies { // 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. 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 index a6ee77f4165..180197f2dd0 100644 --- 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 @@ -25,12 +25,14 @@ 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; @@ -69,6 +71,16 @@ 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) { 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 index 792e167889b..1e9543ff869 100644 --- 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 @@ -29,6 +29,7 @@ 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; @@ -55,29 +56,21 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.FileOutputStream; 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 connector; + private GeyserImpl geyser; private ModContainer mod; private Path dataFolder; private MinecraftServer server; - /** - * Commands that don't require any permission level to ran - */ - private List playerCommands; - private final List commandExecutors = new ArrayList<>(); - private GeyserCommandManager geyserCommandManager; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; @@ -111,8 +104,6 @@ public void onEnable() { 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); - File permissionsFile = fileOrCopiedFromResource(dataFolder.resolve("permissions.yml").toFile(), "permissions.yml"); - this.playerCommands = Arrays.asList(FileUtils.loadConfig(permissionsFile, GeyserFabricPermissions.class).getCommands()); } catch (IOException ex) { LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); @@ -123,10 +114,14 @@ public void onEnable() { 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); @@ -170,38 +165,37 @@ public void startGeyser(MinecraftServer server) { geyserConfig.loadFloodgate(this, floodgate.orElse(null)); - this.connector = GeyserImpl.load(PlatformType.FABRIC, this); - GeyserImpl.start(); // shrug + GeyserImpl.start(); - this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - this.geyserCommandManager = new GeyserCommandManager(connector); + 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(connector, - (GeyserCommand) connector.commandManager().getCommands().get("help"), !playerCommands.contains("help")); - commandExecutors.add(helpExecutor); + 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 : connector.commandManager().getCommands().entrySet()) { - GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, (GeyserCommand) command.getValue(), - !playerCommands.contains(command.getKey())); - commandExecutors.add(executor); - builder.then(Commands.literal(command.getKey()).executes(executor)); + 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 (connector != null) { - connector.shutdown(); - connector = null; + if (geyser != null) { + geyser.shutdown(); + geyser = null; } if (!reloading) { this.server = null; @@ -267,34 +261,6 @@ public void setReloading(boolean reloading) { this.reloading = reloading; } - private File fileOrCopiedFromResource(File file, String name) throws IOException { - if (!file.exists()) { - //noinspection ResultOfMethodCallIgnored - file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); - InputStream input = getResource(name); - - byte[] bytes = new byte[input.available()]; - - //noinspection ResultOfMethodCallIgnored - input.read(bytes); - - for(char c : new String(bytes).toCharArray()) { - fos.write(c); - } - - fos.flush(); - input.close(); - fos.close(); - } - - return file; - } - - public List getCommandExecutors() { - return commandExecutors; - } - public static GeyserFabricMod getInstance() { return instance; } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java similarity index 65% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java index a625f6d1fe7..1ea69cbe225 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPermissions.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java @@ -25,26 +25,19 @@ package org.geysermc.geyser.platform.fabric; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +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; -/** - * A class outline of the permissions.yml file - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserFabricPermissions { - - /** - * The minimum permission level a command source must have in order for it to run commands that are restricted - */ - @JsonIgnore - public static final int RESTRICTED_MIN_LEVEL = 2; - - @JsonProperty("commands") - private String[] commands; +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())); + } + } - public String[] getCommands() { - return this.commands; + private GeyserFabricUpdateListener() { } } 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 index 0bb171e6ba3..5973e04f178 100644 --- 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 @@ -25,12 +25,13 @@ 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.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.text.ChatColor; import javax.annotation.Nonnull; @@ -57,22 +58,23 @@ public void sendMessage(@Nonnull String message) { } } + @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 s) { - // Mostly copied from fabric's world manager since the method there takes a GeyserSession - - // Workaround for our commands because fabric doesn't have native permissions - for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { - if (executor.getCommand().permission().equals(s)) { - return executor.canRun(source); - } - } - - return false; + 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 index f691cd49e1c..7600e41361f 100644 --- 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 @@ -27,12 +27,12 @@ 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.platform.fabric.GeyserFabricPermissions; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; @@ -40,27 +40,15 @@ import java.util.Collections; public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { - private final GeyserCommand command; - /** - * Whether the command requires an OP permission level of 2 or greater - */ - private final boolean requiresPermission; - public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, boolean requiresPermission) { + public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) { super(connector, Collections.singletonMap(command.name(), command)); this.command = command; - this.requiresPermission = requiresPermission; } - /** - * Determine whether or not a command source is allowed to run a given executor. - * - * @param source The command source attempting to run the command - * @return True if the command source is allowed to - */ - public boolean canRun(CommandSourceStack source) { - return !requiresPermission() || source.hasPermission(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL); + public boolean testPermission(CommandSourceStack source) { + return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); } @Override @@ -68,8 +56,8 @@ public int run(CommandContext context) { CommandSourceStack source = (CommandSourceStack) context.getSource(); FabricCommandSender sender = new FabricCommandSender(source); GeyserSession session = getGeyserSession(sender); - if (!canRun(source)) { - sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")); + if (!testPermission(source)) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return 0; } if (this.command.name().equals("reload")) { @@ -83,15 +71,4 @@ public int run(CommandContext context) { command.execute(session, sender, new String[0]); return 0; } - - public GeyserCommand getCommand() { - return command; - } - - /** - * Returns whether the command requires permission level of {@link GeyserFabricPermissions#RESTRICTED_MIN_LEVEL} or higher to be ran - */ - public boolean requiresPermission() { - return requiresPermission; - } } 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 index 0746198f372..eb4f61c6760 100644 --- 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 @@ -29,6 +29,7 @@ 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; @@ -39,8 +40,6 @@ 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.platform.fabric.GeyserFabricMod; -import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.util.BlockEntityUtils; @@ -124,14 +123,8 @@ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boole @Override public boolean hasPermission(GeyserSession session, String permission) { - // Workaround for our commands because fabric doesn't have native permissions - for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) { - if (executor.getCommand().permission().equals(permission)) { - return executor.canRun(getPlayer(session).createCommandSourceStack()); - } - } - - return false; + ServerPlayer player = getPlayer(session); + return Permissions.check(player, permission); } private ServerPlayer getPlayer(GeyserSession session) { diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index ee23dd06dd8..98a4109508b 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,7 @@ "depends": { "fabricloader": ">=0.14.8", "fabric": "*", - "minecraft": ">=1.19" + "minecraft": ">=1.19", + "fabric-permissions-api-v0": "*" } } diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml deleted file mode 100644 index ae20447ed48..00000000000 --- a/bootstrap/fabric/src/main/resources/permissions.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Uncomment any commands that you wish to be run by clients -# Commented commands require an OP permission of 2 or greater -commands: - - help - - advancements - - statistics - - settings - - offhand - - tooltips -# - list -# - reload -# - version -# - dump 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/gradle/libs.versions.toml b/gradle/libs.versions.toml index b16812b6f4f..4e2efe00ba8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,9 @@ 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" } @@ -63,9 +66,9 @@ paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "pap 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 = "1.19.1" } -fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.14.8" } -fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.58.5+1.19.1" } +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" } From ca7799d984abfbdaa9d467f132ed8ec7261d3170 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:26:28 -0400 Subject: [PATCH 297/358] Add core Gradle Adventure change --- core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1ab99990aa7..994325ea038 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } } // Adventure text serialization - implementation(libs.bundles.adventure) + api(libs.bundles.adventure) // Test testImplementation(libs.junit) From e9b99b209814d43573e7c74712ccc165b9d6a1f3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:49:34 -0400 Subject: [PATCH 298/358] Indicate 1.19.40 support; bump Protocol --- README.md | 2 +- .../java/org/geysermc/geyser/network/GameProtocol.java | 8 +++----- gradle/libs.versions.toml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d91672eb4b4..a28ba8eb023 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.30/1.19.31 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.40 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 5870b92b64c..7bba6bb8962 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -48,9 +48,7 @@ public final class GameProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v554.V554_CODEC.toBuilder() - .minecraftVersion("1.19.31") - .build(); + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v557.V557_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -73,10 +71,10 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v545.V545_CODEC.toBuilder() .minecraftVersion("1.19.21/1.19.22") .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + 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); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4e2efe00ba8..3724836d9df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ 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.13-20221018.122404-1" +protocol = "2.9.14-20221025.193624-1" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "9f78bd5" From 0d3b77e56793d2e5c2012ff1f5d8c6253a43db5c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:57:40 -0400 Subject: [PATCH 299/358] Fix chunk translation errors in online mode --- .../geyser/translator/protocol/java/JavaLoginTranslator.java | 4 ++++ 1 file changed, 4 insertions(+) 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 60382e17aea..d48d7843995 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 @@ -105,6 +105,10 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { // It is now safe to send these packets session.getUpstream().sendPostStartGamePackets(); + } else if (!session.isSpawned()) { + // Called for online mode, being presented with a GUI before logging ing + session.setDimensionType(dimensions.get(newDimension)); + ChunkUtils.loadDimension(session); } AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); From cc82f4b871bbdcd2f68a71ceac395d4be770f177 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 29 Oct 2022 20:08:41 -0400 Subject: [PATCH 300/358] Fix bounding box for 1.19.40 after death + respawn (#3374) Co-authored-by: onebeastchris <105284508+onebeastchris@users.noreply.github.com> --- .../geyser/entity/type/player/SessionPlayerEntity.java | 10 ++++++++++ .../bedrock/entity/player/BedrockActionTranslator.java | 3 +++ 2 files changed, 13 insertions(+) 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 7a5d3497368..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 @@ -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/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 72f064a55d4..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 @@ -75,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)) { From 90c9d5b9c4df64d9f4f9666f646cc7c43bc14f74 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:28:48 -0400 Subject: [PATCH 301/358] Prevent large Object[] allocations in command list translation --- .../geyser/translator/protocol/java/JavaCommandsTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3fa43c78896..8f4e46454da 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 @@ -137,7 +137,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]); } } From 87f8cf9ceaa506c37f6e6f84b68c1056e629a881 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:31:46 -0400 Subject: [PATCH 302/358] Update mappings to fix bow sounds (#3375) Fixes #3311 --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index f1c9c2fbba0..10baa9a45de 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit f1c9c2fbba0e102dc4f8c96dd9485f7ec9768174 +Subproject commit 10baa9a45de074afa643e8477bd5a4e72ecfa563 From 592b48dbf5fa807cf09e04909a0054a1daaf9567 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:59:44 -0400 Subject: [PATCH 303/358] Simplify IP censoring in dumps (#3330) --- .../bungeecord/GeyserBungeeDumpInfo.java | 15 +++-- bootstrap/fabric/build.gradle.kts | 3 - .../platform/fabric/GeyserFabricDumpInfo.java | 57 +++++++--------- .../platform/fabric/GeyserFabricMod.java | 5 +- .../geyser/platform/fabric/ModInfo.java | 66 ------------------- .../platform/spigot/GeyserSpigotDumpInfo.java | 9 +-- .../platform/sponge/GeyserSpongeDumpInfo.java | 9 +-- .../velocity/GeyserVelocityDumpInfo.java | 9 +-- .../geyser/dump/BootstrapDumpInfo.java | 3 + .../geyser/text/AsteriskSerializer.java | 12 ++-- 10 files changed, 56 insertions(+), 132 deletions(-) delete mode 100644 bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java 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 2278d99d9b8..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 @@ -29,7 +29,6 @@ import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.geysermc.geyser.dump.BootstrapDumpInfo; -import org.geysermc.geyser.text.AsteriskSerializer; import java.util.ArrayList; import java.util.Collections; @@ -52,15 +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 = listener.getHost().getHostString(); - if (!AsteriskSerializer.showSensitive && !(hostname.equals("") || hostname.equals("0.0.0.0"))) { - 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/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 02f24bba569..743b75a2697 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -1,8 +1,5 @@ plugins { id("fabric-loom") version "1.0-SNAPSHOT" - id("maven-publish") - id("com.github.johnrengelman.shadow") - id("java") } java { 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 index 2db6a372925..ee986ee6216 100644 --- 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 @@ -25,65 +25,58 @@ 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; -@SuppressWarnings("unused") // The way that the dump renders makes them used +@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) { - super(); - for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) { - if (modContainer.getMetadata().getId().equals("fabricloader")) { - this.platformVersion = modContainer.getMetadata().getVersion().getFriendlyString(); - break; - } - } + FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod -> + this.platformVersion = mod.getMetadata().getVersion().getFriendlyString()); + this.environmentType = FabricLoader.getInstance().getEnvironmentType(); - if (AsteriskSerializer.showSensitive || (server.getLocalIp() == null || server.getLocalIp().equals("") || server.getLocalIp().equals("0.0.0.0"))) { - this.serverIP = server.getLocalIp(); - } else { - this.serverIP = "***"; - } + this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp(); this.serverPort = server.getPort(); this.mods = new ArrayList<>(); for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { - this.mods.add(new ModInfo(mod)); + 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())) + ); } } - public String getPlatformVersion() { - return platformVersion; - } - - public EnvType getEnvironmentType() { - return environmentType; - } - - public String getServerIP() { - return this.serverIP; - } - - public int getServerPort() { - return this.serverPort; - } - - public List getMods() { - return this.mods; + @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/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index 1e9543ff869..e5ff4b57731 100644 --- 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 @@ -246,7 +246,10 @@ public String getMinecraftServerVersion() { @Override public InputStream getResourceOrNull(String resource) { // We need to handle this differently, because Fabric shares the classloader across multiple mods - Path path = this.mod.getPath(resource); + Path path = this.mod.findPath(resource).orElse(null); + if (path == null) { + return null; + } try { return path.getFileSystem() diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java deleted file mode 100644 index b9137c43880..00000000000 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModInfo.java +++ /dev/null @@ -1,66 +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.fabric; - -import net.fabricmc.loader.api.ModContainer; - -import java.util.ArrayList; -import java.util.List; - -/** - * A wrapper for Fabric mod information to be presented in a Geyser dump - */ -public class ModInfo { - - private final String name; - private final String id; - private final String version; - private final List authors; - - public ModInfo(ModContainer mod) { - this.name = mod.getMetadata().getName(); - this.id = mod.getMetadata().getId(); - this.authors = new ArrayList<>(); - mod.getMetadata().getAuthors().forEach((person) -> this.authors.add(person.getName())); - this.version = mod.getMetadata().getVersion().getFriendlyString(); - } - - public String getName() { - return this.name; - } - - public String getId() { - return this.id; - } - - public String getVersion() { - return this.version; - } - - public List getAuthors() { - return this.authors; - } -} 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 92c2fe16b4e..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 @@ -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,12 +53,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { this.platformVersion = Bukkit.getVersion(); this.platformAPIVersion = Bukkit.getBukkitVersion(); this.onlineMode = Bukkit.getOnlineMode(); - String ip = Bukkit.getIp(); - if (AsteriskSerializer.showSensitive || (ip.equals("") || ip.equals("0.0.0.0"))) { - this.serverIP = ip; - } else { - this.serverIP = "***"; - } + this.serverIP = Bukkit.getIp(); this.serverPort = Bukkit.getPort(); this.plugins = new ArrayList<>(); 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 fc1bff3efe3..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 @@ -45,6 +45,8 @@ 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; @@ -56,12 +58,7 @@ public class GeyserSpongeDumpInfo extends BootstrapDumpInfo { this.platformVersion = platformMeta.version().getQualifier(); this.onlineMode = Sponge.server().isOnlineModeEnabled(); Optional socketAddress = Sponge.server().boundAddress(); - String hostString = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown"); - if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0") || hostString.equals("unknown"))) { - this.serverIP = hostString; - } else { - this.serverIP = "***"; - } + this.serverIP = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown"); this.serverPort = socketAddress.map(InetSocketAddress::getPort).orElse(-1); this.plugins = new ArrayList<>(); 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 b2765d3b22b..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 @@ -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,12 +53,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { this.platformVersion = proxy.getVersion().getVersion(); this.platformVendor = proxy.getVersion().getVendor(); this.onlineMode = proxy.getConfiguration().isOnlineMode(); - String hostString = proxy.getBoundAddress().getHostString(); - if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0"))) { - this.serverIP = hostString; - } else { - this.serverIP = "***"; - } + this.serverIP = proxy.getBoundAddress().getHostString(); this.serverPort = proxy.getBoundAddress().getPort(); this.plugins = new ArrayList<>(); 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/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; } } From b1d832ddedf724165f9a2c6c4afad9f020c33550 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 9 Nov 2022 11:12:12 -0500 Subject: [PATCH 304/358] Replace ; with : in motd/submotd (#3389) --- .../network/ConnectorServerEventHandler.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 c9a3201c1f1..49fe6c42ddb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -125,13 +125,9 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { 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()) { @@ -160,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()) { From 7d84928627e94aab7b91d1aabc757371f48c8339 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:10:08 -0500 Subject: [PATCH 305/358] (Should) remove unneeded messages about incorrect chunk heights --- .../translator/protocol/java/JavaLoginTranslator.java | 8 ++------ .../protocol/java/JavaRespawnTranslator.java | 3 +++ .../java/org/geysermc/geyser/util/ChunkUtils.java | 11 +++-------- .../java/org/geysermc/geyser/util/DimensionUtils.java | 8 ++++++-- 4 files changed, 14 insertions(+), 16 deletions(-) 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 d48d7843995..ea0de7851ed 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 @@ -99,16 +99,10 @@ 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); - session.setDimensionType(dimensions.get(newDimension)); - ChunkUtils.loadDimension(session); session.connect(); // It is now safe to send these packets session.getUpstream().sendPostStartGamePackets(); - } else if (!session.isSpawned()) { - // Called for online mode, being presented with a GUI before logging ing - session.setDimensionType(dimensions.get(newDimension)); - ChunkUtils.loadDimension(session); } AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); @@ -151,5 +145,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { // If the player is spawning into the "fake" nether, send them some fog session.sendFog("minecraft:fog_hell"); } + + ChunkUtils.loadDimension(session); } } 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 ed429b87dbf..0f02256d0ea 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 @@ -36,6 +36,7 @@ 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.util.ChunkUtils; import org.geysermc.geyser.util.DimensionUtils; @Translator(packet = ClientboundRespawnPacket.class) @@ -92,6 +93,8 @@ public void translate(GeyserSession session, ClientboundRespawnPacket packet) { } session.setWorldName(packet.getWorldName()); DimensionUtils.switchDimension(session, newDimension); + + ChunkUtils.loadDimension(session); } } } 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 679d7a6584b..501087f7881 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -222,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(); @@ -233,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 59f5ed55dc9..5989b4e9da8 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.packet.MobEffectPacket; import com.nukkitx.protocol.bedrock.packet.StopSoundPacket; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.session.GeyserSession; import java.util.Set; @@ -94,8 +95,11 @@ public static void switchDimension(GeyserSession session, String javaDimension) changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); session.setDimension(javaDimension); - session.setDimensionType(session.getDimensions().get(javaDimension)); - ChunkUtils.loadDimension(session); + 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; + }); player.setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); From 886d7e5b4b44e84a7cf91139ac9305b2180c8c88 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 12 Nov 2022 10:28:53 -0500 Subject: [PATCH 306/358] Fix crashes when joining a server in the Nether --- .../geyser/session/GeyserSession.java | 2 +- .../protocol/java/JavaLoginTranslator.java | 1 + .../geysermc/geyser/util/DimensionUtils.java | 24 +++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) 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 67aedec153d..5fd01f541bd 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1420,7 +1420,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); 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 ea0de7851ed..a0d418324ed 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 @@ -99,6 +99,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 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 5989b4e9da8..4d1fa84b3d0 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -95,11 +95,7 @@ public static void switchDimension(GeyserSession session, String javaDimension) changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); session.setDimension(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; - }); + setBedrockDimension(session, javaDimension); player.setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); @@ -137,6 +133,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 * From 5ddb0ad90a7098ed5822e630b51717f6ebb9592d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:12:46 -0500 Subject: [PATCH 307/358] Allow virtual inventories to be opened when player at world height commit c53bb38a47d1a48f0b5a72059e81c4354c2b8e90 Author: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon Nov 14 15:12:29 2022 -0500 Final touch commit f9ff9553eda7c80620a8e6f63e14f01adb39ac8b Merge: b57109ddf 886d7e5b4 Author: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon Nov 14 14:54:28 2022 -0500 Merge branch 'master' of https://github.com/GeyserMC/Geyser into pull/3281 commit b57109ddf7dabe77ca97f36de48d7266eaec08a1 Author: Kevin Ludwig Date: Mon Sep 12 12:23:36 2022 +0200 Revert use entities for single chest inventories commit fda66e83b90984505bdc3d87cf41e986dd10424b Author: Kevin Ludwig Date: Sat Sep 10 11:49:40 2022 +0200 Use entities for single chest inventories, check if a block for server-side opened inventories can be placed either above or below, otherwise, close the inventory (same logic as with inventory translator found) --- .../holder/BlockInventoryHolder.java | 28 +++++++++++++++---- .../inventory/holder/InventoryHolder.java | 2 +- .../AbstractBlockInventoryTranslator.java | 4 +-- .../inventory/InventoryTranslator.java | 2 +- .../inventory/LecternInventoryTranslator.java | 3 +- .../MerchantInventoryTranslator.java | 4 ++- .../inventory/PlayerInventoryTranslator.java | 3 +- .../chest/DoubleChestInventoryTranslator.java | 27 ++++++++++++++---- .../chest/SingleChestInventoryTranslator.java | 4 +-- .../AbstractHorseInventoryTranslator.java | 3 +- .../geysermc/geyser/util/InventoryUtils.java | 4 +-- 11 files changed, 61 insertions(+), 23 deletions(-) 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 379eb2566e3..eb15fa47759 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 @@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; @@ -49,6 +50,8 @@ * This class will attempt to use a real block first, if possible. */ public class BlockInventoryHolder extends InventoryHolder { + private static final int FAKE_BLOCK_DISTANCE = 1; + /** * The default Java block ID to translate as a fake block */ @@ -70,7 +73,7 @@ public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerT } @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 +86,26 @@ 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; + } + } + + // 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 false; + } + if (position.getY() >= maxY) { + position = flatPlayerPosition.sub(0, 4, 0); + if (position.getY() >= maxY) { + return false; } } - // Otherwise, time to conjure up a fake block! - Vector3i position = session.getPlayerEntity().getPosition().toInt(); - position = position.add(Vector3i.UP); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); @@ -99,6 +115,8 @@ public void prepareInventory(InventoryTranslator translator, GeyserSession sessi inventory.setHolderPosition(position); setCustomName(session, position, inventory, defaultJavaBlockState); + + return true; } /** 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/translator/inventory/AbstractBlockInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java index c1fabcf0f54..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 @@ -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/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 8c7ee1c8018..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); 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 7b2f861f57a..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 @@ -55,7 +55,8 @@ public LecternInventoryTranslator() { } @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/MerchantInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java index 5e9c99ae95c..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 @@ -94,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(); @@ -117,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 ee7d6a7c6df..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 @@ -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/chest/DoubleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java index 0dd8553fdb3..5bf96fd39db 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.level.BedrockDimension; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.DoubleChestValue; -import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator; +import org.geysermc.geyser.registry.BlockRegistries; 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,25 @@ public void prepareInventory(GeyserSession session, Inventory inventory) { dataPacket.setData(tag.build()); dataPacket.setBlockPosition(session.getLastInteractionBlockPosition()); session.sendUpstreamPacket(dataPacket); - return; + + return true; + } + } + + // 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 position = session.getPlayerEntity().getPosition().toInt().add(0, 5, 0); + if (position.getY() < minY) { + return false; + } + if (position.getY() >= maxY) { + position = session.getPlayerEntity().getPosition().toInt().sub(0, 5, 0); + if (position.getY() >= maxY) { + return false; } } - Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); int bedrockBlockId = session.getBlockMappings().getBedrockBlockId(defaultJavaBlockState); @@ -125,6 +140,8 @@ public void prepareInventory(GeyserSession session, Inventory inventory) { session.sendUpstreamPacket(dataPacket); inventory.setHolderPosition(position); + + return true; } @Override 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 41e7bfb9fc6..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 @@ -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/horse/AbstractHorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java index 0ad6ba13796..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 @@ -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/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 56da67bec2d..ce88bc69ca3 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -85,8 +85,7 @@ 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(); @@ -103,7 +102,6 @@ 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); } } From 3338f5c707bd82dc54b55895276ef90801233e9f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:50:58 -0500 Subject: [PATCH 308/358] Remove duplicate inventory logic --- .../holder/BlockInventoryHolder.java | 21 +++-------- .../chest/DoubleChestInventoryTranslator.java | 19 +++------- .../geysermc/geyser/util/InventoryUtils.java | 36 ++++++++++++------- 3 files changed, 33 insertions(+), 43 deletions(-) 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 eb15fa47759..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; @@ -35,11 +34,11 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.InventoryUtils; import java.util.Collections; import java.util.HashSet; @@ -50,8 +49,6 @@ * This class will attempt to use a real block first, if possible. */ public class BlockInventoryHolder extends InventoryHolder { - private static final int FAKE_BLOCK_DISTANCE = 1; - /** * The default Java block ID to translate as a fake block */ @@ -66,7 +63,7 @@ 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)); } @@ -91,20 +88,10 @@ public boolean prepareInventory(InventoryTranslator translator, GeyserSession se } } - // 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) { + Vector3i position = InventoryUtils.findAvailableWorldSpace(session); + if (position == null) { return false; } - if (position.getY() >= maxY) { - position = flatPlayerPosition.sub(0, 4, 0); - if (position.getY() >= maxY) { - return false; - } - } UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); 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 5bf96fd39db..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,12 +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.level.BedrockDimension; -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; @@ -82,19 +82,10 @@ public boolean prepareInventory(GeyserSession session, Inventory inventory) { } } - // 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 position = session.getPlayerEntity().getPosition().toInt().add(0, 5, 0); - if (position.getY() < minY) { + Vector3i position = InventoryUtils.findAvailableWorldSpace(session); + if (position == null) { return false; } - if (position.getY() >= maxY) { - position = session.getPlayerEntity().getPosition().toInt().sub(0, 5, 0); - if (position.getY() >= maxY) { - return false; - } - } Vector3i pairPosition = position.add(Vector3i.UNIT_X); int bedrockBlockId = session.getBlockMappings().getBedrockBlockId(defaultJavaBlockState); 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 ce88bc69ca3..cb426963c50 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; @@ -46,6 +47,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; @@ -134,6 +136,28 @@ public static Inventory getInventory(GeyserSession session, int javaId) { } } + /** + * 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); @@ -148,18 +172,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. */ From 37931e49968ca774fcc4cc21fb88db14072ae430 Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:36:18 +0100 Subject: [PATCH 309/358] Fix potion recipes not working on pre-1.12 servers (#3408) --- .../java/org/geysermc/geyser/session/GeyserSession.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 5fd01f541bd..51648f8a25e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -629,6 +629,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); From 7171ade0bd2da19fc70e3c36005371c50048ab2a Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:04:22 -0800 Subject: [PATCH 310/358] Prevent double placement for custom block items (#3399) --- .../populator/CustomItemRegistryPopulator.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 94e04e97265..0e40f9c4384 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -124,6 +124,10 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, G computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder); } + if (mapping.getFirstBlockRuntimeId() != null) { + computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); + } + computeRenderOffsets(false, customItemData, componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); @@ -260,6 +264,15 @@ private static void computeArmorProperties(String armorType, int protectionValue } } + 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 computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { if (isHat) { componentBuilder.remove("minecraft:render_offsets"); From 759db72536e74eddd9b864a7ed6a6d6b7dda8e7b Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:13:59 -0500 Subject: [PATCH 311/358] Compiling for 1.19.3 --- .../type/living/monster/EndermanEntity.java | 15 ++- .../geyser/session/GeyserSession.java | 6 +- .../geysermc/geyser/text/ChatTypeEntry.java | 2 +- .../java/JavaPlayerChatTranslator.java | 2 +- .../JavaPlayerInfoRemoveTranslator.java | 65 +++++++++ .../player/JavaPlayerInfoTranslator.java | 125 ------------------ .../JavaPlayerInfoUpdateTranslator.java | 113 ++++++++++++++++ .../java/level/JavaExplodeTranslator.java | 7 +- gradle/libs.versions.toml | 4 +- 9 files changed, 201 insertions(+), 138 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java 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..593cd945708 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 = entityMetadata.getValue().getAsInt(); + } else { + bedrockBlockId = session.getBlockMappings().getBedrockAirId(); + } + + dirtyMetadata.put(EntityData.CARRIED_BLOCK, bedrockBlockId); } /** 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 51648f8a25e..f4d7a174ac0 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1360,18 +1360,20 @@ public String locale() { return clientData.getLanguageCode(); } + // TODO: 1.19.3 int offest and ack'd messages BitSet??? + /** * 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) { 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/translator/protocol/java/JavaPlayerChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java index 143fa16a99b..4c2d51cb885 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 @@ -51,7 +51,7 @@ public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) textPacket.setType(TextPacket.Type.CHAT); textPacket.setNeedsTranslation(false); - Component message = packet.getUnsignedContent() == null ? packet.getMessageDecorated() : packet.getUnsignedContent(); + Component message = packet.getUnsignedContent() == null ? Component.text(packet.getContent()) : packet.getUnsignedContent(); TextDecoration decoration = session.getChatTypes().get(packet.getChatType()); if (decoration != null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java new file mode 100644 index 00000000000..a47683bd2e9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java @@ -0,0 +1,65 @@ +/* + * 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.protocol.packet.ingame.clientbound.ClientboundPlayerInfoRemovePacket; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +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 java.util.UUID; + +@Translator(packet = ClientboundPlayerInfoRemovePacket.class) +public class JavaPlayerInfoRemoveTranslator 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)); + } + } + + if (!translate.getEntries().isEmpty()) { + 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 1cefb97313c..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.skin.SkinManager; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - -@Translator(packet = ClientboundPlayerInfoPacket.class) -public class JavaPlayerInfoTranslator extends PacketTranslator { - @Override - public void translate(GeyserSession session, ClientboundPlayerInfoPacket packet) { - if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) - return; - - PlayerListPacket translate = new PlayerListPacket(); - translate.setAction(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Action.ADD : PlayerListPacket.Action.REMOVE); - - for (PlayerListEntry entry : packet.getEntries()) { - switch (packet.getAction()) { - case ADD_PLAYER -> { - GameProfile profile = entry.getProfile(); - PlayerEntity playerEntity; - boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); - - if (self) { - // Entity is ourself - playerEntity = session.getPlayerEntity(); - } else { - playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); - } - - GameProfile.Property textures = profile.getProperty("textures"); - String texturesProperty = textures == null ? null : textures.getValue(); - - if (playerEntity == null) { - // It's a new player - playerEntity = new PlayerEntity( - session, - -1, - session.getEntityCache().getNextEntityId().incrementAndGet(), - profile.getId(), - Vector3f.ZERO, - Vector3f.ZERO, - 0, 0, 0, - profile.getName(), - texturesProperty - ); - - session.getEntityCache().addPlayerEntity(playerEntity); - } else { - playerEntity.setUsername(profile.getName()); - playerEntity.setTexturesProperty(texturesProperty); - } - - playerEntity.setPlayerList(true); - - // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape - // But we need to send other player's entries so they show up in the player list - // without processing their skin information - that'll be processed when they spawn in - if (self) { - SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> - GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); - } else { - playerEntity.setValid(true); - PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - - translate.getEntries().add(playerListEntry); - } - } - case REMOVE_PLAYER -> { - // As the player entity is no longer present, we can remove the entry - PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); - if (entity != null) { - // Just remove the entity's player list status - // Don't despawn the entity - the Java server will also take care of that. - entity.setPlayerList(false); - } - if (entity == session.getPlayerEntity()) { - // If removing ourself we use our AuthData UUID - translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid())); - } else { - translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); - } - } - } - } - - if (!translate.getEntries().isEmpty()) { - session.sendUpstreamPacket(translate); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java new file mode 100644 index 00000000000..24989c7010c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.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.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()) { + for (PlayerListEntryAction action : packet.getActions()) { + if (action != PlayerListEntryAction.ADD_PLAYER) { + continue; + } + + 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/level/JavaExplodeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java index 51c508e578f..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 @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelEventGenericPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; @@ -49,9 +48,9 @@ public void translate(GeyserSession session, ClientboundExplodePacket packet) { LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket(); levelEventPacket.setEventId(2026/*LevelEventType.PARTICLE_BLOCK_EXPLOSION*/); NbtMapBuilder builder = NbtMap.builder(); - builder.putFloat("originX", packet.getX()); - builder.putFloat("originY", packet.getY()); - builder.putFloat("originZ", packet.getZ()); + builder.putFloat("originX", (float) packet.getX()); + builder.putFloat("originY", (float) packet.getY()); + builder.putFloat("originZ", (float) packet.getZ()); builder.putFloat("radius", packet.getRadius()); builder.putInt("size", packet.getExploded().size()); int i = 0; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3724836d9df..3c70ecf4623 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.14-20221025.193624-1" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "9f78bd5" +mcprotocollib = "1.19.3-SNAPSHOT" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" @@ -80,7 +80,7 @@ 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.GeyserMC", name = "MCProtocolLib", version.ref = "mcprotocollib" } +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-v557", version.ref = "protocol" } raknet = { group = "com.nukkitx.network", name = "raknet", version.ref = "raknet" } From 2f56f024777a00627395241211bd3934668d4b09 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 22 Nov 2022 18:58:34 -0500 Subject: [PATCH 312/358] Recipe fix thing --- .../geyser/registry/populator/RecipeRegistryPopulator.java | 2 -- 1 file changed, 2 deletions(-) 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 920ada5fb43..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 @@ -82,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 From b35667d187bbfaff3841b7f18ec1ed085de74a0c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 22 Nov 2022 19:02:01 -0500 Subject: [PATCH 313/358] Fix mistake on Enderman carried block updater --- .../geyser/entity/type/living/monster/EndermanEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 593cd945708..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 @@ -48,7 +48,7 @@ public EndermanEntity(GeyserSession session, int entityId, long geyserId, UUID u public void setCarriedBlock(EntityMetadata entityMetadata) { int bedrockBlockId; if (entityMetadata.getValue().isPresent()) { - bedrockBlockId = entityMetadata.getValue().getAsInt(); + bedrockBlockId = session.getBlockMappings().getBedrockBlockId(entityMetadata.getValue().getAsInt()); } else { bedrockBlockId = session.getBlockMappings().getBedrockAirId(); } From 70a8272bc24149395caae1dcc64a433b65728f3f Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Thu, 24 Nov 2022 09:10:04 +0800 Subject: [PATCH 314/358] Bump jackson-databind to 2.14.0 (#3406) --- build-logic/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index c992a3ca9e8..e21806660f7 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -16,11 +16,11 @@ dependencies { // 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.13.3") + implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") } tasks.withType { kotlinOptions { jvmTarget = "16" } -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3724836d9df..b3f3df9c502 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -jackson = "2.13.4" +jackson = "2.14.0" fastutil = "8.5.2" netty = "4.1.80.Final" guava = "29.0-jre" @@ -95,4 +95,4 @@ 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" ] \ No newline at end of file +jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ] From 1a1837619c9b10f203efd5712f3ba51b607a5849 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 24 Nov 2022 03:33:55 +0100 Subject: [PATCH 315/358] Option to specify the "unusable inventory space" item (#3402) Adds an "unusable-space-block" setting in the config.yml to specify an item to indicate unavailable spaces in a bedrock inventory. If the item is invalid, a barrier block is used & an error gets printed --- .../geyser/configuration/GeyserConfiguration.java | 2 ++ .../configuration/GeyserJacksonConfiguration.java | 3 +++ .../org/geysermc/geyser/util/InventoryUtils.java | 14 +++++++++++++- core/src/main/resources/config.yml | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) 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 109ad32117f..8a366baae15 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -111,6 +111,8 @@ public interface GeyserConfiguration { boolean isNotifyOnNewBedrockUpdate(); + String getUnusableSpaceBlock(); + IMetricsInfo getMetrics(); int getPendingAuthenticationTimeout(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 73e2089637a..229895c3c55 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -154,6 +154,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @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") 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 ce88bc69ca3..4b001901d1b 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -38,6 +38,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; @@ -184,11 +185,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/resources/config.yml b/core/src/main/resources/config.yml index d5b9c755f23..502441560bd 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -183,6 +183,10 @@ log-player-ip-addresses: true # 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/. From f505f132162f6cac0ac6aa451f9c5b123829f822 Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Thu, 24 Nov 2022 21:19:55 +0100 Subject: [PATCH 316/358] Fix issues with sending multiple Bedrock resource packs (#3416) --- .../geyser/network/UpstreamPacketHandler.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) 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 c2a91fd759a..227f0ed5ac3 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -46,9 +46,13 @@ 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); } @@ -161,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: @@ -271,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); @@ -283,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); + } } From 09cce58746fee8a7c9b1889bed099aeb89389eb1 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 24 Nov 2022 18:35:00 -0500 Subject: [PATCH 317/358] use my mappings --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 10baa9a45de..72f927083d8 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 10baa9a45de074afa643e8477bd5a4e72ecfa563 +Subproject commit 72f927083d8e08f1f1c736d7d12095c7eee3bc68 From 7dc2ca35d615a43f4c6e37fd5bd3e01a6b969c63 Mon Sep 17 00:00:00 2001 From: Kevin Ludwig <32491319+valaphee@users.noreply.github.com> Date: Mon, 28 Nov 2022 18:46:07 +0100 Subject: [PATCH 318/358] Fully strip formatting from chat and commands (#3417) --- .../geyser/inventory/AnvilContainer.java | 2 +- .../BedrockCommandRequestTranslator.java | 11 +++++----- .../bedrock/BedrockTextTranslator.java | 16 +------------- .../translator/text/MessageTranslator.java | 22 +++++++++++++++++++ .../chat/MessageTranslatorTest.java | 1 + 5 files changed, 30 insertions(+), 22 deletions(-) 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 141f2b6f20d..471aff8b248 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -76,7 +76,7 @@ public String checkForRename(GeyserSession session, String rename) { String originalName = ItemUtils.getCustomName(getInput().getNbt()); String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale()); - String plainNewName = MessageTranslator.convertToPlainText(rename, session.locale()); + String plainNewName = MessageTranslator.convertToPlainText(rename); if (!plainOriginalName.equals(plainNewName)) { // Strip out formatting since Java Edition does not allow it correctRename = plainNewName; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 3301f7b9f87..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 @@ -29,6 +29,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -38,16 +39,14 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { @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/text/MessageTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java index 10b1bbc5aa5..1b267823a52 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 @@ -201,6 +201,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 * 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")); From 8f968230485f90e3eaa5109344bfd8ac0529b101 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Mon, 28 Nov 2022 20:53:17 -0600 Subject: [PATCH 319/358] Add support for Bedrock 1.19.50 (560) --- README.md | 2 +- .../geysermc/geyser/entity/type/Entity.java | 14 +- .../type/player/SessionPlayerEntity.java | 4 +- .../geysermc/geyser/network/GameProtocol.java | 15 +- .../populator/BlockRegistryPopulator.java | 11 +- .../populator/ItemRegistryPopulator.java | 5 +- .../geyser/session/GeyserSession.java | 185 +- ...BedrockInventoryTransactionTranslator.java | 26 +- .../JavaPlayerCombatKillTranslator.java | 2 +- .../geysermc/geyser/util/DimensionUtils.java | 18 + .../bedrock/block_palette.1_19_0.nbt | Bin 46005 -> 0 bytes .../bedrock/block_palette.1_19_50.nbt | Bin 0 -> 74726 bytes .../bedrock/creative_items.1_19_0.json | 5437 ----------------- ...19_10.json => creative_items.1_19_50.json} | 1378 ++--- .../bedrock/runtime_item_states.1_19_0.json | 4530 -------------- ....json => runtime_item_states.1_19_50.json} | 168 +- gradle/libs.versions.toml | 4 +- 17 files changed, 1006 insertions(+), 10793 deletions(-) delete mode 100644 core/src/main/resources/bedrock/block_palette.1_19_0.nbt create mode 100644 core/src/main/resources/bedrock/block_palette.1_19_50.nbt delete mode 100644 core/src/main/resources/bedrock/creative_items.1_19_0.json rename core/src/main/resources/bedrock/{creative_items.1_19_10.json => creative_items.1_19_50.json} (83%) delete mode 100644 core/src/main/resources/bedrock/runtime_item_states.1_19_0.json rename core/src/main/resources/bedrock/{runtime_item_states.1_19_10.json => runtime_item_states.1_19_50.json} (97%) diff --git a/README.md b/README.md index a28ba8eb023..f5051ed04d6 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.40 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.50 and Minecraft Java 1.19.1/1.19.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. 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 c4046bcf346..dbbdba05acb 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; @@ -353,10 +355,14 @@ public void updateBedrockMetadata() { public void setFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); 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); + // As of 1.19.50, the client does not want the sprinting, sneaking or gliding set on itself + if (!GameProtocol.supports1_19_50(session) || !(this instanceof SessionPlayerEntity sessionPlayer) || sessionPlayer.getSession() != session) { + 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); + } setInvisible((xd & 0x20) == 0x20); } 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 74b95b73c1d..be1eca2c308 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 @@ -116,7 +116,9 @@ public void setPositionManual(Vector3f position) { @Override public void setFlags(ByteEntityMetadata entityMetadata) { super.setFlags(entityMetadata); - session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING)); + + byte flags = entityMetadata.getPrimitiveValue(); + session.setSwimmingInWater((flags & 0x10) == 0x10 && (flags & 0x08) == 0x08); refreshSpeed = true; } diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 7bba6bb8962..d10111fee1b 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -34,6 +34,7 @@ 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; @@ -48,7 +49,7 @@ public final class GameProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v557.V557_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v560.V560_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -61,9 +62,6 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.V527_CODEC.toBuilder() - .minecraftVersion("1.19.0/1.19.2") - .build()); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v534.V534_CODEC.toBuilder() .minecraftVersion("1.19.10/1.19.11") .build()); @@ -74,6 +72,7 @@ public final class GameProtocol { 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); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } @@ -93,14 +92,14 @@ 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(); + } + /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * 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 afc79082a3b..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 @@ -31,6 +31,7 @@ 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; @@ -73,13 +74,9 @@ public static void populate() { private static void registerBedrockBlocks() { BiFunction emptyMapper = (bedrockIdentifier, statesBuilder) -> null; ImmutableMap, BiFunction> blockMappers = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_19_0", Bedrock_v527.V527_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { - if (bedrockIdentifier.equals("minecraft:muddy_mangrove_roots")) { - statesBuilder.remove("pillar_axis"); - } - return null; - }) - .put(ObjectIntPair.of("1_19_20", Bedrock_v544.V544_CODEC.getProtocolVersion()), emptyMapper).build(); + .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; 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 f928361cc82..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 @@ -37,6 +37,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 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; @@ -76,10 +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(); 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 51648f8a25e..0d4eee1dd12 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -58,19 +58,55 @@ import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Session; -import com.github.steveice10.packetlib.event.session.*; +import com.github.steveice10.packetlib.event.session.ConnectedEvent; +import com.github.steveice10.packetlib.event.session.DisconnectedEvent; +import com.github.steveice10.packetlib.event.session.PacketErrorEvent; +import com.github.steveice10.packetlib.event.session.PacketSendingEvent; +import com.github.steveice10.packetlib.event.session.SessionAdapter; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpClientSession; import com.github.steveice10.packetlib.tcp.TcpSession; import com.nukkitx.math.GenericMath; -import com.nukkitx.math.vector.*; +import com.nukkitx.math.vector.Vector2f; +import com.nukkitx.math.vector.Vector2i; +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; -import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.protocol.bedrock.data.Ability; +import com.nukkitx.protocol.bedrock.data.AbilityLayer; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.AuthoritativeMovementMode; +import com.nukkitx.protocol.bedrock.data.ChatRestrictionLevel; +import com.nukkitx.protocol.bedrock.data.GamePublishSetting; +import com.nukkitx.protocol.bedrock.data.GameRuleData; +import com.nukkitx.protocol.bedrock.data.GameType; +import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.SyncedPlayerMovementSettings; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; +import com.nukkitx.protocol.bedrock.packet.BiomeDefinitionListPacket; +import com.nukkitx.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; +import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import com.nukkitx.protocol.bedrock.packet.CreativeContentPacket; +import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; +import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; +import com.nukkitx.protocol.bedrock.packet.ItemComponentPacket; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; +import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket; +import com.nukkitx.protocol.bedrock.packet.SetTimePacket; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import com.nukkitx.protocol.bedrock.packet.TextPacket; +import com.nukkitx.protocol.bedrock.packet.TransferPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAbilitiesPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAdventureSettingsPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.bytes.ByteArrays; @@ -127,7 +163,20 @@ import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; -import org.geysermc.geyser.session.cache.*; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.session.cache.BookEditCache; +import org.geysermc.geyser.session.cache.ChunkCache; +import org.geysermc.geyser.session.cache.EntityCache; +import org.geysermc.geyser.session.cache.EntityEffectCache; +import org.geysermc.geyser.session.cache.FormCache; +import org.geysermc.geyser.session.cache.LodestoneCache; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.session.cache.PreferencesCache; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.session.cache.TagCache; +import org.geysermc.geyser.session.cache.TeleportCache; +import org.geysermc.geyser.session.cache.WorldBorder; +import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; @@ -143,7 +192,14 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -1228,7 +1284,11 @@ private void setSneakingPose(boolean sneaking) { this.pose = Pose.SNEAKING; playerEntity.setBoundingBoxHeight(1.5f); } - playerEntity.setFlag(EntityFlag.SNEAKING, sneaking); + + // As of 1.19.50, the client does not want sneaking set on itself + if (!GameProtocol.supports1_19_50(this)) { + playerEntity.setFlag(EntityFlag.SNEAKING, sneaking); + } } public void setSwimming(boolean swimming) { @@ -1628,76 +1688,40 @@ public void sendAdventureSettings() { boolean spectator = gameMode == GameMode.SPECTATOR; boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; - if (GameProtocol.supports1_19_10(this)) { - UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket(); - adventureSettingsPacket.setNoMvP(false); - adventureSettingsPacket.setNoPvM(false); - adventureSettingsPacket.setImmutableWorld(worldImmutable); - adventureSettingsPacket.setShowNameTags(false); - adventureSettingsPacket.setAutoJump(true); - sendUpstreamPacket(adventureSettingsPacket); - - 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) { @@ -1707,20 +1731,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() { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 436f26cb97a..6992dada46f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -32,12 +32,21 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.nukkitx.math.vector.Vector3d; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.data.inventory.LegacySetItemSlotData; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; @@ -54,7 +63,6 @@ import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -63,7 +71,11 @@ 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.util.*; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InventoryUtils; import java.util.List; import java.util.concurrent.TimeUnit; @@ -464,10 +476,8 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) InteractAction.ATTACK, session.isSneaking()); session.sendDownstreamPacket(attackPacket); - if (GameProtocol.supports1_19_10(session)) { - // Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities - CooldownUtils.sendCooldown(session); - } + // Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities + CooldownUtils.sendCooldown(session); } } break; 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 89be26e4a0e..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 @@ -39,7 +39,7 @@ public class JavaPlayerCombatKillTranslator extends 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 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 0000000000000000000000000000000000000000..8b53566c5c9b8439b71c1fd4de7052cfd6e86590 GIT binary patch literal 74726 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 Date: Mon, 28 Nov 2022 21:00:24 -0600 Subject: [PATCH 320/358] Actually drop 1.19.10 support & remove redundant check --- core/src/main/java/org/geysermc/geyser/entity/type/Entity.java | 2 +- .../main/java/org/geysermc/geyser/network/GameProtocol.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) 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 dbbdba05acb..fff15a49496 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 @@ -356,7 +356,7 @@ public void setFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire // As of 1.19.50, the client does not want the sprinting, sneaking or gliding set on itself - if (!GameProtocol.supports1_19_50(session) || !(this instanceof SessionPlayerEntity sessionPlayer) || sessionPlayer.getSession() != session) { + if (!GameProtocol.supports1_19_50(session) || !(this instanceof SessionPlayerEntity)) { setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index d10111fee1b..7aa64f1f705 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -62,9 +62,6 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v534.V534_CODEC.toBuilder() - .minecraftVersion("1.19.10/1.19.11") - .build()); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.V544_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v545.V545_CODEC.toBuilder() .minecraftVersion("1.19.21/1.19.22") From 49d3254ea925ac550e33f30dd785bbbbe56eaacd Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Mon, 28 Nov 2022 21:29:55 -0600 Subject: [PATCH 321/358] Use new Protocol version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a784c08536c..01b7179f328 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ 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.14-20221129.013131-3" +protocol = "2.9.15-20221129.032348-1" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "9f78bd5" From c7e79299b698e9d4e89f508ee70667921d47792b Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:34:09 -0500 Subject: [PATCH 322/358] Improve 1.19.50 flags (#3422) --- .../java/org/geysermc/geyser/entity/type/Entity.java | 11 ++++------- .../entity/type/player/SessionPlayerEntity.java | 4 +--- .../org/geysermc/geyser/session/GeyserSession.java | 6 +----- gradle/libs.versions.toml | 2 +- 4 files changed, 7 insertions(+), 16 deletions(-) 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 fff15a49496..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 @@ -355,14 +355,11 @@ public void updateBedrockMetadata() { public void setFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire - // As of 1.19.50, the client does not want the sprinting, sneaking or gliding set on itself - if (!GameProtocol.supports1_19_50(session) || !(this instanceof SessionPlayerEntity)) { - setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); - setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); + 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); - } + // Swimming is ignored here and instead we rely on the pose + setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); setInvisible((xd & 0x20) == 0x20); } 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 be1eca2c308..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 @@ -116,9 +116,7 @@ public void setPositionManual(Vector3f position) { @Override public void setFlags(ByteEntityMetadata entityMetadata) { super.setFlags(entityMetadata); - - byte flags = entityMetadata.getPrimitiveValue(); - session.setSwimmingInWater((flags & 0x10) == 0x10 && (flags & 0x08) == 0x08); + session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING)); refreshSpeed = true; } 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 0d4eee1dd12..17a609fb710 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1284,11 +1284,7 @@ private void setSneakingPose(boolean sneaking) { this.pose = Pose.SNEAKING; playerEntity.setBoundingBoxHeight(1.5f); } - - // As of 1.19.50, the client does not want sneaking set on itself - if (!GameProtocol.supports1_19_50(this)) { - playerEntity.setFlag(EntityFlag.SNEAKING, sneaking); - } + playerEntity.setFlag(EntityFlag.SNEAKING, sneaking); } public void setSwimming(boolean swimming) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01b7179f328..5218c3e8a20 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ 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.032348-1" +protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "9f78bd5" From c6e417a6af869cb49ca92503d6163cdd439df2ef Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 29 Nov 2022 23:59:01 -0500 Subject: [PATCH 323/358] Possibly fix #3421 --- .../geysermc/geyser/network/GameProtocol.java | 2 -- .../geysermc/geyser/util/DimensionUtils.java | 25 ++++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 7aa64f1f705..e85dc689d77 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -28,8 +28,6 @@ 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; 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 edcbeefa7fd..887f42a8e82 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -123,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); @@ -137,18 +150,6 @@ public static void switchDimension(GeyserSession session, String javaDimension) session.removeFog("minecraft:fog_hell"); } } - - // 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)) { - 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); - } } public static void setBedrockDimension(GeyserSession session, String javaDimension) { From 8c70ac48d509ee10963d6e8510555a2e662edbe9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 30 Nov 2022 12:09:21 -0500 Subject: [PATCH 324/358] Fix maps in 1.19.50 Fixes #3427 --- .../protocol/java/level/JavaMapItemDataTranslator.java | 2 ++ 1 file changed, 2 insertions(+) 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 4685cf115fe..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 @@ -51,6 +51,8 @@ public void translate(GeyserSession session, ClientboundMapItemDataPacket packet 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) { From 02208a5aed52af94e95859a27337cd1fb959787c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 30 Nov 2022 16:05:35 -0500 Subject: [PATCH 325/358] Fix anvil usage in 1.19.50 --- .../translator/inventory/AnvilInventoryTranslator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 956fdeae025..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 @@ -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); From 1616b7740c252e147aa0ee96d68073d9fb34d438 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 1 Dec 2022 22:05:12 -0500 Subject: [PATCH 326/358] Bump mappings --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 72f927083d8..19094e36080 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 72f927083d8e08f1f1c736d7d12095c7eee3bc68 +Subproject commit 19094e36080d78212534f5f0d073fb5d3f68a89f From 58eede37c0ef2dac2b2c1c1a8671993dced9e6c5 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Dec 2022 00:28:24 -0500 Subject: [PATCH 327/358] Drop anything below 1.19.50 --- .../geysermc/geyser/network/GameProtocol.java | 18 - .../populator/BlockRegistryPopulator.java | 3 - .../populator/ItemRegistryPopulator.java | 1 - .../BedrockRequestAbilityTranslator.java | 5 - .../geysermc/geyser/util/DimensionUtils.java | 18 +- .../bedrock/block_palette.1_19_20.nbt | Bin 55132 -> 0 bytes .../bedrock/creative_items.1_19_20.json | 5440 ----------------- .../bedrock/runtime_item_states.1_19_20.json | 4530 -------------- 8 files changed, 8 insertions(+), 10007 deletions(-) delete mode 100644 core/src/main/resources/bedrock/block_palette.1_19_20.nbt delete mode 100644 core/src/main/resources/bedrock/creative_items.1_19_20.json delete mode 100644 core/src/main/resources/bedrock/runtime_item_states.1_19_20.json diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index e85dc689d77..56159cfa8d8 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -60,14 +60,6 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - 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); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } @@ -85,16 +77,6 @@ public static BedrockPacketCodec getBedrockCodec(int protocolVersion) { return null; } - /* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */ - - 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(); - } - /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * 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 cbab0399020..72116f548b6 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 @@ -29,8 +29,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode; 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; @@ -74,7 +72,6 @@ public static void populate() { private static void registerBedrockBlocks() { 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(); 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 4b218aa7dd9..645cf17ba4d 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 @@ -77,7 +77,6 @@ private record PaletteVersion(int protocolVersion, Map additiona public static void populate() { Map paletteVersions = new Object2ObjectOpenHashMap<>(); - 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(); 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 index fe8150d406b..44953dfdac2 100644 --- 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 @@ -43,11 +43,6 @@ public class BedrockRequestAbilityTranslator extends PacketTranslatorX5GfPypkF`Ueo`Ex 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

    N!K?VWkIdC++V5CX^Dc;QTi}}9T=lerttP4d#b zb@t-=s{f05Iog>R1Xnsx(kBjuLM1(5y> z0`qrk9IcJ3+X^bauWCFyv*Q|9W3$obUsIWO6GmRVc&~n)wYRmd-o(d};}KuUSvr_< zo?aev3X z@?O();jB+SQW0T>*lBV|#ERayqI6_69C>_wlrJ2B*~T_zyVA)LGk_H(QOpU&q^aPc zgIaaCmZOZhK#J7-|J%$y!aAdFvA{53{F3{;m8ORSjLa;Yd0HZz1AiZcz$hmDM`Awa zn{-Jc@GLf5N>k!DrZJRc<1Di;v;f9x^n#gwRmlZFpeT_!p?pUzZBF&mka*oEPSx^3 z%4?=6**e=DBe_)h1LL%53-I%rtiTCBzA~3~2^a{$*?Ucsrhl{dEK;Yra)wkk6D;f` zTGJY90D-s%PIKUCK4_XQuZ!y-86EpCGadf7nMeO+rrdvH=H-8HrUaJBX=-j)FB5`v zQIKt9Qv^8%>R&&^`QnSxu&Fp5}1Li)>l8YTv9@r0ZJG;P5AP^?cZ9k;c1oTm@6?}pJ zIzgZEZyyB>sSSb4mB%**{(%8@LZt)k{ASHq_V=ML1%3ZzX666fOlaPgjik=}JWRvd z?+W4I@6-2dtjAl;)4`RdKUK2&n$d0piAM?yg}^}O7o^ZzRG4s>o2^<>lpu37+`n>F zuqyr-85+24$wO9Q)v)eIJ?^yZpk^agqM~c~^5}C*Mt3)pwabX8U4Nd__3uP7Z->8O zKwPFCId^Pi0`xyHR#^k9M&`)F7Hs@kBQ5%K94Lx^#YN~G4y%nvc$ur@K>|$EG zAKNdmu)?UQPP2YS zU=&B@Ra9zNLe2ROO+n^5G1=p$??b{bYTsXUWu8g(qoWFqbDVo8GMsqN@FsYKVTl z8lK9|?b-#)Zwz9VH%lp}R{E-1_D8c(moRQtfWn;P2kME5d;EbqxtsOQ1YUdN?gTlp vX!0f2XX*s~rfjqL4L=}#m+!XCdF;?V!=^KE4f37S^XE`*=^ZZ(bcp{2Ge*tJ diff --git a/core/src/main/resources/bedrock/creative_items.1_19_20.json b/core/src/main/resources/bedrock/creative_items.1_19_20.json deleted file mode 100644 index 98d9e007a2e..00000000000 --- a/core/src/main/resources/bedrock/creative_items.1_19_20.json +++ /dev/null @@ -1,5440 +0,0 @@ -{ - "items" : [ - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6073 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6074 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6075 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6076 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6077 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 6078 - }, - { - "id" : "minecraft:mangrove_planks", - "blockRuntimeId" : 949 - }, - { - "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 4852 - }, - { - "id" : "minecraft:warped_planks", - "blockRuntimeId" : 922 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1184 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1185 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1186 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1187 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1188 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1189 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1196 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1191 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1192 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1190 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1193 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1197 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1194 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1195 - }, - { - "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 3932 - }, - { - "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6726 - }, - { - "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 973 - }, - { - "id" : "minecraft:cobbled_deepslate_wall", - "blockRuntimeId" : 8084 - }, - { - "id" : "minecraft:deepslate_tile_wall", - "blockRuntimeId" : 5073 - }, - { - "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 7819 - }, - { - "id" : "minecraft:deepslate_brick_wall", - "blockRuntimeId" : 431 - }, - { - "id" : "minecraft:mud_brick_wall", - "blockRuntimeId" : 732 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7366 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7367 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7368 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7369 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7370 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 7371 - }, - { - "id" : "minecraft:mangrove_fence", - "blockRuntimeId" : 6635 - }, - { - "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 4292 - }, - { - "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 7998 - }, - { - "id" : "minecraft:warped_fence", - "blockRuntimeId" : 5855 - }, - { - "id" : "minecraft:fence_gate", - "blockRuntimeId" : 76 - }, - { - "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 6586 - }, - { - "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 3779 - }, - { - "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5367 - }, - { - "id" : "minecraft:acacia_fence_gate", - "blockRuntimeId" : 7588 - }, - { - "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 4175 - }, - { - "id" : "minecraft:mangrove_fence_gate", - "blockRuntimeId" : 4627 - }, - { - "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 4663 - }, - { - "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 5401 - }, - { - "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 635 - }, - { - "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 3710 - }, - { - "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 4094 - }, - { - "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 273 - }, - { - "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 128 - }, - { - "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 7005 - }, - { - "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 6969 - }, - { - "id" : "minecraft:acacia_stairs", - "blockRuntimeId" : 6202 - }, - { - "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 5065 - }, - { - "id" : "minecraft:mangrove_stairs", - "blockRuntimeId" : 4597 - }, - { - "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 933 - }, - { - "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5885 - }, - { - "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 3589 - }, - { - "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 3629 - }, - { - "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 5352 - }, - { - "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 5548 - }, - { - "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 3539 - }, - { - "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 4152 - }, - { - "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 4393 - }, - { - "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6716 - }, - { - "id" : "minecraft:andesite_stairs", - "blockRuntimeId" : 5310 - }, - { - "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 7030 - }, - { - "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 6532 - }, - { - "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 106 - }, - { - "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6604 - }, - { - "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 6384 - }, - { - "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 4769 - }, - { - "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 7702 - }, - { - "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 7757 - }, - { - "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 7265 - }, - { - "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 7432 - }, - { - "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 206 - }, - { - "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 6282 - }, - { - "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 3720 - }, - { - "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 7021 - }, - { - "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 4299 - }, - { - "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 4479 - }, - { - "id" : "minecraft:cut_copper_stairs", - "blockRuntimeId" : 4606 - }, - { - "id" : "minecraft:exposed_cut_copper_stairs", - "blockRuntimeId" : 4589 - }, - { - "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 4307 - }, - { - "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 353 - }, - { - "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 395 - }, - { - "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 3904 - }, - { - "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 6169 - }, - { - "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 5842 - }, - { - "id" : "minecraft:cobbled_deepslate_stairs", - "blockRuntimeId" : 147 - }, - { - "id" : "minecraft:deepslate_tile_stairs", - "blockRuntimeId" : 4655 - }, - { - "id" : "minecraft:polished_deepslate_stairs", - "blockRuntimeId" : 294 - }, - { - "id" : "minecraft:deepslate_brick_stairs", - "blockRuntimeId" : 7424 - }, - { - "id" : "minecraft:mud_brick_stairs", - "blockRuntimeId" : 5524 - }, - { - "id" : "minecraft:wooden_door" - }, - { - "id" : "minecraft:spruce_door" - }, - { - "id" : "minecraft:birch_door" - }, - { - "id" : "minecraft:jungle_door" - }, - { - "id" : "minecraft:acacia_door" - }, - { - "id" : "minecraft:dark_oak_door" - }, - { - "id" : "minecraft:mangrove_door" - }, - { - "id" : "minecraft:iron_door" - }, - { - "id" : "minecraft:crimson_door" - }, - { - "id" : "minecraft:warped_door" - }, - { - "id" : "minecraft:trapdoor", - "blockRuntimeId" : 229 - }, - { - "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 6554 - }, - { - "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 6652 - }, - { - "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5383 - }, - { - "id" : "minecraft:acacia_trapdoor", - "blockRuntimeId" : 5591 - }, - { - "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 7504 - }, - { - "id" : "minecraft:mangrove_trapdoor", - "blockRuntimeId" : 4487 - }, - { - "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 321 - }, - { - "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 4335 - }, - { - "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 4735 - }, - { - "id" : "minecraft:iron_bars", - "blockRuntimeId" : 4803 - }, - { - "id" : "minecraft:glass", - "blockRuntimeId" : 6166 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1135 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1143 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1142 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1150 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1147 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1149 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1136 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1139 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1140 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1148 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1144 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1138 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1146 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1145 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1137 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 1141 - }, - { - "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 5977 - }, - { - "id" : "minecraft:glass_pane", - "blockRuntimeId" : 5235 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4854 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4862 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4861 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4869 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4866 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4868 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4855 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4858 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4859 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4867 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4863 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4857 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4865 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4864 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4856 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 4860 - }, - { - "id" : "minecraft:ladder", - "blockRuntimeId" : 8264 - }, - { - "id" : "minecraft:scaffolding", - "blockRuntimeId" : 3573 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4272 - }, - { - "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5824 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4275 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5795 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5272 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5273 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5274 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5275 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5276 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 5277 - }, - { - "id" : "minecraft:mangrove_slab", - "blockRuntimeId" : 1151 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4277 - }, - { - "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5822 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4273 - }, - { - "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5825 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5796 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5790 - }, - { - "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5826 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5807 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5812 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5813 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5810 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5811 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5809 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5808 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4276 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4279 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5797 - }, - { - "id" : "minecraft:stone_block_slab3", - "blockRuntimeId" : 5806 - }, - { - "id" : "minecraft:stone_block_slab", - "blockRuntimeId" : 4278 - }, - { - "id" : "minecraft:stone_block_slab4", - "blockRuntimeId" : 5823 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5791 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5792 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5793 - }, - { - "id" : "minecraft:stone_block_slab2", - "blockRuntimeId" : 5794 - }, - { - "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 5902 - }, - { - "id" : "minecraft:warped_slab", - "blockRuntimeId" : 6486 - }, - { - "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 912 - }, - { - "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6020 - }, - { - "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 4194 - }, - { - "id" : "minecraft:cut_copper_slab", - "blockRuntimeId" : 5237 - }, - { - "id" : "minecraft:exposed_cut_copper_slab", - "blockRuntimeId" : 6602 - }, - { - "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 6055 - }, - { - "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5284 - }, - { - "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7817 - }, - { - "id" : "minecraft:waxed_exposed_cut_copper_slab", - "blockRuntimeId" : 249 - }, - { - "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 6547 - }, - { - "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 710 - }, - { - "id" : "minecraft:cobbled_deepslate_slab", - "blockRuntimeId" : 7312 - }, - { - "id" : "minecraft:polished_deepslate_slab", - "blockRuntimeId" : 288 - }, - { - "id" : "minecraft:deepslate_tile_slab", - "blockRuntimeId" : 4293 - }, - { - "id" : "minecraft:deepslate_brick_slab", - "blockRuntimeId" : 3718 - }, - { - "id" : "minecraft:mud_brick_slab", - "blockRuntimeId" : 3912 - }, - { - "id" : "minecraft:brick_block", - "blockRuntimeId" : 4767 - }, - { - "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 7251 - }, - { - "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 4554 - }, - { - "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6353 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6549 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6550 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6551 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6552 - }, - { - "id" : "minecraft:end_bricks", - "blockRuntimeId" : 281 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6089 - }, - { - "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 4682 - }, - { - "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 7216 - }, - { - "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4588 - }, - { - "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 5064 - }, - { - "id" : "minecraft:deepslate_tiles", - "blockRuntimeId" : 4583 - }, - { - "id" : "minecraft:cracked_deepslate_tiles", - "blockRuntimeId" : 4162 - }, - { - "id" : "minecraft:deepslate_bricks", - "blockRuntimeId" : 5466 - }, - { - "id" : "minecraft:cracked_deepslate_bricks", - "blockRuntimeId" : 5366 - }, - { - "id" : "minecraft:chiseled_deepslate", - "blockRuntimeId" : 5236 - }, - { - "id" : "minecraft:cobblestone", - "blockRuntimeId" : 3617 - }, - { - "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 252 - }, - { - "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 6672 - }, - { - "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 4584 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3655 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3656 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3657 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 3658 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6582 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6583 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6584 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6585 - }, - { - "id" : "minecraft:coal_block", - "blockRuntimeId" : 5400 - }, - { - "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 7981 - }, - { - "id" : "minecraft:gold_block", - "blockRuntimeId" : 291 - }, - { - "id" : "minecraft:iron_block", - "blockRuntimeId" : 8263 - }, - { - "id" : "minecraft:copper_block", - "blockRuntimeId" : 4653 - }, - { - "id" : "minecraft:exposed_copper", - "blockRuntimeId" : 595 - }, - { - "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 8248 - }, - { - "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 3555 - }, - { - "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7736 - }, - { - "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 696 - }, - { - "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 709 - }, - { - "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7544 - }, - { - "id" : "minecraft:cut_copper", - "blockRuntimeId" : 4691 - }, - { - "id" : "minecraft:exposed_cut_copper", - "blockRuntimeId" : 6168 - }, - { - "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7199 - }, - { - "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5480 - }, - { - "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7295 - }, - { - "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 3811 - }, - { - "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 4853 - }, - { - "id" : "minecraft:waxed_oxidized_cut_copper", - "blockRuntimeId" : 214 - }, - { - "id" : "minecraft:emerald_block", - "blockRuntimeId" : 1161 - }, - { - "id" : "minecraft:diamond_block", - "blockRuntimeId" : 272 - }, - { - "id" : "minecraft:lapis_block", - "blockRuntimeId" : 4288 - }, - { - "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 8262 - }, - { - "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 5271 - }, - { - "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 363 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3698 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3700 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3699 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 3701 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6087 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6088 - }, - { - "id" : "minecraft:slime", - "blockRuntimeId" : 4235 - }, - { - "id" : "minecraft:honey_block", - "blockRuntimeId" : 894 - }, - { - "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 4478 - }, - { - "id" : "minecraft:hay_block", - "blockRuntimeId" : 697 - }, - { - "id" : "minecraft:bone_block", - "blockRuntimeId" : 4236 - }, - { - "id" : "minecraft:nether_brick", - "blockRuntimeId" : 7274 - }, - { - "id" : "minecraft:red_nether_brick", - "blockRuntimeId" : 146 - }, - { - "id" : "minecraft:netherite_block", - "blockRuntimeId" : 3777 - }, - { - "id" : "minecraft:lodestone", - "blockRuntimeId" : 8261 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3460 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3468 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3467 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3475 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3472 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3474 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3461 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3464 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3465 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3473 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3469 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3463 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3471 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3470 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3462 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 3466 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 951 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 959 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 958 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 966 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 963 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 965 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 952 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 955 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 956 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 964 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 960 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 954 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 962 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 961 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 953 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 957 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6266 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6274 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6273 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6281 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6278 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6280 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6267 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6270 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6271 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6279 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6275 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6269 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6277 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6276 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6268 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 6272 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 662 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 670 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 669 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 677 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 674 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 676 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 663 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 666 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 667 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 675 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 671 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 665 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 673 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 672 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 664 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 668 - }, - { - "id" : "minecraft:clay", - "blockRuntimeId" : 7126 - }, - { - "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 643 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6178 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6186 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6185 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6193 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6190 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6192 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6179 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6182 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6183 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6191 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6187 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6181 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6189 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6188 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6180 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 6184 - }, - { - "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 5575 - }, - { - "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 3533 - }, - { - "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 8255 - }, - { - "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 5836 - }, - { - "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 3549 - }, - { - "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 4169 - }, - { - "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 1153 - }, - { - "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 915 - }, - { - "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 223 - }, - { - "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 6612 - }, - { - "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 5360 - }, - { - "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5473 - }, - { - "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 5467 - }, - { - "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 7013 - }, - { - "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 967 - }, - { - "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 6541 - }, - { - "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7716 - }, - { - "id" : "minecraft:purpur_block", - "blockRuntimeId" : 7718 - }, - { - "id" : "minecraft:packed_mud", - "blockRuntimeId" : 283 - }, - { - "id" : "minecraft:mud_bricks", - "blockRuntimeId" : 6891 - }, - { - "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 4295 - }, - { - "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 5907 - }, - { - "id" : "minecraft:shroomlight", - "blockRuntimeId" : 5063 - }, - { - "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 4191 - }, - { - "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 6351 - }, - { - "id" : "minecraft:basalt", - "blockRuntimeId" : 4351 - }, - { - "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 24 - }, - { - "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 1159 - }, - { - "id" : "minecraft:soul_soil", - "blockRuntimeId" : 5832 - }, - { - "id" : "minecraft:dirt", - "blockRuntimeId" : 5753 - }, - { - "id" : "minecraft:dirt", - "blockRuntimeId" : 5754 - }, - { - "id" : "minecraft:farmland", - "blockRuntimeId" : 3914 - }, - { - "id" : "minecraft:grass", - "blockRuntimeId" : 6977 - }, - { - "id" : "minecraft:grass_path", - "blockRuntimeId" : 8083 - }, - { - "id" : "minecraft:podzol", - "blockRuntimeId" : 4652 - }, - { - "id" : "minecraft:mycelium", - "blockRuntimeId" : 3685 - }, - { - "id" : "minecraft:mud", - "blockRuntimeId" : 6686 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 655 - }, - { - "id" : "minecraft:iron_ore", - "blockRuntimeId" : 4692 - }, - { - "id" : "minecraft:gold_ore", - "blockRuntimeId" : 914 - }, - { - "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 4363 - }, - { - "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 7701 - }, - { - "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 4291 - }, - { - "id" : "minecraft:coal_ore", - "blockRuntimeId" : 4289 - }, - { - "id" : "minecraft:copper_ore", - "blockRuntimeId" : 3556 - }, - { - "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 7349 - }, - { - "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 4503 - }, - { - "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 27 - }, - { - "id" : "minecraft:ancient_debris", - "blockRuntimeId" : 6109 - }, - { - "id" : "minecraft:deepslate_iron_ore", - "blockRuntimeId" : 7275 - }, - { - "id" : "minecraft:deepslate_gold_ore", - "blockRuntimeId" : 6108 - }, - { - "id" : "minecraft:deepslate_diamond_ore", - "blockRuntimeId" : 8040 - }, - { - "id" : "minecraft:deepslate_lapis_ore", - "blockRuntimeId" : 7264 - }, - { - "id" : "minecraft:deepslate_redstone_ore", - "blockRuntimeId" : 6618 - }, - { - "id" : "minecraft:deepslate_emerald_ore", - "blockRuntimeId" : 6352 - }, - { - "id" : "minecraft:deepslate_coal_ore", - "blockRuntimeId" : 7198 - }, - { - "id" : "minecraft:deepslate_copper_ore", - "blockRuntimeId" : 105 - }, - { - "id" : "minecraft:gravel", - "blockRuntimeId" : 8289 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 656 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 658 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 660 - }, - { - "id" : "minecraft:blackstone", - "blockRuntimeId" : 7587 - }, - { - "id" : "minecraft:deepslate", - "blockRuntimeId" : 253 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 657 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 659 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 661 - }, - { - "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 3684 - }, - { - "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 7756 - }, - { - "id" : "minecraft:sand", - "blockRuntimeId" : 4197 - }, - { - "id" : "minecraft:sand", - "blockRuntimeId" : 4198 - }, - { - "id" : "minecraft:cactus", - "blockRuntimeId" : 6988 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6674 - }, - { - "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7545 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6675 - }, - { - "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6290 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6676 - }, - { - "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 5974 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 6677 - }, - { - "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 644 - }, - { - "id" : "minecraft:log2", - "blockRuntimeId" : 3832 - }, - { - "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 5850 - }, - { - "id" : "minecraft:log2", - "blockRuntimeId" : 3833 - }, - { - "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 216 - }, - { - "id" : "minecraft:mangrove_log", - "blockRuntimeId" : 350 - }, - { - "id" : "minecraft:stripped_mangrove_log", - "blockRuntimeId" : 8286 - }, - { - "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 5899 - }, - { - "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 6950 - }, - { - "id" : "minecraft:warped_stem", - "blockRuntimeId" : 6488 - }, - { - "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7402 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3476 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3482 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3477 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3483 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3478 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3484 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3479 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3485 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3480 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3486 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3481 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 3487 - }, - { - "id" : "minecraft:mangrove_wood", - "blockRuntimeId" : 4163 - }, - { - "id" : "minecraft:stripped_mangrove_wood", - "blockRuntimeId" : 4231 - }, - { - "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 4296 - }, - { - "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 6501 - }, - { - "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 5904 - }, - { - "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 5581 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6092 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6093 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6094 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 6095 - }, - { - "id" : "minecraft:leaves2", - "blockRuntimeId" : 4355 - }, - { - "id" : "minecraft:leaves2", - "blockRuntimeId" : 4356 - }, - { - "id" : "minecraft:mangrove_leaves", - "blockRuntimeId" : 6668 - }, - { - "id" : "minecraft:azalea_leaves", - "blockRuntimeId" : 7712 - }, - { - "id" : "minecraft:azalea_leaves_flowered", - "blockRuntimeId" : 6341 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 714 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 715 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 716 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 717 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 718 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 719 - }, - { - "id" : "minecraft:mangrove_propagule", - "blockRuntimeId" : 6978 - }, - { - "id" : "minecraft:bee_nest", - "blockRuntimeId" : 5756 - }, - { - "id" : "minecraft:wheat_seeds" - }, - { - "id" : "minecraft:pumpkin_seeds" - }, - { - "id" : "minecraft:melon_seeds" - }, - { - "id" : "minecraft:beetroot_seeds" - }, - { - "id" : "minecraft:wheat" - }, - { - "id" : "minecraft:beetroot" - }, - { - "id" : "minecraft:potato" - }, - { - "id" : "minecraft:poisonous_potato" - }, - { - "id" : "minecraft:carrot" - }, - { - "id" : "minecraft:golden_carrot" - }, - { - "id" : "minecraft:apple" - }, - { - "id" : "minecraft:golden_apple" - }, - { - "id" : "minecraft:enchanted_golden_apple" - }, - { - "id" : "minecraft:melon_block", - "blockRuntimeId" : 394 - }, - { - "id" : "minecraft:melon_slice" - }, - { - "id" : "minecraft:glistering_melon_slice" - }, - { - "id" : "minecraft:sweet_berries" - }, - { - "id" : "minecraft:glow_berries" - }, - { - "id" : "minecraft:pumpkin", - "blockRuntimeId" : 4579 - }, - { - "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 7380 - }, - { - "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 6687 - }, - { - "id" : "minecraft:honeycomb" - }, - { - "id" : "minecraft:tallgrass", - "blockRuntimeId" : 931 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5457 - }, - { - "id" : "minecraft:tallgrass", - "blockRuntimeId" : 930 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5456 - }, - { - "id" : "minecraft:nether_sprouts" - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6494 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6492 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6493 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6491 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6495 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6499 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6497 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6498 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6496 - }, - { - "id" : "minecraft:coral", - "blockRuntimeId" : 6500 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4618 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4616 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4617 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4615 - }, - { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 4619 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 69 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 67 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 68 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 66 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 70 - }, - { - "id" : "minecraft:kelp" - }, - { - "id" : "minecraft:seagrass", - "blockRuntimeId" : 246 - }, - { - "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 7575 - }, - { - "id" : "minecraft:warped_roots", - "blockRuntimeId" : 4364 - }, - { - "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 302 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3618 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3619 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3620 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3621 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3622 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3623 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3624 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3625 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3626 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3627 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 3628 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5454 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5455 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5458 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 5459 - }, - { - "id" : "minecraft:wither_rose", - "blockRuntimeId" : 6167 - }, - { - "id" : "minecraft:white_dye" - }, - { - "id" : "minecraft:light_gray_dye" - }, - { - "id" : "minecraft:gray_dye" - }, - { - "id" : "minecraft:black_dye" - }, - { - "id" : "minecraft:brown_dye" - }, - { - "id" : "minecraft:red_dye" - }, - { - "id" : "minecraft:orange_dye" - }, - { - "id" : "minecraft:yellow_dye" - }, - { - "id" : "minecraft:lime_dye" - }, - { - "id" : "minecraft:green_dye" - }, - { - "id" : "minecraft:cyan_dye" - }, - { - "id" : "minecraft:light_blue_dye" - }, - { - "id" : "minecraft:blue_dye" - }, - { - "id" : "minecraft:purple_dye" - }, - { - "id" : "minecraft:magenta_dye" - }, - { - "id" : "minecraft:pink_dye" - }, - { - "id" : "minecraft:ink_sac" - }, - { - "id" : "minecraft:glow_ink_sac" - }, - { - "id" : "minecraft:cocoa_beans" - }, - { - "id" : "minecraft:lapis_lazuli" - }, - { - "id" : "minecraft:bone_meal" - }, - { - "id" : "minecraft:vine", - "blockRuntimeId" : 896 - }, - { - "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 5481 - }, - { - "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 5693 - }, - { - "id" : "minecraft:waterlily", - "blockRuntimeId" : 1160 - }, - { - "id" : "minecraft:deadbush", - "blockRuntimeId" : 4679 - }, - { - "id" : "minecraft:bamboo", - "blockRuntimeId" : 3686 - }, - { - "id" : "minecraft:snow", - "blockRuntimeId" : 4196 - }, - { - "id" : "minecraft:ice", - "blockRuntimeId" : 6691 - }, - { - "id" : "minecraft:packed_ice", - "blockRuntimeId" : 282 - }, - { - "id" : "minecraft:blue_ice", - "blockRuntimeId" : 7029 - }, - { - "id" : "minecraft:snow_layer", - "blockRuntimeId" : 155 - }, - { - "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 7418 - }, - { - "id" : "minecraft:dripstone_block", - "blockRuntimeId" : 895 - }, - { - "id" : "minecraft:moss_carpet", - "blockRuntimeId" : 286 - }, - { - "id" : "minecraft:moss_block", - "blockRuntimeId" : 6540 - }, - { - "id" : "minecraft:dirt_with_roots", - "blockRuntimeId" : 5399 - }, - { - "id" : "minecraft:hanging_roots", - "blockRuntimeId" : 205 - }, - { - "id" : "minecraft:mangrove_roots", - "blockRuntimeId" : 6177 - }, - { - "id" : "minecraft:muddy_mangrove_roots", - "blockRuntimeId" : 345 - }, - { - "id" : "minecraft:big_dripleaf", - "blockRuntimeId" : 5982 - }, - { - "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 4322 - }, - { - "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 7314 - }, - { - "id" : "minecraft:azalea", - "blockRuntimeId" : 6890 - }, - { - "id" : "minecraft:flowering_azalea", - "blockRuntimeId" : 5479 - }, - { - "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 5686 - }, - { - "id" : "minecraft:amethyst_block", - "blockRuntimeId" : 290 - }, - { - "id" : "minecraft:budding_amethyst", - "blockRuntimeId" : 7004 - }, - { - "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 7812 - }, - { - "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 4730 - }, - { - "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 4378 - }, - { - "id" : "minecraft:small_amethyst_bud", - "blockRuntimeId" : 304 - }, - { - "id" : "minecraft:tuff", - "blockRuntimeId" : 349 - }, - { - "id" : "minecraft:calcite", - "blockRuntimeId" : 215 - }, - { - "id" : "minecraft:chicken" - }, - { - "id" : "minecraft:porkchop" - }, - { - "id" : "minecraft:beef" - }, - { - "id" : "minecraft:mutton" - }, - { - "id" : "minecraft:rabbit" - }, - { - "id" : "minecraft:cod" - }, - { - "id" : "minecraft:salmon" - }, - { - "id" : "minecraft:tropical_fish" - }, - { - "id" : "minecraft:pufferfish" - }, - { - "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 3548 - }, - { - "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 4587 - }, - { - "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 7755 - }, - { - "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 287 - }, - { - "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7364 - }, - { - "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 3613 - }, - { - "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7365 - }, - { - "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 7350 - }, - { - "id" : "minecraft:egg" - }, - { - "id" : "minecraft:sugar_cane" - }, - { - "id" : "minecraft:sugar" - }, - { - "id" : "minecraft:rotten_flesh" - }, - { - "id" : "minecraft:bone" - }, - { - "id" : "minecraft:web", - "blockRuntimeId" : 6715 - }, - { - "id" : "minecraft:spider_eye" - }, - { - "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 403 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4146 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4147 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4148 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4149 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4150 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4151 - }, - { - "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 4643 - }, - { - "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 7273 - }, - { - "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7999 - }, - { - "id" : "minecraft:frog_spawn", - "blockRuntimeId" : 4401 - }, - { - "id" : "minecraft:pearlescent_froglight", - "blockRuntimeId" : 6437 - }, - { - "id" : "minecraft:verdant_froglight", - "blockRuntimeId" : 6483 - }, - { - "id" : "minecraft:ochre_froglight", - "blockRuntimeId" : 3512 - }, - { - "id" : "minecraft:chicken_spawn_egg" - }, - { - "id" : "minecraft:bee_spawn_egg" - }, - { - "id" : "minecraft:cow_spawn_egg" - }, - { - "id" : "minecraft:pig_spawn_egg" - }, - { - "id" : "minecraft:sheep_spawn_egg" - }, - { - "id" : "minecraft:wolf_spawn_egg" - }, - { - "id" : "minecraft:polar_bear_spawn_egg" - }, - { - "id" : "minecraft:ocelot_spawn_egg" - }, - { - "id" : "minecraft:cat_spawn_egg" - }, - { - "id" : "minecraft:mooshroom_spawn_egg" - }, - { - "id" : "minecraft:bat_spawn_egg" - }, - { - "id" : "minecraft:parrot_spawn_egg" - }, - { - "id" : "minecraft:rabbit_spawn_egg" - }, - { - "id" : "minecraft:llama_spawn_egg" - }, - { - "id" : "minecraft:horse_spawn_egg" - }, - { - "id" : "minecraft:donkey_spawn_egg" - }, - { - "id" : "minecraft:mule_spawn_egg" - }, - { - "id" : "minecraft:skeleton_horse_spawn_egg" - }, - { - "id" : "minecraft:zombie_horse_spawn_egg" - }, - { - "id" : "minecraft:tropical_fish_spawn_egg" - }, - { - "id" : "minecraft:cod_spawn_egg" - }, - { - "id" : "minecraft:pufferfish_spawn_egg" - }, - { - "id" : "minecraft:salmon_spawn_egg" - }, - { - "id" : "minecraft:dolphin_spawn_egg" - }, - { - "id" : "minecraft:turtle_spawn_egg" - }, - { - "id" : "minecraft:panda_spawn_egg" - }, - { - "id" : "minecraft:fox_spawn_egg" - }, - { - "id" : "minecraft:creeper_spawn_egg" - }, - { - "id" : "minecraft:enderman_spawn_egg" - }, - { - "id" : "minecraft:silverfish_spawn_egg" - }, - { - "id" : "minecraft:skeleton_spawn_egg" - }, - { - "id" : "minecraft:wither_skeleton_spawn_egg" - }, - { - "id" : "minecraft:stray_spawn_egg" - }, - { - "id" : "minecraft:slime_spawn_egg" - }, - { - "id" : "minecraft:spider_spawn_egg" - }, - { - "id" : "minecraft:zombie_spawn_egg" - }, - { - "id" : "minecraft:zombie_pigman_spawn_egg" - }, - { - "id" : "minecraft:husk_spawn_egg" - }, - { - "id" : "minecraft:drowned_spawn_egg" - }, - { - "id" : "minecraft:squid_spawn_egg" - }, - { - "id" : "minecraft:glow_squid_spawn_egg" - }, - { - "id" : "minecraft:cave_spider_spawn_egg" - }, - { - "id" : "minecraft:witch_spawn_egg" - }, - { - "id" : "minecraft:guardian_spawn_egg" - }, - { - "id" : "minecraft:elder_guardian_spawn_egg" - }, - { - "id" : "minecraft:endermite_spawn_egg" - }, - { - "id" : "minecraft:magma_cube_spawn_egg" - }, - { - "id" : "minecraft:strider_spawn_egg" - }, - { - "id" : "minecraft:hoglin_spawn_egg" - }, - { - "id" : "minecraft:piglin_spawn_egg" - }, - { - "id" : "minecraft:zoglin_spawn_egg" - }, - { - "id" : "minecraft:piglin_brute_spawn_egg" - }, - { - "id" : "minecraft:goat_spawn_egg" - }, - { - "id" : "minecraft:axolotl_spawn_egg" - }, - { - "id" : "minecraft:warden_spawn_egg" - }, - { - "id" : "minecraft:allay_spawn_egg" - }, - { - "id" : "minecraft:frog_spawn_egg" - }, - { - "id" : "minecraft:tadpole_spawn_egg" - }, - { - "id" : "minecraft:trader_llama_spawn_egg" - }, - { - "id" : "minecraft:ghast_spawn_egg" - }, - { - "id" : "minecraft:blaze_spawn_egg" - }, - { - "id" : "minecraft:shulker_spawn_egg" - }, - { - "id" : "minecraft:vindicator_spawn_egg" - }, - { - "id" : "minecraft:evoker_spawn_egg" - }, - { - "id" : "minecraft:vex_spawn_egg" - }, - { - "id" : "minecraft:villager_spawn_egg" - }, - { - "id" : "minecraft:wandering_trader_spawn_egg" - }, - { - "id" : "minecraft:zombie_villager_spawn_egg" - }, - { - "id" : "minecraft:phantom_spawn_egg" - }, - { - "id" : "minecraft:pillager_spawn_egg" - }, - { - "id" : "minecraft:ravager_spawn_egg" - }, - { - "id" : "minecraft:obsidian", - "blockRuntimeId" : 430 - }, - { - "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 6724 - }, - { - "id" : "minecraft:bedrock", - "blockRuntimeId" : 7019 - }, - { - "id" : "minecraft:soul_sand", - "blockRuntimeId" : 5833 - }, - { - "id" : "minecraft:netherrack", - "blockRuntimeId" : 7039 - }, - { - "id" : "minecraft:magma", - "blockRuntimeId" : 8011 - }, - { - "id" : "minecraft:nether_wart" - }, - { - "id" : "minecraft:end_stone", - "blockRuntimeId" : 3838 - }, - { - "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 4532 - }, - { - "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 5507 - }, - { - "id" : "minecraft:chorus_fruit" - }, - { - "id" : "minecraft:popped_chorus_fruit" - }, - { - "id" : "minecraft:sponge", - "blockRuntimeId" : 631 - }, - { - "id" : "minecraft:sponge", - "blockRuntimeId" : 632 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5239 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5240 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5241 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5242 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5243 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5244 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5245 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5246 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5247 - }, - { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 5248 - }, - { - "id" : "minecraft:sculk", - "blockRuntimeId" : 7038 - }, - { - "id" : "minecraft:sculk_vein", - "blockRuntimeId" : 7134 - }, - { - "id" : "minecraft:sculk_catalyst", - "blockRuntimeId" : 3615 - }, - { - "id" : "minecraft:sculk_shrieker", - "blockRuntimeId" : 219 - }, - { - "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 4391 - }, - { - "id" : "minecraft:reinforced_deepslate", - "blockRuntimeId" : 5834 - }, - { - "id" : "minecraft:leather_helmet" - }, - { - "id" : "minecraft:chainmail_helmet" - }, - { - "id" : "minecraft:iron_helmet" - }, - { - "id" : "minecraft:golden_helmet" - }, - { - "id" : "minecraft:diamond_helmet" - }, - { - "id" : "minecraft:netherite_helmet" - }, - { - "id" : "minecraft:leather_chestplate" - }, - { - "id" : "minecraft:chainmail_chestplate" - }, - { - "id" : "minecraft:iron_chestplate" - }, - { - "id" : "minecraft:golden_chestplate" - }, - { - "id" : "minecraft:diamond_chestplate" - }, - { - "id" : "minecraft:netherite_chestplate" - }, - { - "id" : "minecraft:leather_leggings" - }, - { - "id" : "minecraft:chainmail_leggings" - }, - { - "id" : "minecraft:iron_leggings" - }, - { - "id" : "minecraft:golden_leggings" - }, - { - "id" : "minecraft:diamond_leggings" - }, - { - "id" : "minecraft:netherite_leggings" - }, - { - "id" : "minecraft:leather_boots" - }, - { - "id" : "minecraft:chainmail_boots" - }, - { - "id" : "minecraft:iron_boots" - }, - { - "id" : "minecraft:golden_boots" - }, - { - "id" : "minecraft:diamond_boots" - }, - { - "id" : "minecraft:netherite_boots" - }, - { - "id" : "minecraft:wooden_sword" - }, - { - "id" : "minecraft:stone_sword" - }, - { - "id" : "minecraft:iron_sword" - }, - { - "id" : "minecraft:golden_sword" - }, - { - "id" : "minecraft:diamond_sword" - }, - { - "id" : "minecraft:netherite_sword" - }, - { - "id" : "minecraft:wooden_axe" - }, - { - "id" : "minecraft:stone_axe" - }, - { - "id" : "minecraft:iron_axe" - }, - { - "id" : "minecraft:golden_axe" - }, - { - "id" : "minecraft:diamond_axe" - }, - { - "id" : "minecraft:netherite_axe" - }, - { - "id" : "minecraft:wooden_pickaxe" - }, - { - "id" : "minecraft:stone_pickaxe" - }, - { - "id" : "minecraft:iron_pickaxe" - }, - { - "id" : "minecraft:golden_pickaxe" - }, - { - "id" : "minecraft:diamond_pickaxe" - }, - { - "id" : "minecraft:netherite_pickaxe" - }, - { - "id" : "minecraft:wooden_shovel" - }, - { - "id" : "minecraft:stone_shovel" - }, - { - "id" : "minecraft:iron_shovel" - }, - { - "id" : "minecraft:golden_shovel" - }, - { - "id" : "minecraft:diamond_shovel" - }, - { - "id" : "minecraft:netherite_shovel" - }, - { - "id" : "minecraft:wooden_hoe" - }, - { - "id" : "minecraft:stone_hoe" - }, - { - "id" : "minecraft:iron_hoe" - }, - { - "id" : "minecraft:golden_hoe" - }, - { - "id" : "minecraft:diamond_hoe" - }, - { - "id" : "minecraft:netherite_hoe" - }, - { - "id" : "minecraft:bow" - }, - { - "id" : "minecraft:crossbow" - }, - { - "id" : "minecraft:arrow" - }, - { - "id" : "minecraft:arrow", - "damage" : 6 - }, - { - "id" : "minecraft:arrow", - "damage" : 7 - }, - { - "id" : "minecraft:arrow", - "damage" : 8 - }, - { - "id" : "minecraft:arrow", - "damage" : 9 - }, - { - "id" : "minecraft:arrow", - "damage" : 10 - }, - { - "id" : "minecraft:arrow", - "damage" : 11 - }, - { - "id" : "minecraft:arrow", - "damage" : 12 - }, - { - "id" : "minecraft:arrow", - "damage" : 13 - }, - { - "id" : "minecraft:arrow", - "damage" : 14 - }, - { - "id" : "minecraft:arrow", - "damage" : 15 - }, - { - "id" : "minecraft:arrow", - "damage" : 16 - }, - { - "id" : "minecraft:arrow", - "damage" : 17 - }, - { - "id" : "minecraft:arrow", - "damage" : 18 - }, - { - "id" : "minecraft:arrow", - "damage" : 19 - }, - { - "id" : "minecraft:arrow", - "damage" : 20 - }, - { - "id" : "minecraft:arrow", - "damage" : 21 - }, - { - "id" : "minecraft:arrow", - "damage" : 22 - }, - { - "id" : "minecraft:arrow", - "damage" : 23 - }, - { - "id" : "minecraft:arrow", - "damage" : 24 - }, - { - "id" : "minecraft:arrow", - "damage" : 25 - }, - { - "id" : "minecraft:arrow", - "damage" : 26 - }, - { - "id" : "minecraft:arrow", - "damage" : 27 - }, - { - "id" : "minecraft:arrow", - "damage" : 28 - }, - { - "id" : "minecraft:arrow", - "damage" : 29 - }, - { - "id" : "minecraft:arrow", - "damage" : 30 - }, - { - "id" : "minecraft:arrow", - "damage" : 31 - }, - { - "id" : "minecraft:arrow", - "damage" : 32 - }, - { - "id" : "minecraft:arrow", - "damage" : 33 - }, - { - "id" : "minecraft:arrow", - "damage" : 34 - }, - { - "id" : "minecraft:arrow", - "damage" : 35 - }, - { - "id" : "minecraft:arrow", - "damage" : 36 - }, - { - "id" : "minecraft:arrow", - "damage" : 37 - }, - { - "id" : "minecraft:arrow", - "damage" : 38 - }, - { - "id" : "minecraft:arrow", - "damage" : 39 - }, - { - "id" : "minecraft:arrow", - "damage" : 40 - }, - { - "id" : "minecraft:arrow", - "damage" : 41 - }, - { - "id" : "minecraft:arrow", - "damage" : 42 - }, - { - "id" : "minecraft:arrow", - "damage" : 43 - }, - { - "id" : "minecraft:shield" - }, - { - "id" : "minecraft:cooked_chicken" - }, - { - "id" : "minecraft:cooked_porkchop" - }, - { - "id" : "minecraft:cooked_beef" - }, - { - "id" : "minecraft:cooked_mutton" - }, - { - "id" : "minecraft:cooked_rabbit" - }, - { - "id" : "minecraft:cooked_cod" - }, - { - "id" : "minecraft:cooked_salmon" - }, - { - "id" : "minecraft:bread" - }, - { - "id" : "minecraft:mushroom_stew" - }, - { - "id" : "minecraft:beetroot_soup" - }, - { - "id" : "minecraft:rabbit_stew" - }, - { - "id" : "minecraft:baked_potato" - }, - { - "id" : "minecraft:cookie" - }, - { - "id" : "minecraft:pumpkin_pie" - }, - { - "id" : "minecraft:cake" - }, - { - "id" : "minecraft:dried_kelp" - }, - { - "id" : "minecraft:fishing_rod" - }, - { - "id" : "minecraft:carrot_on_a_stick" - }, - { - "id" : "minecraft:warped_fungus_on_a_stick" - }, - { - "id" : "minecraft:snowball" - }, - { - "id" : "minecraft:shears" - }, - { - "id" : "minecraft:flint_and_steel" - }, - { - "id" : "minecraft:lead" - }, - { - "id" : "minecraft:clock" - }, - { - "id" : "minecraft:compass" - }, - { - "id" : "minecraft:recovery_compass" - }, - { - "id" : "minecraft:goat_horn" - }, - { - "id" : "minecraft:goat_horn", - "damage" : 1 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 2 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 3 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 4 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 5 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 6 - }, - { - "id" : "minecraft:goat_horn", - "damage" : 7 - }, - { - "id" : "minecraft:empty_map" - }, - { - "id" : "minecraft:empty_map", - "damage" : 2 - }, - { - "id" : "minecraft:saddle" - }, - { - "id" : "minecraft:leather_horse_armor" - }, - { - "id" : "minecraft:iron_horse_armor" - }, - { - "id" : "minecraft:golden_horse_armor" - }, - { - "id" : "minecraft:diamond_horse_armor" - }, - { - "id" : "minecraft:trident" - }, - { - "id" : "minecraft:turtle_helmet" - }, - { - "id" : "minecraft:elytra" - }, - { - "id" : "minecraft:totem_of_undying" - }, - { - "id" : "minecraft:glass_bottle" - }, - { - "id" : "minecraft:experience_bottle" - }, - { - "id" : "minecraft:potion" - }, - { - "id" : "minecraft:potion", - "damage" : 1 - }, - { - "id" : "minecraft:potion", - "damage" : 2 - }, - { - "id" : "minecraft:potion", - "damage" : 3 - }, - { - "id" : "minecraft:potion", - "damage" : 4 - }, - { - "id" : "minecraft:potion", - "damage" : 5 - }, - { - "id" : "minecraft:potion", - "damage" : 6 - }, - { - "id" : "minecraft:potion", - "damage" : 7 - }, - { - "id" : "minecraft:potion", - "damage" : 8 - }, - { - "id" : "minecraft:potion", - "damage" : 9 - }, - { - "id" : "minecraft:potion", - "damage" : 10 - }, - { - "id" : "minecraft:potion", - "damage" : 11 - }, - { - "id" : "minecraft:potion", - "damage" : 12 - }, - { - "id" : "minecraft:potion", - "damage" : 13 - }, - { - "id" : "minecraft:potion", - "damage" : 14 - }, - { - "id" : "minecraft:potion", - "damage" : 15 - }, - { - "id" : "minecraft:potion", - "damage" : 16 - }, - { - "id" : "minecraft:potion", - "damage" : 17 - }, - { - "id" : "minecraft:potion", - "damage" : 18 - }, - { - "id" : "minecraft:potion", - "damage" : 19 - }, - { - "id" : "minecraft:potion", - "damage" : 20 - }, - { - "id" : "minecraft:potion", - "damage" : 21 - }, - { - "id" : "minecraft:potion", - "damage" : 22 - }, - { - "id" : "minecraft:potion", - "damage" : 23 - }, - { - "id" : "minecraft:potion", - "damage" : 24 - }, - { - "id" : "minecraft:potion", - "damage" : 25 - }, - { - "id" : "minecraft:potion", - "damage" : 26 - }, - { - "id" : "minecraft:potion", - "damage" : 27 - }, - { - "id" : "minecraft:potion", - "damage" : 28 - }, - { - "id" : "minecraft:potion", - "damage" : 29 - }, - { - "id" : "minecraft:potion", - "damage" : 30 - }, - { - "id" : "minecraft:potion", - "damage" : 31 - }, - { - "id" : "minecraft:potion", - "damage" : 32 - }, - { - "id" : "minecraft:potion", - "damage" : 33 - }, - { - "id" : "minecraft:potion", - "damage" : 34 - }, - { - "id" : "minecraft:potion", - "damage" : 35 - }, - { - "id" : "minecraft:potion", - "damage" : 36 - }, - { - "id" : "minecraft:potion", - "damage" : 37 - }, - { - "id" : "minecraft:potion", - "damage" : 38 - }, - { - "id" : "minecraft:potion", - "damage" : 39 - }, - { - "id" : "minecraft:potion", - "damage" : 40 - }, - { - "id" : "minecraft:potion", - "damage" : 41 - }, - { - "id" : "minecraft:potion", - "damage" : 42 - }, - { - "id" : "minecraft:splash_potion" - }, - { - "id" : "minecraft:splash_potion", - "damage" : 1 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 2 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 3 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 4 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 5 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 6 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 7 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 8 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 9 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 10 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 11 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 12 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 13 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 14 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 15 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 16 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 17 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 18 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 19 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 20 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 21 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 22 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 23 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 24 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 25 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 26 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 27 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 28 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 29 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 30 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 31 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 32 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 33 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 34 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 35 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 36 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 37 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 38 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 39 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 40 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 41 - }, - { - "id" : "minecraft:splash_potion", - "damage" : 42 - }, - { - "id" : "minecraft:lingering_potion" - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 1 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 2 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 3 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 4 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 5 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 6 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 7 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 8 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 9 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 10 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 11 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 12 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 13 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 14 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 15 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 16 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 17 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 18 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 19 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 20 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 21 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 22 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 23 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 24 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 25 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 26 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 27 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 28 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 29 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 30 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 31 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 32 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 33 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 34 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 35 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 36 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 37 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 38 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 39 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 40 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 41 - }, - { - "id" : "minecraft:lingering_potion", - "damage" : 42 - }, - { - "id" : "minecraft:spyglass" - }, - { - "id" : "minecraft:stick" - }, - { - "id" : "minecraft:bed" - }, - { - "id" : "minecraft:bed", - "damage" : 8 - }, - { - "id" : "minecraft:bed", - "damage" : 7 - }, - { - "id" : "minecraft:bed", - "damage" : 15 - }, - { - "id" : "minecraft:bed", - "damage" : 12 - }, - { - "id" : "minecraft:bed", - "damage" : 14 - }, - { - "id" : "minecraft:bed", - "damage" : 1 - }, - { - "id" : "minecraft:bed", - "damage" : 4 - }, - { - "id" : "minecraft:bed", - "damage" : 5 - }, - { - "id" : "minecraft:bed", - "damage" : 13 - }, - { - "id" : "minecraft:bed", - "damage" : 9 - }, - { - "id" : "minecraft:bed", - "damage" : 3 - }, - { - "id" : "minecraft:bed", - "damage" : 11 - }, - { - "id" : "minecraft:bed", - "damage" : 10 - }, - { - "id" : "minecraft:bed", - "damage" : 2 - }, - { - "id" : "minecraft:bed", - "damage" : 6 - }, - { - "id" : "minecraft:torch", - "blockRuntimeId" : 726 - }, - { - "id" : "minecraft:soul_torch", - "blockRuntimeId" : 4646 - }, - { - "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 5857 - }, - { - "id" : "minecraft:lantern", - "blockRuntimeId" : 7076 - }, - { - "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 5751 - }, - { - "id" : "minecraft:candle", - "blockRuntimeId" : 7405 - }, - { - "id" : "minecraft:white_candle", - "blockRuntimeId" : 5302 - }, - { - "id" : "minecraft:orange_candle", - "blockRuntimeId" : 364 - }, - { - "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 420 - }, - { - "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 4571 - }, - { - "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 6194 - }, - { - "id" : "minecraft:lime_candle", - "blockRuntimeId" : 6370 - }, - { - "id" : "minecraft:pink_candle", - "blockRuntimeId" : 7372 - }, - { - "id" : "minecraft:gray_candle", - "blockRuntimeId" : 941 - }, - { - "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 6226 - }, - { - "id" : "minecraft:cyan_candle", - "blockRuntimeId" : 7728 - }, - { - "id" : "minecraft:purple_candle", - "blockRuntimeId" : 7040 - }, - { - "id" : "minecraft:blue_candle" - }, - { - "id" : "minecraft:brown_candle", - "blockRuntimeId" : 5877 - }, - { - "id" : "minecraft:green_candle", - "blockRuntimeId" : 688 - }, - { - "id" : "minecraft:red_candle", - "blockRuntimeId" : 4683 - }, - { - "id" : "minecraft:black_candle", - "blockRuntimeId" : 171 - }, - { - "id" : "minecraft:crafting_table", - "blockRuntimeId" : 5856 - }, - { - "id" : "minecraft:cartography_table", - "blockRuntimeId" : 8290 - }, - { - "id" : "minecraft:fletching_table", - "blockRuntimeId" : 5835 - }, - { - "id" : "minecraft:smithing_table", - "blockRuntimeId" : 3728 - }, - { - "id" : "minecraft:beehive", - "blockRuntimeId" : 6110 - }, - { - "id" : "minecraft:campfire" - }, - { - "id" : "minecraft:soul_campfire" - }, - { - "id" : "minecraft:furnace", - "blockRuntimeId" : 7804 - }, - { - "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 7569 - }, - { - "id" : "minecraft:smoker", - "blockRuntimeId" : 649 - }, - { - "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 683 - }, - { - "id" : "minecraft:brewing_stand" - }, - { - "id" : "minecraft:anvil", - "blockRuntimeId" : 6636 - }, - { - "id" : "minecraft:anvil", - "blockRuntimeId" : 6640 - }, - { - "id" : "minecraft:anvil", - "blockRuntimeId" : 6644 - }, - { - "id" : "minecraft:grindstone", - "blockRuntimeId" : 8041 - }, - { - "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 6725 - }, - { - "id" : "minecraft:bookshelf", - "blockRuntimeId" : 6673 - }, - { - "id" : "minecraft:lectern", - "blockRuntimeId" : 6942 - }, - { - "id" : "minecraft:cauldron" - }, - { - "id" : "minecraft:composter", - "blockRuntimeId" : 5417 - }, - { - "id" : "minecraft:chest", - "blockRuntimeId" : 7117 - }, - { - "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 5585 - }, - { - "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4371 - }, - { - "id" : "minecraft:barrel", - "blockRuntimeId" : 4520 - }, - { - "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 3683 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5318 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5326 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5325 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5333 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5330 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5332 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5319 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5322 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5323 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5331 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5327 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5321 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5329 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5328 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5320 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5324 - }, - { - "id" : "minecraft:armor_stand" - }, - { - "id" : "minecraft:noteblock", - "blockRuntimeId" : 348 - }, - { - "id" : "minecraft:jukebox", - "blockRuntimeId" : 4876 - }, - { - "id" : "minecraft:music_disc_13" - }, - { - "id" : "minecraft:music_disc_cat" - }, - { - "id" : "minecraft:music_disc_blocks" - }, - { - "id" : "minecraft:music_disc_chirp" - }, - { - "id" : "minecraft:music_disc_far" - }, - { - "id" : "minecraft:music_disc_mall" - }, - { - "id" : "minecraft:music_disc_mellohi" - }, - { - "id" : "minecraft:music_disc_stal" - }, - { - "id" : "minecraft:music_disc_strad" - }, - { - "id" : "minecraft:music_disc_ward" - }, - { - "id" : "minecraft:music_disc_11" - }, - { - "id" : "minecraft:music_disc_wait" - }, - { - "id" : "minecraft:music_disc_otherside" - }, - { - "id" : "minecraft:music_disc_5" - }, - { - "id" : "minecraft:music_disc_pigstep" - }, - { - "id" : "minecraft:disc_fragment_5" - }, - { - "id" : "minecraft:glowstone_dust" - }, - { - "id" : "minecraft:glowstone", - "blockRuntimeId" : 3887 - }, - { - "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 251 - }, - { - "id" : "minecraft:sea_lantern", - "blockRuntimeId" : 7548 - }, - { - "id" : "minecraft:oak_sign" - }, - { - "id" : "minecraft:spruce_sign" - }, - { - "id" : "minecraft:birch_sign" - }, - { - "id" : "minecraft:jungle_sign" - }, - { - "id" : "minecraft:acacia_sign" - }, - { - "id" : "minecraft:dark_oak_sign" - }, - { - "id" : "minecraft:mangrove_sign" - }, - { - "id" : "minecraft:crimson_sign" - }, - { - "id" : "minecraft:warped_sign" - }, - { - "id" : "minecraft:painting" - }, - { - "id" : "minecraft:frame" - }, - { - "id" : "minecraft:glow_frame" - }, - { - "id" : "minecraft:honey_bottle" - }, - { - "id" : "minecraft:flower_pot" - }, - { - "id" : "minecraft:bowl" - }, - { - "id" : "minecraft:bucket" - }, - { - "id" : "minecraft:milk_bucket" - }, - { - "id" : "minecraft:water_bucket" - }, - { - "id" : "minecraft:lava_bucket" - }, - { - "id" : "minecraft:cod_bucket" - }, - { - "id" : "minecraft:salmon_bucket" - }, - { - "id" : "minecraft:tropical_fish_bucket" - }, - { - "id" : "minecraft:pufferfish_bucket" - }, - { - "id" : "minecraft:powder_snow_bucket" - }, - { - "id" : "minecraft:axolotl_bucket" - }, - { - "id" : "minecraft:tadpole_bucket" - }, - { - "id" : "minecraft:skull", - "damage" : 3 - }, - { - "id" : "minecraft:skull", - "damage" : 2 - }, - { - "id" : "minecraft:skull", - "damage" : 4 - }, - { - "id" : "minecraft:skull", - "damage" : 5 - }, - { - "id" : "minecraft:skull" - }, - { - "id" : "minecraft:skull", - "damage" : 1 - }, - { - "id" : "minecraft:beacon", - "blockRuntimeId" : 145 - }, - { - "id" : "minecraft:bell", - "blockRuntimeId" : 6910 - }, - { - "id" : "minecraft:conduit", - "blockRuntimeId" : 4234 - }, - { - "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7576 - }, - { - "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 6079 - }, - { - "id" : "minecraft:coal" - }, - { - "id" : "minecraft:charcoal" - }, - { - "id" : "minecraft:diamond" - }, - { - "id" : "minecraft:iron_nugget" - }, - { - "id" : "minecraft:raw_iron" - }, - { - "id" : "minecraft:raw_gold" - }, - { - "id" : "minecraft:raw_copper" - }, - { - "id" : "minecraft:copper_ingot" - }, - { - "id" : "minecraft:iron_ingot" - }, - { - "id" : "minecraft:netherite_scrap" - }, - { - "id" : "minecraft:netherite_ingot" - }, - { - "id" : "minecraft:gold_nugget" - }, - { - "id" : "minecraft:gold_ingot" - }, - { - "id" : "minecraft:emerald" - }, - { - "id" : "minecraft:quartz" - }, - { - "id" : "minecraft:clay_ball" - }, - { - "id" : "minecraft:brick" - }, - { - "id" : "minecraft:netherbrick" - }, - { - "id" : "minecraft:prismarine_shard" - }, - { - "id" : "minecraft:amethyst_shard" - }, - { - "id" : "minecraft:prismarine_crystals" - }, - { - "id" : "minecraft:nautilus_shell" - }, - { - "id" : "minecraft:heart_of_the_sea" - }, - { - "id" : "minecraft:scute" - }, - { - "id" : "minecraft:phantom_membrane" - }, - { - "id" : "minecraft:string" - }, - { - "id" : "minecraft:feather" - }, - { - "id" : "minecraft:flint" - }, - { - "id" : "minecraft:gunpowder" - }, - { - "id" : "minecraft:leather" - }, - { - "id" : "minecraft:rabbit_hide" - }, - { - "id" : "minecraft:rabbit_foot" - }, - { - "id" : "minecraft:fire_charge" - }, - { - "id" : "minecraft:blaze_rod" - }, - { - "id" : "minecraft:blaze_powder" - }, - { - "id" : "minecraft:magma_cream" - }, - { - "id" : "minecraft:fermented_spider_eye" - }, - { - "id" : "minecraft:echo_shard" - }, - { - "id" : "minecraft:dragon_breath" - }, - { - "id" : "minecraft:shulker_shell" - }, - { - "id" : "minecraft:ghast_tear" - }, - { - "id" : "minecraft:slime_ball" - }, - { - "id" : "minecraft:ender_pearl" - }, - { - "id" : "minecraft:ender_eye" - }, - { - "id" : "minecraft:nether_star" - }, - { - "id" : "minecraft:end_rod", - "blockRuntimeId" : 5893 - }, - { - "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 1178 - }, - { - "id" : "minecraft:end_crystal" - }, - { - "id" : "minecraft:paper" - }, - { - "id" : "minecraft:book" - }, - { - "id" : "minecraft:writable_book" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" - }, - { - "id" : "minecraft:enchanted_book", - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" - }, - { - "id" : "minecraft:oak_boat" - }, - { - "id" : "minecraft:spruce_boat" - }, - { - "id" : "minecraft:birch_boat" - }, - { - "id" : "minecraft:jungle_boat" - }, - { - "id" : "minecraft:acacia_boat" - }, - { - "id" : "minecraft:dark_oak_boat" - }, - { - "id" : "minecraft:mangrove_boat" - }, - { - "id" : "minecraft:oak_chest_boat" - }, - { - "id" : "minecraft:spruce_chest_boat" - }, - { - "id" : "minecraft:birch_chest_boat" - }, - { - "id" : "minecraft:jungle_chest_boat" - }, - { - "id" : "minecraft:acacia_chest_boat" - }, - { - "id" : "minecraft:dark_oak_chest_boat" - }, - { - "id" : "minecraft:mangrove_chest_boat" - }, - { - "id" : "minecraft:rail", - "blockRuntimeId" : 3922 - }, - { - "id" : "minecraft:golden_rail", - "blockRuntimeId" : 5334 - }, - { - "id" : "minecraft:detector_rail", - "blockRuntimeId" : 4134 - }, - { - "id" : "minecraft:activator_rail", - "blockRuntimeId" : 309 - }, - { - "id" : "minecraft:minecart" - }, - { - "id" : "minecraft:chest_minecart" - }, - { - "id" : "minecraft:hopper_minecart" - }, - { - "id" : "minecraft:tnt_minecart" - }, - { - "id" : "minecraft:redstone" - }, - { - "id" : "minecraft:redstone_block", - "blockRuntimeId" : 3778 - }, - { - "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 3527 - }, - { - "id" : "minecraft:lever", - "blockRuntimeId" : 6516 - }, - { - "id" : "minecraft:wooden_button", - "blockRuntimeId" : 6393 - }, - { - "id" : "minecraft:spruce_button", - "blockRuntimeId" : 4323 - }, - { - "id" : "minecraft:birch_button", - "blockRuntimeId" : 7768 - }, - { - "id" : "minecraft:jungle_button", - "blockRuntimeId" : 116 - }, - { - "id" : "minecraft:acacia_button", - "blockRuntimeId" : 7233 - }, - { - "id" : "minecraft:dark_oak_button", - "blockRuntimeId" : 93 - }, - { - "id" : "minecraft:mangrove_button", - "blockRuntimeId" : 7064 - }, - { - "id" : "minecraft:stone_button", - "blockRuntimeId" : 598 - }, - { - "id" : "minecraft:crimson_button", - "blockRuntimeId" : 4434 - }, - { - "id" : "minecraft:warped_button", - "blockRuntimeId" : 7252 - }, - { - "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 7792 - }, - { - "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 5916 - }, - { - "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 8065 - }, - { - "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 3761 - }, - { - "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 3557 - }, - { - "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 3637 - }, - { - "id" : "minecraft:acacia_pressure_plate", - "blockRuntimeId" : 5249 - }, - { - "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 5958 - }, - { - "id" : "minecraft:mangrove_pressure_plate", - "blockRuntimeId" : 3871 - }, - { - "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 8270 - }, - { - "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 256 - }, - { - "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 3888 - }, - { - "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 3667 - }, - { - "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 1162 - }, - { - "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6234 - }, - { - "id" : "minecraft:observer", - "blockRuntimeId" : 3515 - }, - { - "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 4199 - }, - { - "id" : "minecraft:repeater" - }, - { - "id" : "minecraft:comparator" - }, - { - "id" : "minecraft:hopper" - }, - { - "id" : "minecraft:dropper", - "blockRuntimeId" : 7387 - }, - { - "id" : "minecraft:dispenser", - "blockRuntimeId" : 8015 - }, - { - "id" : "minecraft:piston", - "blockRuntimeId" : 924 - }, - { - "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 4366 - }, - { - "id" : "minecraft:tnt", - "blockRuntimeId" : 6709 - }, - { - "id" : "minecraft:name_tag" - }, - { - "id" : "minecraft:loom", - "blockRuntimeId" : 3828 - }, - { - "id" : "minecraft:banner" - }, - { - "id" : "minecraft:banner", - "damage" : 8 - }, - { - "id" : "minecraft:banner", - "damage" : 7 - }, - { - "id" : "minecraft:banner", - "damage" : 15 - }, - { - "id" : "minecraft:banner", - "damage" : 12 - }, - { - "id" : "minecraft:banner", - "damage" : 14 - }, - { - "id" : "minecraft:banner", - "damage" : 1 - }, - { - "id" : "minecraft:banner", - "damage" : 4 - }, - { - "id" : "minecraft:banner", - "damage" : 5 - }, - { - "id" : "minecraft:banner", - "damage" : 13 - }, - { - "id" : "minecraft:banner", - "damage" : 9 - }, - { - "id" : "minecraft:banner", - "damage" : 3 - }, - { - "id" : "minecraft:banner", - "damage" : 11 - }, - { - "id" : "minecraft:banner", - "damage" : 10 - }, - { - "id" : "minecraft:banner", - "damage" : 2 - }, - { - "id" : "minecraft:banner", - "damage" : 6 - }, - { - "id" : "minecraft:banner", - "damage" : 15, - "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" - }, - { - "id" : "minecraft:creeper_banner_pattern" - }, - { - "id" : "minecraft:skull_banner_pattern" - }, - { - "id" : "minecraft:flower_banner_pattern" - }, - { - "id" : "minecraft:mojang_banner_pattern" - }, - { - "id" : "minecraft:field_masoned_banner_pattern" - }, - { - "id" : "minecraft:bordure_indented_banner_pattern" - }, - { - "id" : "minecraft:piglin_banner_pattern" - }, - { - "id" : "minecraft:globe_banner_pattern" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_rocket", - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : "minecraft:firework_star", - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 8, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 7, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 15, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 12, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 14, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 1, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 4, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 5, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 13, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 9, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 3, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 11, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 10, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 2, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" - }, - { - "id" : "minecraft:firework_star", - "damage" : 6, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" - }, - { - "id" : "minecraft:chain" - }, - { - "id" : "minecraft:target", - "blockRuntimeId" : 6392 - }, - { - "id" : "minecraft:lodestone_compass" - } - ] -} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json deleted file mode 100644 index 00be1af06ec..00000000000 --- a/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json +++ /dev/null @@ -1,4530 +0,0 @@ -[ - { - "name" : "minecraft:acacia_boat", - "id" : 379 - }, - { - "name" : "minecraft:acacia_button", - "id" : -140 - }, - { - "name" : "minecraft:acacia_chest_boat", - "id" : 642 - }, - { - "name" : "minecraft:acacia_door", - "id" : 556 - }, - { - "name" : "minecraft:acacia_fence_gate", - "id" : 187 - }, - { - "name" : "minecraft:acacia_pressure_plate", - "id" : -150 - }, - { - "name" : "minecraft:acacia_sign", - "id" : 579 - }, - { - "name" : "minecraft:acacia_stairs", - "id" : 163 - }, - { - "name" : "minecraft:acacia_standing_sign", - "id" : -190 - }, - { - "name" : "minecraft:acacia_trapdoor", - "id" : -145 - }, - { - "name" : "minecraft:acacia_wall_sign", - "id" : -191 - }, - { - "name" : "minecraft:activator_rail", - "id" : 126 - }, - { - "name" : "minecraft:agent_spawn_egg", - "id" : 487 - }, - { - "name" : "minecraft:air", - "id" : -158 - }, - { - "name" : "minecraft:allay_spawn_egg", - "id" : 631 - }, - { - "name" : "minecraft:allow", - "id" : 210 - }, - { - "name" : "minecraft:amethyst_block", - "id" : -327 - }, - { - "name" : "minecraft:amethyst_cluster", - "id" : -329 - }, - { - "name" : "minecraft:amethyst_shard", - "id" : 624 - }, - { - "name" : "minecraft:ancient_debris", - "id" : -271 - }, - { - "name" : "minecraft:andesite_stairs", - "id" : -171 - }, - { - "name" : "minecraft:anvil", - "id" : 145 - }, - { - "name" : "minecraft:apple", - "id" : 257 - }, - { - "name" : "minecraft:armor_stand", - "id" : 552 - }, - { - "name" : "minecraft:arrow", - "id" : 301 - }, - { - "name" : "minecraft:axolotl_bucket", - "id" : 369 - }, - { - "name" : "minecraft:axolotl_spawn_egg", - "id" : 500 - }, - { - "name" : "minecraft:azalea", - "id" : -337 - }, - { - "name" : "minecraft:azalea_leaves", - "id" : -324 - }, - { - "name" : "minecraft:azalea_leaves_flowered", - "id" : -325 - }, - { - "name" : "minecraft:baked_potato", - "id" : 281 - }, - { - "name" : "minecraft:balloon", - "id" : 598 - }, - { - "name" : "minecraft:bamboo", - "id" : -163 - }, - { - "name" : "minecraft:bamboo_sapling", - "id" : -164 - }, - { - "name" : "minecraft:banner", - "id" : 567 - }, - { - "name" : "minecraft:banner_pattern", - "id" : 651 - }, - { - "name" : "minecraft:barrel", - "id" : -203 - }, - { - "name" : "minecraft:barrier", - "id" : -161 - }, - { - "name" : "minecraft:basalt", - "id" : -234 - }, - { - "name" : "minecraft:bat_spawn_egg", - "id" : 453 - }, - { - "name" : "minecraft:beacon", - "id" : 138 - }, - { - "name" : "minecraft:bed", - "id" : 418 - }, - { - "name" : "minecraft:bedrock", - "id" : 7 - }, - { - "name" : "minecraft:bee_nest", - "id" : -218 - }, - { - "name" : "minecraft:bee_spawn_egg", - "id" : 494 - }, - { - "name" : "minecraft:beef", - "id" : 273 - }, - { - "name" : "minecraft:beehive", - "id" : -219 - }, - { - "name" : "minecraft:beetroot", - "id" : 285 - }, - { - "name" : "minecraft:beetroot_seeds", - "id" : 295 - }, - { - "name" : "minecraft:beetroot_soup", - "id" : 286 - }, - { - "name" : "minecraft:bell", - "id" : -206 - }, - { - "name" : "minecraft:big_dripleaf", - "id" : -323 - }, - { - "name" : "minecraft:birch_boat", - "id" : 376 - }, - { - "name" : "minecraft:birch_button", - "id" : -141 - }, - { - "name" : "minecraft:birch_chest_boat", - "id" : 639 - }, - { - "name" : "minecraft:birch_door", - "id" : 554 - }, - { - "name" : "minecraft:birch_fence_gate", - "id" : 184 - }, - { - "name" : "minecraft:birch_pressure_plate", - "id" : -151 - }, - { - "name" : "minecraft:birch_sign", - "id" : 577 - }, - { - "name" : "minecraft:birch_stairs", - "id" : 135 - }, - { - "name" : "minecraft:birch_standing_sign", - "id" : -186 - }, - { - "name" : "minecraft:birch_trapdoor", - "id" : -146 - }, - { - "name" : "minecraft:birch_wall_sign", - "id" : -187 - }, - { - "name" : "minecraft:black_candle", - "id" : -428 - }, - { - "name" : "minecraft:black_candle_cake", - "id" : -445 - }, - { - "name" : "minecraft:black_dye", - "id" : 395 - }, - { - "name" : "minecraft:black_glazed_terracotta", - "id" : 235 - }, - { - "name" : "minecraft:blackstone", - "id" : -273 - }, - { - "name" : "minecraft:blackstone_double_slab", - "id" : -283 - }, - { - "name" : "minecraft:blackstone_slab", - "id" : -282 - }, - { - "name" : "minecraft:blackstone_stairs", - "id" : -276 - }, - { - "name" : "minecraft:blackstone_wall", - "id" : -277 - }, - { - "name" : "minecraft:blast_furnace", - "id" : -196 - }, - { - "name" : "minecraft:blaze_powder", - "id" : 429 - }, - { - "name" : "minecraft:blaze_rod", - "id" : 423 - }, - { - "name" : "minecraft:blaze_spawn_egg", - "id" : 456 - }, - { - "name" : "minecraft:bleach", - "id" : 596 - }, - { - "name" : "minecraft:blue_candle", - "id" : -424 - }, - { - "name" : "minecraft:blue_candle_cake", - "id" : -441 - }, - { - "name" : "minecraft:blue_dye", - "id" : 399 - }, - { - "name" : "minecraft:blue_glazed_terracotta", - "id" : 231 - }, - { - "name" : "minecraft:blue_ice", - "id" : -11 - }, - { - "name" : "minecraft:boat", - "id" : 649 - }, - { - "name" : "minecraft:bone", - "id" : 415 - }, - { - "name" : "minecraft:bone_block", - "id" : 216 - }, - { - "name" : "minecraft:bone_meal", - "id" : 411 - }, - { - "name" : "minecraft:book", - "id" : 387 - }, - { - "name" : "minecraft:bookshelf", - "id" : 47 - }, - { - "name" : "minecraft:border_block", - "id" : 212 - }, - { - "name" : "minecraft:bordure_indented_banner_pattern", - "id" : 586 - }, - { - "name" : "minecraft:bow", - "id" : 300 - }, - { - "name" : "minecraft:bowl", - "id" : 321 - }, - { - "name" : "minecraft:bread", - "id" : 261 - }, - { - "name" : "minecraft:brewing_stand", - "id" : 431 - }, - { - "name" : "minecraft:brick", - "id" : 383 - }, - { - "name" : "minecraft:brick_block", - "id" : 45 - }, - { - "name" : "minecraft:brick_stairs", - "id" : 108 - }, - { - "name" : "minecraft:brown_candle", - "id" : -425 - }, - { - "name" : "minecraft:brown_candle_cake", - "id" : -442 - }, - { - "name" : "minecraft:brown_dye", - "id" : 398 - }, - { - "name" : "minecraft:brown_glazed_terracotta", - "id" : 232 - }, - { - "name" : "minecraft:brown_mushroom", - "id" : 39 - }, - { - "name" : "minecraft:brown_mushroom_block", - "id" : 99 - }, - { - "name" : "minecraft:bubble_column", - "id" : -160 - }, - { - "name" : "minecraft:bucket", - "id" : 360 - }, - { - "name" : "minecraft:budding_amethyst", - "id" : -328 - }, - { - "name" : "minecraft:cactus", - "id" : 81 - }, - { - "name" : "minecraft:cake", - "id" : 417 - }, - { - "name" : "minecraft:calcite", - "id" : -326 - }, - { - "name" : "minecraft:camera", - "id" : 593 - }, - { - "name" : "minecraft:campfire", - "id" : 589 - }, - { - "name" : "minecraft:candle", - "id" : -412 - }, - { - "name" : "minecraft:candle_cake", - "id" : -429 - }, - { - "name" : "minecraft:carpet", - "id" : 171 - }, - { - "name" : "minecraft:carrot", - "id" : 279 - }, - { - "name" : "minecraft:carrot_on_a_stick", - "id" : 517 - }, - { - "name" : "minecraft:carrots", - "id" : 141 - }, - { - "name" : "minecraft:cartography_table", - "id" : -200 - }, - { - "name" : "minecraft:carved_pumpkin", - "id" : -155 - }, - { - "name" : "minecraft:cat_spawn_egg", - "id" : 488 - }, - { - "name" : "minecraft:cauldron", - "id" : 432 - }, - { - "name" : "minecraft:cave_spider_spawn_egg", - "id" : 457 - }, - { - "name" : "minecraft:cave_vines", - "id" : -322 - }, - { - "name" : "minecraft:cave_vines_body_with_berries", - "id" : -375 - }, - { - "name" : "minecraft:cave_vines_head_with_berries", - "id" : -376 - }, - { - "name" : "minecraft:chain", - "id" : 619 - }, - { - "name" : "minecraft:chain_command_block", - "id" : 189 - }, - { - "name" : "minecraft:chainmail_boots", - "id" : 342 - }, - { - "name" : "minecraft:chainmail_chestplate", - "id" : 340 - }, - { - "name" : "minecraft:chainmail_helmet", - "id" : 339 - }, - { - "name" : "minecraft:chainmail_leggings", - "id" : 341 - }, - { - "name" : "minecraft:charcoal", - "id" : 303 - }, - { - "name" : "minecraft:chemical_heat", - "id" : 192 - }, - { - "name" : "minecraft:chemistry_table", - "id" : 238 - }, - { - "name" : "minecraft:chest", - "id" : 54 - }, - { - "name" : "minecraft:chest_boat", - "id" : 645 - }, - { - "name" : "minecraft:chest_minecart", - "id" : 389 - }, - { - "name" : "minecraft:chicken", - "id" : 275 - }, - { - "name" : "minecraft:chicken_spawn_egg", - "id" : 435 - }, - { - "name" : "minecraft:chiseled_deepslate", - "id" : -395 - }, - { - "name" : "minecraft:chiseled_nether_bricks", - "id" : -302 - }, - { - "name" : "minecraft:chiseled_polished_blackstone", - "id" : -279 - }, - { - "name" : "minecraft:chorus_flower", - "id" : 200 - }, - { - "name" : "minecraft:chorus_fruit", - "id" : 558 - }, - { - "name" : "minecraft:chorus_plant", - "id" : 240 - }, - { - "name" : "minecraft:clay", - "id" : 82 - }, - { - "name" : "minecraft:clay_ball", - "id" : 384 - }, - { - "name" : "minecraft:client_request_placeholder_block", - "id" : -465 - }, - { - "name" : "minecraft:clock", - "id" : 393 - }, - { - "name" : "minecraft:coal", - "id" : 302 - }, - { - "name" : "minecraft:coal_block", - "id" : 173 - }, - { - "name" : "minecraft:coal_ore", - "id" : 16 - }, - { - "name" : "minecraft:cobbled_deepslate", - "id" : -379 - }, - { - "name" : "minecraft:cobbled_deepslate_double_slab", - "id" : -396 - }, - { - "name" : "minecraft:cobbled_deepslate_slab", - "id" : -380 - }, - { - "name" : "minecraft:cobbled_deepslate_stairs", - "id" : -381 - }, - { - "name" : "minecraft:cobbled_deepslate_wall", - "id" : -382 - }, - { - "name" : "minecraft:cobblestone", - "id" : 4 - }, - { - "name" : "minecraft:cobblestone_wall", - "id" : 139 - }, - { - "name" : "minecraft:cocoa", - "id" : 127 - }, - { - "name" : "minecraft:cocoa_beans", - "id" : 412 - }, - { - "name" : "minecraft:cod", - "id" : 264 - }, - { - "name" : "minecraft:cod_bucket", - "id" : 364 - }, - { - "name" : "minecraft:cod_spawn_egg", - "id" : 480 - }, - { - "name" : "minecraft:colored_torch_bp", - "id" : 204 - }, - { - "name" : "minecraft:colored_torch_rg", - "id" : 202 - }, - { - "name" : "minecraft:command_block", - "id" : 137 - }, - { - "name" : "minecraft:command_block_minecart", - "id" : 563 - }, - { - "name" : "minecraft:comparator", - "id" : 522 - }, - { - "name" : "minecraft:compass", - "id" : 391 - }, - { - "name" : "minecraft:composter", - "id" : -213 - }, - { - "name" : "minecraft:compound", - "id" : 594 - }, - { - "name" : "minecraft:concrete", - "id" : 236 - }, - { - "name" : "minecraft:concrete_powder", - "id" : 237 - }, - { - "name" : "minecraft:conduit", - "id" : -157 - }, - { - "name" : "minecraft:cooked_beef", - "id" : 274 - }, - { - "name" : "minecraft:cooked_chicken", - "id" : 276 - }, - { - "name" : "minecraft:cooked_cod", - "id" : 268 - }, - { - "name" : "minecraft:cooked_mutton", - "id" : 551 - }, - { - "name" : "minecraft:cooked_porkchop", - "id" : 263 - }, - { - "name" : "minecraft:cooked_rabbit", - "id" : 289 - }, - { - "name" : "minecraft:cooked_salmon", - "id" : 269 - }, - { - "name" : "minecraft:cookie", - "id" : 271 - }, - { - "name" : "minecraft:copper_block", - "id" : -340 - }, - { - "name" : "minecraft:copper_ingot", - "id" : 504 - }, - { - "name" : "minecraft:copper_ore", - "id" : -311 - }, - { - "name" : "minecraft:coral", - "id" : -131 - }, - { - "name" : "minecraft:coral_block", - "id" : -132 - }, - { - "name" : "minecraft:coral_fan", - "id" : -133 - }, - { - "name" : "minecraft:coral_fan_dead", - "id" : -134 - }, - { - "name" : "minecraft:coral_fan_hang", - "id" : -135 - }, - { - "name" : "minecraft:coral_fan_hang2", - "id" : -136 - }, - { - "name" : "minecraft:coral_fan_hang3", - "id" : -137 - }, - { - "name" : "minecraft:cow_spawn_egg", - "id" : 436 - }, - { - "name" : "minecraft:cracked_deepslate_bricks", - "id" : -410 - }, - { - "name" : "minecraft:cracked_deepslate_tiles", - "id" : -409 - }, - { - "name" : "minecraft:cracked_nether_bricks", - "id" : -303 - }, - { - "name" : "minecraft:cracked_polished_blackstone_bricks", - "id" : -280 - }, - { - "name" : "minecraft:crafting_table", - "id" : 58 - }, - { - "name" : "minecraft:creeper_banner_pattern", - "id" : 582 - }, - { - "name" : "minecraft:creeper_spawn_egg", - "id" : 441 - }, - { - "name" : "minecraft:crimson_button", - "id" : -260 - }, - { - "name" : "minecraft:crimson_door", - "id" : 616 - }, - { - "name" : "minecraft:crimson_double_slab", - "id" : -266 - }, - { - "name" : "minecraft:crimson_fence", - "id" : -256 - }, - { - "name" : "minecraft:crimson_fence_gate", - "id" : -258 - }, - { - "name" : "minecraft:crimson_fungus", - "id" : -228 - }, - { - "name" : "minecraft:crimson_hyphae", - "id" : -299 - }, - { - "name" : "minecraft:crimson_nylium", - "id" : -232 - }, - { - "name" : "minecraft:crimson_planks", - "id" : -242 - }, - { - "name" : "minecraft:crimson_pressure_plate", - "id" : -262 - }, - { - "name" : "minecraft:crimson_roots", - "id" : -223 - }, - { - "name" : "minecraft:crimson_sign", - "id" : 614 - }, - { - "name" : "minecraft:crimson_slab", - "id" : -264 - }, - { - "name" : "minecraft:crimson_stairs", - "id" : -254 - }, - { - "name" : "minecraft:crimson_standing_sign", - "id" : -250 - }, - { - "name" : "minecraft:crimson_stem", - "id" : -225 - }, - { - "name" : "minecraft:crimson_trapdoor", - "id" : -246 - }, - { - "name" : "minecraft:crimson_wall_sign", - "id" : -252 - }, - { - "name" : "minecraft:crossbow", - "id" : 575 - }, - { - "name" : "minecraft:crying_obsidian", - "id" : -289 - }, - { - "name" : "minecraft:cut_copper", - "id" : -347 - }, - { - "name" : "minecraft:cut_copper_slab", - "id" : -361 - }, - { - "name" : "minecraft:cut_copper_stairs", - "id" : -354 - }, - { - "name" : "minecraft:cyan_candle", - "id" : -422 - }, - { - "name" : "minecraft:cyan_candle_cake", - "id" : -439 - }, - { - "name" : "minecraft:cyan_dye", - "id" : 401 - }, - { - "name" : "minecraft:cyan_glazed_terracotta", - "id" : 229 - }, - { - "name" : "minecraft:dark_oak_boat", - "id" : 380 - }, - { - "name" : "minecraft:dark_oak_button", - "id" : -142 - }, - { - "name" : "minecraft:dark_oak_chest_boat", - "id" : 643 - }, - { - "name" : "minecraft:dark_oak_door", - "id" : 557 - }, - { - "name" : "minecraft:dark_oak_fence_gate", - "id" : 186 - }, - { - "name" : "minecraft:dark_oak_pressure_plate", - "id" : -152 - }, - { - "name" : "minecraft:dark_oak_sign", - "id" : 580 - }, - { - "name" : "minecraft:dark_oak_stairs", - "id" : 164 - }, - { - "name" : "minecraft:dark_oak_trapdoor", - "id" : -147 - }, - { - "name" : "minecraft:dark_prismarine_stairs", - "id" : -3 - }, - { - "name" : "minecraft:darkoak_standing_sign", - "id" : -192 - }, - { - "name" : "minecraft:darkoak_wall_sign", - "id" : -193 - }, - { - "name" : "minecraft:daylight_detector", - "id" : 151 - }, - { - "name" : "minecraft:daylight_detector_inverted", - "id" : 178 - }, - { - "name" : "minecraft:deadbush", - "id" : 32 - }, - { - "name" : "minecraft:deepslate", - "id" : -378 - }, - { - "name" : "minecraft:deepslate_brick_double_slab", - "id" : -399 - }, - { - "name" : "minecraft:deepslate_brick_slab", - "id" : -392 - }, - { - "name" : "minecraft:deepslate_brick_stairs", - "id" : -393 - }, - { - "name" : "minecraft:deepslate_brick_wall", - "id" : -394 - }, - { - "name" : "minecraft:deepslate_bricks", - "id" : -391 - }, - { - "name" : "minecraft:deepslate_coal_ore", - "id" : -406 - }, - { - "name" : "minecraft:deepslate_copper_ore", - "id" : -408 - }, - { - "name" : "minecraft:deepslate_diamond_ore", - "id" : -405 - }, - { - "name" : "minecraft:deepslate_emerald_ore", - "id" : -407 - }, - { - "name" : "minecraft:deepslate_gold_ore", - "id" : -402 - }, - { - "name" : "minecraft:deepslate_iron_ore", - "id" : -401 - }, - { - "name" : "minecraft:deepslate_lapis_ore", - "id" : -400 - }, - { - "name" : "minecraft:deepslate_redstone_ore", - "id" : -403 - }, - { - "name" : "minecraft:deepslate_tile_double_slab", - "id" : -398 - }, - { - "name" : "minecraft:deepslate_tile_slab", - "id" : -388 - }, - { - "name" : "minecraft:deepslate_tile_stairs", - "id" : -389 - }, - { - "name" : "minecraft:deepslate_tile_wall", - "id" : -390 - }, - { - "name" : "minecraft:deepslate_tiles", - "id" : -387 - }, - { - "name" : "minecraft:deny", - "id" : 211 - }, - { - "name" : "minecraft:detector_rail", - "id" : 28 - }, - { - "name" : "minecraft:diamond", - "id" : 304 - }, - { - "name" : "minecraft:diamond_axe", - "id" : 319 - }, - { - "name" : "minecraft:diamond_block", - "id" : 57 - }, - { - "name" : "minecraft:diamond_boots", - "id" : 350 - }, - { - "name" : "minecraft:diamond_chestplate", - "id" : 348 - }, - { - "name" : "minecraft:diamond_helmet", - "id" : 347 - }, - { - "name" : "minecraft:diamond_hoe", - "id" : 332 - }, - { - "name" : "minecraft:diamond_horse_armor", - "id" : 533 - }, - { - "name" : "minecraft:diamond_leggings", - "id" : 349 - }, - { - "name" : "minecraft:diamond_ore", - "id" : 56 - }, - { - "name" : "minecraft:diamond_pickaxe", - "id" : 318 - }, - { - "name" : "minecraft:diamond_shovel", - "id" : 317 - }, - { - "name" : "minecraft:diamond_sword", - "id" : 316 - }, - { - "name" : "minecraft:diorite_stairs", - "id" : -170 - }, - { - "name" : "minecraft:dirt", - "id" : 3 - }, - { - "name" : "minecraft:dirt_with_roots", - "id" : -318 - }, - { - "name" : "minecraft:disc_fragment_5", - "id" : 637 - }, - { - "name" : "minecraft:dispenser", - "id" : 23 - }, - { - "name" : "minecraft:dolphin_spawn_egg", - "id" : 484 - }, - { - "name" : "minecraft:donkey_spawn_egg", - "id" : 465 - }, - { - "name" : "minecraft:double_cut_copper_slab", - "id" : -368 - }, - { - "name" : "minecraft:double_plant", - "id" : 175 - }, - { - "name" : "minecraft:double_stone_block_slab", - "id" : 43 - }, - { - "name" : "minecraft:double_stone_block_slab2", - "id" : 181 - }, - { - "name" : "minecraft:double_stone_block_slab3", - "id" : -167 - }, - { - "name" : "minecraft:double_stone_block_slab4", - "id" : -168 - }, - { - "name" : "minecraft:double_wooden_slab", - "id" : 157 - }, - { - "name" : "minecraft:dragon_breath", - "id" : 560 - }, - { - "name" : "minecraft:dragon_egg", - "id" : 122 - }, - { - "name" : "minecraft:dried_kelp", - "id" : 270 - }, - { - "name" : "minecraft:dried_kelp_block", - "id" : -139 - }, - { - "name" : "minecraft:dripstone_block", - "id" : -317 - }, - { - "name" : "minecraft:dropper", - "id" : 125 - }, - { - "name" : "minecraft:drowned_spawn_egg", - "id" : 483 - }, - { - "name" : "minecraft:dye", - "id" : 650 - }, - { - "name" : "minecraft:echo_shard", - "id" : 647 - }, - { - "name" : "minecraft:egg", - "id" : 390 - }, - { - "name" : "minecraft:elder_guardian_spawn_egg", - "id" : 471 - }, - { - "name" : "minecraft:element_0", - "id" : 36 - }, - { - "name" : "minecraft:element_1", - "id" : -12 - }, - { - "name" : "minecraft:element_10", - "id" : -21 - }, - { - "name" : "minecraft:element_100", - "id" : -111 - }, - { - "name" : "minecraft:element_101", - "id" : -112 - }, - { - "name" : "minecraft:element_102", - "id" : -113 - }, - { - "name" : "minecraft:element_103", - "id" : -114 - }, - { - "name" : "minecraft:element_104", - "id" : -115 - }, - { - "name" : "minecraft:element_105", - "id" : -116 - }, - { - "name" : "minecraft:element_106", - "id" : -117 - }, - { - "name" : "minecraft:element_107", - "id" : -118 - }, - { - "name" : "minecraft:element_108", - "id" : -119 - }, - { - "name" : "minecraft:element_109", - "id" : -120 - }, - { - "name" : "minecraft:element_11", - "id" : -22 - }, - { - "name" : "minecraft:element_110", - "id" : -121 - }, - { - "name" : "minecraft:element_111", - "id" : -122 - }, - { - "name" : "minecraft:element_112", - "id" : -123 - }, - { - "name" : "minecraft:element_113", - "id" : -124 - }, - { - "name" : "minecraft:element_114", - "id" : -125 - }, - { - "name" : "minecraft:element_115", - "id" : -126 - }, - { - "name" : "minecraft:element_116", - "id" : -127 - }, - { - "name" : "minecraft:element_117", - "id" : -128 - }, - { - "name" : "minecraft:element_118", - "id" : -129 - }, - { - "name" : "minecraft:element_12", - "id" : -23 - }, - { - "name" : "minecraft:element_13", - "id" : -24 - }, - { - "name" : "minecraft:element_14", - "id" : -25 - }, - { - "name" : "minecraft:element_15", - "id" : -26 - }, - { - "name" : "minecraft:element_16", - "id" : -27 - }, - { - "name" : "minecraft:element_17", - "id" : -28 - }, - { - "name" : "minecraft:element_18", - "id" : -29 - }, - { - "name" : "minecraft:element_19", - "id" : -30 - }, - { - "name" : "minecraft:element_2", - "id" : -13 - }, - { - "name" : "minecraft:element_20", - "id" : -31 - }, - { - "name" : "minecraft:element_21", - "id" : -32 - }, - { - "name" : "minecraft:element_22", - "id" : -33 - }, - { - "name" : "minecraft:element_23", - "id" : -34 - }, - { - "name" : "minecraft:element_24", - "id" : -35 - }, - { - "name" : "minecraft:element_25", - "id" : -36 - }, - { - "name" : "minecraft:element_26", - "id" : -37 - }, - { - "name" : "minecraft:element_27", - "id" : -38 - }, - { - "name" : "minecraft:element_28", - "id" : -39 - }, - { - "name" : "minecraft:element_29", - "id" : -40 - }, - { - "name" : "minecraft:element_3", - "id" : -14 - }, - { - "name" : "minecraft:element_30", - "id" : -41 - }, - { - "name" : "minecraft:element_31", - "id" : -42 - }, - { - "name" : "minecraft:element_32", - "id" : -43 - }, - { - "name" : "minecraft:element_33", - "id" : -44 - }, - { - "name" : "minecraft:element_34", - "id" : -45 - }, - { - "name" : "minecraft:element_35", - "id" : -46 - }, - { - "name" : "minecraft:element_36", - "id" : -47 - }, - { - "name" : "minecraft:element_37", - "id" : -48 - }, - { - "name" : "minecraft:element_38", - "id" : -49 - }, - { - "name" : "minecraft:element_39", - "id" : -50 - }, - { - "name" : "minecraft:element_4", - "id" : -15 - }, - { - "name" : "minecraft:element_40", - "id" : -51 - }, - { - "name" : "minecraft:element_41", - "id" : -52 - }, - { - "name" : "minecraft:element_42", - "id" : -53 - }, - { - "name" : "minecraft:element_43", - "id" : -54 - }, - { - "name" : "minecraft:element_44", - "id" : -55 - }, - { - "name" : "minecraft:element_45", - "id" : -56 - }, - { - "name" : "minecraft:element_46", - "id" : -57 - }, - { - "name" : "minecraft:element_47", - "id" : -58 - }, - { - "name" : "minecraft:element_48", - "id" : -59 - }, - { - "name" : "minecraft:element_49", - "id" : -60 - }, - { - "name" : "minecraft:element_5", - "id" : -16 - }, - { - "name" : "minecraft:element_50", - "id" : -61 - }, - { - "name" : "minecraft:element_51", - "id" : -62 - }, - { - "name" : "minecraft:element_52", - "id" : -63 - }, - { - "name" : "minecraft:element_53", - "id" : -64 - }, - { - "name" : "minecraft:element_54", - "id" : -65 - }, - { - "name" : "minecraft:element_55", - "id" : -66 - }, - { - "name" : "minecraft:element_56", - "id" : -67 - }, - { - "name" : "minecraft:element_57", - "id" : -68 - }, - { - "name" : "minecraft:element_58", - "id" : -69 - }, - { - "name" : "minecraft:element_59", - "id" : -70 - }, - { - "name" : "minecraft:element_6", - "id" : -17 - }, - { - "name" : "minecraft:element_60", - "id" : -71 - }, - { - "name" : "minecraft:element_61", - "id" : -72 - }, - { - "name" : "minecraft:element_62", - "id" : -73 - }, - { - "name" : "minecraft:element_63", - "id" : -74 - }, - { - "name" : "minecraft:element_64", - "id" : -75 - }, - { - "name" : "minecraft:element_65", - "id" : -76 - }, - { - "name" : "minecraft:element_66", - "id" : -77 - }, - { - "name" : "minecraft:element_67", - "id" : -78 - }, - { - "name" : "minecraft:element_68", - "id" : -79 - }, - { - "name" : "minecraft:element_69", - "id" : -80 - }, - { - "name" : "minecraft:element_7", - "id" : -18 - }, - { - "name" : "minecraft:element_70", - "id" : -81 - }, - { - "name" : "minecraft:element_71", - "id" : -82 - }, - { - "name" : "minecraft:element_72", - "id" : -83 - }, - { - "name" : "minecraft:element_73", - "id" : -84 - }, - { - "name" : "minecraft:element_74", - "id" : -85 - }, - { - "name" : "minecraft:element_75", - "id" : -86 - }, - { - "name" : "minecraft:element_76", - "id" : -87 - }, - { - "name" : "minecraft:element_77", - "id" : -88 - }, - { - "name" : "minecraft:element_78", - "id" : -89 - }, - { - "name" : "minecraft:element_79", - "id" : -90 - }, - { - "name" : "minecraft:element_8", - "id" : -19 - }, - { - "name" : "minecraft:element_80", - "id" : -91 - }, - { - "name" : "minecraft:element_81", - "id" : -92 - }, - { - "name" : "minecraft:element_82", - "id" : -93 - }, - { - "name" : "minecraft:element_83", - "id" : -94 - }, - { - "name" : "minecraft:element_84", - "id" : -95 - }, - { - "name" : "minecraft:element_85", - "id" : -96 - }, - { - "name" : "minecraft:element_86", - "id" : -97 - }, - { - "name" : "minecraft:element_87", - "id" : -98 - }, - { - "name" : "minecraft:element_88", - "id" : -99 - }, - { - "name" : "minecraft:element_89", - "id" : -100 - }, - { - "name" : "minecraft:element_9", - "id" : -20 - }, - { - "name" : "minecraft:element_90", - "id" : -101 - }, - { - "name" : "minecraft:element_91", - "id" : -102 - }, - { - "name" : "minecraft:element_92", - "id" : -103 - }, - { - "name" : "minecraft:element_93", - "id" : -104 - }, - { - "name" : "minecraft:element_94", - "id" : -105 - }, - { - "name" : "minecraft:element_95", - "id" : -106 - }, - { - "name" : "minecraft:element_96", - "id" : -107 - }, - { - "name" : "minecraft:element_97", - "id" : -108 - }, - { - "name" : "minecraft:element_98", - "id" : -109 - }, - { - "name" : "minecraft:element_99", - "id" : -110 - }, - { - "name" : "minecraft:elytra", - "id" : 564 - }, - { - "name" : "minecraft:emerald", - "id" : 512 - }, - { - "name" : "minecraft:emerald_block", - "id" : 133 - }, - { - "name" : "minecraft:emerald_ore", - "id" : 129 - }, - { - "name" : "minecraft:empty_map", - "id" : 515 - }, - { - "name" : "minecraft:enchanted_book", - "id" : 521 - }, - { - "name" : "minecraft:enchanted_golden_apple", - "id" : 259 - }, - { - "name" : "minecraft:enchanting_table", - "id" : 116 - }, - { - "name" : "minecraft:end_brick_stairs", - "id" : -178 - }, - { - "name" : "minecraft:end_bricks", - "id" : 206 - }, - { - "name" : "minecraft:end_crystal", - "id" : 653 - }, - { - "name" : "minecraft:end_gateway", - "id" : 209 - }, - { - "name" : "minecraft:end_portal", - "id" : 119 - }, - { - "name" : "minecraft:end_portal_frame", - "id" : 120 - }, - { - "name" : "minecraft:end_rod", - "id" : 208 - }, - { - "name" : "minecraft:end_stone", - "id" : 121 - }, - { - "name" : "minecraft:ender_chest", - "id" : 130 - }, - { - "name" : "minecraft:ender_eye", - "id" : 433 - }, - { - "name" : "minecraft:ender_pearl", - "id" : 422 - }, - { - "name" : "minecraft:enderman_spawn_egg", - "id" : 442 - }, - { - "name" : "minecraft:endermite_spawn_egg", - "id" : 460 - }, - { - "name" : "minecraft:evoker_spawn_egg", - "id" : 475 - }, - { - "name" : "minecraft:experience_bottle", - "id" : 508 - }, - { - "name" : "minecraft:exposed_copper", - "id" : -341 - }, - { - "name" : "minecraft:exposed_cut_copper", - "id" : -348 - }, - { - "name" : "minecraft:exposed_cut_copper_slab", - "id" : -362 - }, - { - "name" : "minecraft:exposed_cut_copper_stairs", - "id" : -355 - }, - { - "name" : "minecraft:exposed_double_cut_copper_slab", - "id" : -369 - }, - { - "name" : "minecraft:farmland", - "id" : 60 - }, - { - "name" : "minecraft:feather", - "id" : 327 - }, - { - "name" : "minecraft:fence", - "id" : 85 - }, - { - "name" : "minecraft:fence_gate", - "id" : 107 - }, - { - "name" : "minecraft:fermented_spider_eye", - "id" : 428 - }, - { - "name" : "minecraft:field_masoned_banner_pattern", - "id" : 585 - }, - { - "name" : "minecraft:filled_map", - "id" : 420 - }, - { - "name" : "minecraft:fire", - "id" : 51 - }, - { - "name" : "minecraft:fire_charge", - "id" : 509 - }, - { - "name" : "minecraft:firework_rocket", - "id" : 519 - }, - { - "name" : "minecraft:firework_star", - "id" : 520 - }, - { - "name" : "minecraft:fishing_rod", - "id" : 392 - }, - { - "name" : "minecraft:fletching_table", - "id" : -201 - }, - { - "name" : "minecraft:flint", - "id" : 356 - }, - { - "name" : "minecraft:flint_and_steel", - "id" : 299 - }, - { - "name" : "minecraft:flower_banner_pattern", - "id" : 581 - }, - { - "name" : "minecraft:flower_pot", - "id" : 514 - }, - { - "name" : "minecraft:flowering_azalea", - "id" : -338 - }, - { - "name" : "minecraft:flowing_lava", - "id" : 10 - }, - { - "name" : "minecraft:flowing_water", - "id" : 8 - }, - { - "name" : "minecraft:fox_spawn_egg", - "id" : 490 - }, - { - "name" : "minecraft:frame", - "id" : 513 - }, - { - "name" : "minecraft:frog_spawn", - "id" : -468 - }, - { - "name" : "minecraft:frog_spawn_egg", - "id" : 628 - }, - { - "name" : "minecraft:frosted_ice", - "id" : 207 - }, - { - "name" : "minecraft:furnace", - "id" : 61 - }, - { - "name" : "minecraft:ghast_spawn_egg", - "id" : 454 - }, - { - "name" : "minecraft:ghast_tear", - "id" : 424 - }, - { - "name" : "minecraft:gilded_blackstone", - "id" : -281 - }, - { - "name" : "minecraft:glass", - "id" : 20 - }, - { - "name" : "minecraft:glass_bottle", - "id" : 427 - }, - { - "name" : "minecraft:glass_pane", - "id" : 102 - }, - { - "name" : "minecraft:glistering_melon_slice", - "id" : 434 - }, - { - "name" : "minecraft:globe_banner_pattern", - "id" : 588 - }, - { - "name" : "minecraft:glow_berries", - "id" : 654 - }, - { - "name" : "minecraft:glow_frame", - "id" : 623 - }, - { - "name" : "minecraft:glow_ink_sac", - "id" : 503 - }, - { - "name" : "minecraft:glow_lichen", - "id" : -411 - }, - { - "name" : "minecraft:glow_squid_spawn_egg", - "id" : 502 - }, - { - "name" : "minecraft:glow_stick", - "id" : 601 - }, - { - "name" : "minecraft:glowingobsidian", - "id" : 246 - }, - { - "name" : "minecraft:glowstone", - "id" : 89 - }, - { - "name" : "minecraft:glowstone_dust", - "id" : 394 - }, - { - "name" : "minecraft:goat_horn", - "id" : 627 - }, - { - "name" : "minecraft:goat_spawn_egg", - "id" : 501 - }, - { - "name" : "minecraft:gold_block", - "id" : 41 - }, - { - "name" : "minecraft:gold_ingot", - "id" : 306 - }, - { - "name" : "minecraft:gold_nugget", - "id" : 425 - }, - { - "name" : "minecraft:gold_ore", - "id" : 14 - }, - { - "name" : "minecraft:golden_apple", - "id" : 258 - }, - { - "name" : "minecraft:golden_axe", - "id" : 325 - }, - { - "name" : "minecraft:golden_boots", - "id" : 354 - }, - { - "name" : "minecraft:golden_carrot", - "id" : 283 - }, - { - "name" : "minecraft:golden_chestplate", - "id" : 352 - }, - { - "name" : "minecraft:golden_helmet", - "id" : 351 - }, - { - "name" : "minecraft:golden_hoe", - "id" : 333 - }, - { - "name" : "minecraft:golden_horse_armor", - "id" : 532 - }, - { - "name" : "minecraft:golden_leggings", - "id" : 353 - }, - { - "name" : "minecraft:golden_pickaxe", - "id" : 324 - }, - { - "name" : "minecraft:golden_rail", - "id" : 27 - }, - { - "name" : "minecraft:golden_shovel", - "id" : 323 - }, - { - "name" : "minecraft:golden_sword", - "id" : 322 - }, - { - "name" : "minecraft:granite_stairs", - "id" : -169 - }, - { - "name" : "minecraft:grass", - "id" : 2 - }, - { - "name" : "minecraft:grass_path", - "id" : 198 - }, - { - "name" : "minecraft:gravel", - "id" : 13 - }, - { - "name" : "minecraft:gray_candle", - "id" : -420 - }, - { - "name" : "minecraft:gray_candle_cake", - "id" : -437 - }, - { - "name" : "minecraft:gray_dye", - "id" : 403 - }, - { - "name" : "minecraft:gray_glazed_terracotta", - "id" : 227 - }, - { - "name" : "minecraft:green_candle", - "id" : -426 - }, - { - "name" : "minecraft:green_candle_cake", - "id" : -443 - }, - { - "name" : "minecraft:green_dye", - "id" : 397 - }, - { - "name" : "minecraft:green_glazed_terracotta", - "id" : 233 - }, - { - "name" : "minecraft:grindstone", - "id" : -195 - }, - { - "name" : "minecraft:guardian_spawn_egg", - "id" : 461 - }, - { - "name" : "minecraft:gunpowder", - "id" : 328 - }, - { - "name" : "minecraft:hanging_roots", - "id" : -319 - }, - { - "name" : "minecraft:hard_glass", - "id" : 253 - }, - { - "name" : "minecraft:hard_glass_pane", - "id" : 190 - }, - { - "name" : "minecraft:hard_stained_glass", - "id" : 254 - }, - { - "name" : "minecraft:hard_stained_glass_pane", - "id" : 191 - }, - { - "name" : "minecraft:hardened_clay", - "id" : 172 - }, - { - "name" : "minecraft:hay_block", - "id" : 170 - }, - { - "name" : "minecraft:heart_of_the_sea", - "id" : 571 - }, - { - "name" : "minecraft:heavy_weighted_pressure_plate", - "id" : 148 - }, - { - "name" : "minecraft:hoglin_spawn_egg", - "id" : 496 - }, - { - "name" : "minecraft:honey_block", - "id" : -220 - }, - { - "name" : "minecraft:honey_bottle", - "id" : 592 - }, - { - "name" : "minecraft:honeycomb", - "id" : 591 - }, - { - "name" : "minecraft:honeycomb_block", - "id" : -221 - }, - { - "name" : "minecraft:hopper", - "id" : 527 - }, - { - "name" : "minecraft:hopper_minecart", - "id" : 526 - }, - { - "name" : "minecraft:horse_spawn_egg", - "id" : 458 - }, - { - "name" : "minecraft:husk_spawn_egg", - "id" : 463 - }, - { - "name" : "minecraft:ice", - "id" : 79 - }, - { - "name" : "minecraft:ice_bomb", - "id" : 595 - }, - { - "name" : "minecraft:infested_deepslate", - "id" : -454 - }, - { - "name" : "minecraft:info_update", - "id" : 248 - }, - { - "name" : "minecraft:info_update2", - "id" : 249 - }, - { - "name" : "minecraft:ink_sac", - "id" : 413 - }, - { - "name" : "minecraft:invisible_bedrock", - "id" : 95 - }, - { - "name" : "minecraft:iron_axe", - "id" : 298 - }, - { - "name" : "minecraft:iron_bars", - "id" : 101 - }, - { - "name" : "minecraft:iron_block", - "id" : 42 - }, - { - "name" : "minecraft:iron_boots", - "id" : 346 - }, - { - "name" : "minecraft:iron_chestplate", - "id" : 344 - }, - { - "name" : "minecraft:iron_door", - "id" : 372 - }, - { - "name" : "minecraft:iron_helmet", - "id" : 343 - }, - { - "name" : "minecraft:iron_hoe", - "id" : 331 - }, - { - "name" : "minecraft:iron_horse_armor", - "id" : 531 - }, - { - "name" : "minecraft:iron_ingot", - "id" : 305 - }, - { - "name" : "minecraft:iron_leggings", - "id" : 345 - }, - { - "name" : "minecraft:iron_nugget", - "id" : 569 - }, - { - "name" : "minecraft:iron_ore", - "id" : 15 - }, - { - "name" : "minecraft:iron_pickaxe", - "id" : 297 - }, - { - "name" : "minecraft:iron_shovel", - "id" : 296 - }, - { - "name" : "minecraft:iron_sword", - "id" : 307 - }, - { - "name" : "minecraft:iron_trapdoor", - "id" : 167 - }, - { - "name" : "minecraft:item.acacia_door", - "id" : 196 - }, - { - "name" : "minecraft:item.bed", - "id" : 26 - }, - { - "name" : "minecraft:item.beetroot", - "id" : 244 - }, - { - "name" : "minecraft:item.birch_door", - "id" : 194 - }, - { - "name" : "minecraft:item.brewing_stand", - "id" : 117 - }, - { - "name" : "minecraft:item.cake", - "id" : 92 - }, - { - "name" : "minecraft:item.camera", - "id" : 242 - }, - { - "name" : "minecraft:item.campfire", - "id" : -209 - }, - { - "name" : "minecraft:item.cauldron", - "id" : 118 - }, - { - "name" : "minecraft:item.chain", - "id" : -286 - }, - { - "name" : "minecraft:item.crimson_door", - "id" : -244 - }, - { - "name" : "minecraft:item.dark_oak_door", - "id" : 197 - }, - { - "name" : "minecraft:item.flower_pot", - "id" : 140 - }, - { - "name" : "minecraft:item.frame", - "id" : 199 - }, - { - "name" : "minecraft:item.glow_frame", - "id" : -339 - }, - { - "name" : "minecraft:item.hopper", - "id" : 154 - }, - { - "name" : "minecraft:item.iron_door", - "id" : 71 - }, - { - "name" : "minecraft:item.jungle_door", - "id" : 195 - }, - { - "name" : "minecraft:item.kelp", - "id" : -138 - }, - { - "name" : "minecraft:item.mangrove_door", - "id" : -493 - }, - { - "name" : "minecraft:item.nether_sprouts", - "id" : -238 - }, - { - "name" : "minecraft:item.nether_wart", - "id" : 115 - }, - { - "name" : "minecraft:item.reeds", - "id" : 83 - }, - { - "name" : "minecraft:item.skull", - "id" : 144 - }, - { - "name" : "minecraft:item.soul_campfire", - "id" : -290 - }, - { - "name" : "minecraft:item.spruce_door", - "id" : 193 - }, - { - "name" : "minecraft:item.warped_door", - "id" : -245 - }, - { - "name" : "minecraft:item.wheat", - "id" : 59 - }, - { - "name" : "minecraft:item.wooden_door", - "id" : 64 - }, - { - "name" : "minecraft:jigsaw", - "id" : -211 - }, - { - "name" : "minecraft:jukebox", - "id" : 84 - }, - { - "name" : "minecraft:jungle_boat", - "id" : 377 - }, - { - "name" : "minecraft:jungle_button", - "id" : -143 - }, - { - "name" : "minecraft:jungle_chest_boat", - "id" : 640 - }, - { - "name" : "minecraft:jungle_door", - "id" : 555 - }, - { - "name" : "minecraft:jungle_fence_gate", - "id" : 185 - }, - { - "name" : "minecraft:jungle_pressure_plate", - "id" : -153 - }, - { - "name" : "minecraft:jungle_sign", - "id" : 578 - }, - { - "name" : "minecraft:jungle_stairs", - "id" : 136 - }, - { - "name" : "minecraft:jungle_standing_sign", - "id" : -188 - }, - { - "name" : "minecraft:jungle_trapdoor", - "id" : -148 - }, - { - "name" : "minecraft:jungle_wall_sign", - "id" : -189 - }, - { - "name" : "minecraft:kelp", - "id" : 382 - }, - { - "name" : "minecraft:ladder", - "id" : 65 - }, - { - "name" : "minecraft:lantern", - "id" : -208 - }, - { - "name" : "minecraft:lapis_block", - "id" : 22 - }, - { - "name" : "minecraft:lapis_lazuli", - "id" : 414 - }, - { - "name" : "minecraft:lapis_ore", - "id" : 21 - }, - { - "name" : "minecraft:large_amethyst_bud", - "id" : -330 - }, - { - "name" : "minecraft:lava", - "id" : 11 - }, - { - "name" : "minecraft:lava_bucket", - "id" : 363 - }, - { - "name" : "minecraft:lava_cauldron", - "id" : -210 - }, - { - "name" : "minecraft:lead", - "id" : 547 - }, - { - "name" : "minecraft:leather", - "id" : 381 - }, - { - "name" : "minecraft:leather_boots", - "id" : 338 - }, - { - "name" : "minecraft:leather_chestplate", - "id" : 336 - }, - { - "name" : "minecraft:leather_helmet", - "id" : 335 - }, - { - "name" : "minecraft:leather_horse_armor", - "id" : 530 - }, - { - "name" : "minecraft:leather_leggings", - "id" : 337 - }, - { - "name" : "minecraft:leaves", - "id" : 18 - }, - { - "name" : "minecraft:leaves2", - "id" : 161 - }, - { - "name" : "minecraft:lectern", - "id" : -194 - }, - { - "name" : "minecraft:lever", - "id" : 69 - }, - { - "name" : "minecraft:light_block", - "id" : -215 - }, - { - "name" : "minecraft:light_blue_candle", - "id" : -416 - }, - { - "name" : "minecraft:light_blue_candle_cake", - "id" : -433 - }, - { - "name" : "minecraft:light_blue_dye", - "id" : 407 - }, - { - "name" : "minecraft:light_blue_glazed_terracotta", - "id" : 223 - }, - { - "name" : "minecraft:light_gray_candle", - "id" : -421 - }, - { - "name" : "minecraft:light_gray_candle_cake", - "id" : -438 - }, - { - "name" : "minecraft:light_gray_dye", - "id" : 402 - }, - { - "name" : "minecraft:light_weighted_pressure_plate", - "id" : 147 - }, - { - "name" : "minecraft:lightning_rod", - "id" : -312 - }, - { - "name" : "minecraft:lime_candle", - "id" : -418 - }, - { - "name" : "minecraft:lime_candle_cake", - "id" : -435 - }, - { - "name" : "minecraft:lime_dye", - "id" : 405 - }, - { - "name" : "minecraft:lime_glazed_terracotta", - "id" : 225 - }, - { - "name" : "minecraft:lingering_potion", - "id" : 562 - }, - { - "name" : "minecraft:lit_blast_furnace", - "id" : -214 - }, - { - "name" : "minecraft:lit_deepslate_redstone_ore", - "id" : -404 - }, - { - "name" : "minecraft:lit_furnace", - "id" : 62 - }, - { - "name" : "minecraft:lit_pumpkin", - "id" : 91 - }, - { - "name" : "minecraft:lit_redstone_lamp", - "id" : 124 - }, - { - "name" : "minecraft:lit_redstone_ore", - "id" : 74 - }, - { - "name" : "minecraft:lit_smoker", - "id" : -199 - }, - { - "name" : "minecraft:llama_spawn_egg", - "id" : 473 - }, - { - "name" : "minecraft:lodestone", - "id" : -222 - }, - { - "name" : "minecraft:lodestone_compass", - "id" : 602 - }, - { - "name" : "minecraft:log", - "id" : 17 - }, - { - "name" : "minecraft:log2", - "id" : 162 - }, - { - "name" : "minecraft:loom", - "id" : -204 - }, - { - "name" : "minecraft:magenta_candle", - "id" : -415 - }, - { - "name" : "minecraft:magenta_candle_cake", - "id" : -432 - }, - { - "name" : "minecraft:magenta_dye", - "id" : 408 - }, - { - "name" : "minecraft:magenta_glazed_terracotta", - "id" : 222 - }, - { - "name" : "minecraft:magma", - "id" : 213 - }, - { - "name" : "minecraft:magma_cream", - "id" : 430 - }, - { - "name" : "minecraft:magma_cube_spawn_egg", - "id" : 455 - }, - { - "name" : "minecraft:mangrove_boat", - "id" : 635 - }, - { - "name" : "minecraft:mangrove_button", - "id" : -487 - }, - { - "name" : "minecraft:mangrove_chest_boat", - "id" : 644 - }, - { - "name" : "minecraft:mangrove_door", - "id" : 633 - }, - { - "name" : "minecraft:mangrove_double_slab", - "id" : -499 - }, - { - "name" : "minecraft:mangrove_fence", - "id" : -491 - }, - { - "name" : "minecraft:mangrove_fence_gate", - "id" : -492 - }, - { - "name" : "minecraft:mangrove_leaves", - "id" : -472 - }, - { - "name" : "minecraft:mangrove_log", - "id" : -484 - }, - { - "name" : "minecraft:mangrove_planks", - "id" : -486 - }, - { - "name" : "minecraft:mangrove_pressure_plate", - "id" : -490 - }, - { - "name" : "minecraft:mangrove_propagule", - "id" : -474 - }, - { - "name" : "minecraft:mangrove_roots", - "id" : -482 - }, - { - "name" : "minecraft:mangrove_sign", - "id" : 634 - }, - { - "name" : "minecraft:mangrove_slab", - "id" : -489 - }, - { - "name" : "minecraft:mangrove_stairs", - "id" : -488 - }, - { - "name" : "minecraft:mangrove_standing_sign", - "id" : -494 - }, - { - "name" : "minecraft:mangrove_trapdoor", - "id" : -496 - }, - { - "name" : "minecraft:mangrove_wall_sign", - "id" : -495 - }, - { - "name" : "minecraft:mangrove_wood", - "id" : -497 - }, - { - "name" : "minecraft:medicine", - "id" : 599 - }, - { - "name" : "minecraft:medium_amethyst_bud", - "id" : -331 - }, - { - "name" : "minecraft:melon_block", - "id" : 103 - }, - { - "name" : "minecraft:melon_seeds", - "id" : 293 - }, - { - "name" : "minecraft:melon_slice", - "id" : 272 - }, - { - "name" : "minecraft:melon_stem", - "id" : 105 - }, - { - "name" : "minecraft:milk_bucket", - "id" : 361 - }, - { - "name" : "minecraft:minecart", - "id" : 370 - }, - { - "name" : "minecraft:mob_spawner", - "id" : 52 - }, - { - "name" : "minecraft:mojang_banner_pattern", - "id" : 584 - }, - { - "name" : "minecraft:monster_egg", - "id" : 97 - }, - { - "name" : "minecraft:mooshroom_spawn_egg", - "id" : 440 - }, - { - "name" : "minecraft:moss_block", - "id" : -320 - }, - { - "name" : "minecraft:moss_carpet", - "id" : -335 - }, - { - "name" : "minecraft:mossy_cobblestone", - "id" : 48 - }, - { - "name" : "minecraft:mossy_cobblestone_stairs", - "id" : -179 - }, - { - "name" : "minecraft:mossy_stone_brick_stairs", - "id" : -175 - }, - { - "name" : "minecraft:moving_block", - "id" : 250 - }, - { - "name" : "minecraft:mud", - "id" : -473 - }, - { - "name" : "minecraft:mud_brick_double_slab", - "id" : -479 - }, - { - "name" : "minecraft:mud_brick_slab", - "id" : -478 - }, - { - "name" : "minecraft:mud_brick_stairs", - "id" : -480 - }, - { - "name" : "minecraft:mud_brick_wall", - "id" : -481 - }, - { - "name" : "minecraft:mud_bricks", - "id" : -475 - }, - { - "name" : "minecraft:muddy_mangrove_roots", - "id" : -483 - }, - { - "name" : "minecraft:mule_spawn_egg", - "id" : 466 - }, - { - "name" : "minecraft:mushroom_stew", - "id" : 260 - }, - { - "name" : "minecraft:music_disc_11", - "id" : 544 - }, - { - "name" : "minecraft:music_disc_13", - "id" : 534 - }, - { - "name" : "minecraft:music_disc_5", - "id" : 636 - }, - { - "name" : "minecraft:music_disc_blocks", - "id" : 536 - }, - { - "name" : "minecraft:music_disc_cat", - "id" : 535 - }, - { - "name" : "minecraft:music_disc_chirp", - "id" : 537 - }, - { - "name" : "minecraft:music_disc_far", - "id" : 538 - }, - { - "name" : "minecraft:music_disc_mall", - "id" : 539 - }, - { - "name" : "minecraft:music_disc_mellohi", - "id" : 540 - }, - { - "name" : "minecraft:music_disc_otherside", - "id" : 626 - }, - { - "name" : "minecraft:music_disc_pigstep", - "id" : 620 - }, - { - "name" : "minecraft:music_disc_stal", - "id" : 541 - }, - { - "name" : "minecraft:music_disc_strad", - "id" : 542 - }, - { - "name" : "minecraft:music_disc_wait", - "id" : 545 - }, - { - "name" : "minecraft:music_disc_ward", - "id" : 543 - }, - { - "name" : "minecraft:mutton", - "id" : 550 - }, - { - "name" : "minecraft:mycelium", - "id" : 110 - }, - { - "name" : "minecraft:name_tag", - "id" : 548 - }, - { - "name" : "minecraft:nautilus_shell", - "id" : 570 - }, - { - "name" : "minecraft:nether_brick", - "id" : 112 - }, - { - "name" : "minecraft:nether_brick_fence", - "id" : 113 - }, - { - "name" : "minecraft:nether_brick_stairs", - "id" : 114 - }, - { - "name" : "minecraft:nether_gold_ore", - "id" : -288 - }, - { - "name" : "minecraft:nether_sprouts", - "id" : 621 - }, - { - "name" : "minecraft:nether_star", - "id" : 518 - }, - { - "name" : "minecraft:nether_wart", - "id" : 294 - }, - { - "name" : "minecraft:nether_wart_block", - "id" : 214 - }, - { - "name" : "minecraft:netherbrick", - "id" : 523 - }, - { - "name" : "minecraft:netherite_axe", - "id" : 607 - }, - { - "name" : "minecraft:netherite_block", - "id" : -270 - }, - { - "name" : "minecraft:netherite_boots", - "id" : 612 - }, - { - "name" : "minecraft:netherite_chestplate", - "id" : 610 - }, - { - "name" : "minecraft:netherite_helmet", - "id" : 609 - }, - { - "name" : "minecraft:netherite_hoe", - "id" : 608 - }, - { - "name" : "minecraft:netherite_ingot", - "id" : 603 - }, - { - "name" : "minecraft:netherite_leggings", - "id" : 611 - }, - { - "name" : "minecraft:netherite_pickaxe", - "id" : 606 - }, - { - "name" : "minecraft:netherite_scrap", - "id" : 613 - }, - { - "name" : "minecraft:netherite_shovel", - "id" : 605 - }, - { - "name" : "minecraft:netherite_sword", - "id" : 604 - }, - { - "name" : "minecraft:netherrack", - "id" : 87 - }, - { - "name" : "minecraft:netherreactor", - "id" : 247 - }, - { - "name" : "minecraft:normal_stone_stairs", - "id" : -180 - }, - { - "name" : "minecraft:noteblock", - "id" : 25 - }, - { - "name" : "minecraft:npc_spawn_egg", - "id" : 470 - }, - { - "name" : "minecraft:oak_boat", - "id" : 375 - }, - { - "name" : "minecraft:oak_chest_boat", - "id" : 638 - }, - { - "name" : "minecraft:oak_sign", - "id" : 358 - }, - { - "name" : "minecraft:oak_stairs", - "id" : 53 - }, - { - "name" : "minecraft:observer", - "id" : 251 - }, - { - "name" : "minecraft:obsidian", - "id" : 49 - }, - { - "name" : "minecraft:ocelot_spawn_egg", - "id" : 451 - }, - { - "name" : "minecraft:ochre_froglight", - "id" : -471 - }, - { - "name" : "minecraft:orange_candle", - "id" : -414 - }, - { - "name" : "minecraft:orange_candle_cake", - "id" : -431 - }, - { - "name" : "minecraft:orange_dye", - "id" : 409 - }, - { - "name" : "minecraft:orange_glazed_terracotta", - "id" : 221 - }, - { - "name" : "minecraft:oxidized_copper", - "id" : -343 - }, - { - "name" : "minecraft:oxidized_cut_copper", - "id" : -350 - }, - { - "name" : "minecraft:oxidized_cut_copper_slab", - "id" : -364 - }, - { - "name" : "minecraft:oxidized_cut_copper_stairs", - "id" : -357 - }, - { - "name" : "minecraft:oxidized_double_cut_copper_slab", - "id" : -371 - }, - { - "name" : "minecraft:packed_ice", - "id" : 174 - }, - { - "name" : "minecraft:packed_mud", - "id" : -477 - }, - { - "name" : "minecraft:painting", - "id" : 357 - }, - { - "name" : "minecraft:panda_spawn_egg", - "id" : 489 - }, - { - "name" : "minecraft:paper", - "id" : 386 - }, - { - "name" : "minecraft:parrot_spawn_egg", - "id" : 478 - }, - { - "name" : "minecraft:pearlescent_froglight", - "id" : -469 - }, - { - "name" : "minecraft:phantom_membrane", - "id" : 574 - }, - { - "name" : "minecraft:phantom_spawn_egg", - "id" : 486 - }, - { - "name" : "minecraft:pig_spawn_egg", - "id" : 437 - }, - { - "name" : "minecraft:piglin_banner_pattern", - "id" : 587 - }, - { - "name" : "minecraft:piglin_brute_spawn_egg", - "id" : 499 - }, - { - "name" : "minecraft:piglin_spawn_egg", - "id" : 497 - }, - { - "name" : "minecraft:pillager_spawn_egg", - "id" : 491 - }, - { - "name" : "minecraft:pink_candle", - "id" : -419 - }, - { - "name" : "minecraft:pink_candle_cake", - "id" : -436 - }, - { - "name" : "minecraft:pink_dye", - "id" : 404 - }, - { - "name" : "minecraft:pink_glazed_terracotta", - "id" : 226 - }, - { - "name" : "minecraft:piston", - "id" : 33 - }, - { - "name" : "minecraft:piston_arm_collision", - "id" : 34 - }, - { - "name" : "minecraft:planks", - "id" : 5 - }, - { - "name" : "minecraft:podzol", - "id" : 243 - }, - { - "name" : "minecraft:pointed_dripstone", - "id" : -308 - }, - { - "name" : "minecraft:poisonous_potato", - "id" : 282 - }, - { - "name" : "minecraft:polar_bear_spawn_egg", - "id" : 472 - }, - { - "name" : "minecraft:polished_andesite_stairs", - "id" : -174 - }, - { - "name" : "minecraft:polished_basalt", - "id" : -235 - }, - { - "name" : "minecraft:polished_blackstone", - "id" : -291 - }, - { - "name" : "minecraft:polished_blackstone_brick_double_slab", - "id" : -285 - }, - { - "name" : "minecraft:polished_blackstone_brick_slab", - "id" : -284 - }, - { - "name" : "minecraft:polished_blackstone_brick_stairs", - "id" : -275 - }, - { - "name" : "minecraft:polished_blackstone_brick_wall", - "id" : -278 - }, - { - "name" : "minecraft:polished_blackstone_bricks", - "id" : -274 - }, - { - "name" : "minecraft:polished_blackstone_button", - "id" : -296 - }, - { - "name" : "minecraft:polished_blackstone_double_slab", - "id" : -294 - }, - { - "name" : "minecraft:polished_blackstone_pressure_plate", - "id" : -295 - }, - { - "name" : "minecraft:polished_blackstone_slab", - "id" : -293 - }, - { - "name" : "minecraft:polished_blackstone_stairs", - "id" : -292 - }, - { - "name" : "minecraft:polished_blackstone_wall", - "id" : -297 - }, - { - "name" : "minecraft:polished_deepslate", - "id" : -383 - }, - { - "name" : "minecraft:polished_deepslate_double_slab", - "id" : -397 - }, - { - "name" : "minecraft:polished_deepslate_slab", - "id" : -384 - }, - { - "name" : "minecraft:polished_deepslate_stairs", - "id" : -385 - }, - { - "name" : "minecraft:polished_deepslate_wall", - "id" : -386 - }, - { - "name" : "minecraft:polished_diorite_stairs", - "id" : -173 - }, - { - "name" : "minecraft:polished_granite_stairs", - "id" : -172 - }, - { - "name" : "minecraft:popped_chorus_fruit", - "id" : 559 - }, - { - "name" : "minecraft:porkchop", - "id" : 262 - }, - { - "name" : "minecraft:portal", - "id" : 90 - }, - { - "name" : "minecraft:potato", - "id" : 280 - }, - { - "name" : "minecraft:potatoes", - "id" : 142 - }, - { - "name" : "minecraft:potion", - "id" : 426 - }, - { - "name" : "minecraft:powder_snow", - "id" : -306 - }, - { - "name" : "minecraft:powder_snow_bucket", - "id" : 368 - }, - { - "name" : "minecraft:powered_comparator", - "id" : 150 - }, - { - "name" : "minecraft:powered_repeater", - "id" : 94 - }, - { - "name" : "minecraft:prismarine", - "id" : 168 - }, - { - "name" : "minecraft:prismarine_bricks_stairs", - "id" : -4 - }, - { - "name" : "minecraft:prismarine_crystals", - "id" : 549 - }, - { - "name" : "minecraft:prismarine_shard", - "id" : 565 - }, - { - "name" : "minecraft:prismarine_stairs", - "id" : -2 - }, - { - "name" : "minecraft:pufferfish", - "id" : 267 - }, - { - "name" : "minecraft:pufferfish_bucket", - "id" : 367 - }, - { - "name" : "minecraft:pufferfish_spawn_egg", - "id" : 481 - }, - { - "name" : "minecraft:pumpkin", - "id" : 86 - }, - { - "name" : "minecraft:pumpkin_pie", - "id" : 284 - }, - { - "name" : "minecraft:pumpkin_seeds", - "id" : 292 - }, - { - "name" : "minecraft:pumpkin_stem", - "id" : 104 - }, - { - "name" : "minecraft:purple_candle", - "id" : -423 - }, - { - "name" : "minecraft:purple_candle_cake", - "id" : -440 - }, - { - "name" : "minecraft:purple_dye", - "id" : 400 - }, - { - "name" : "minecraft:purple_glazed_terracotta", - "id" : 219 - }, - { - "name" : "minecraft:purpur_block", - "id" : 201 - }, - { - "name" : "minecraft:purpur_stairs", - "id" : 203 - }, - { - "name" : "minecraft:quartz", - "id" : 524 - }, - { - "name" : "minecraft:quartz_block", - "id" : 155 - }, - { - "name" : "minecraft:quartz_bricks", - "id" : -304 - }, - { - "name" : "minecraft:quartz_ore", - "id" : 153 - }, - { - "name" : "minecraft:quartz_stairs", - "id" : 156 - }, - { - "name" : "minecraft:rabbit", - "id" : 288 - }, - { - "name" : "minecraft:rabbit_foot", - "id" : 528 - }, - { - "name" : "minecraft:rabbit_hide", - "id" : 529 - }, - { - "name" : "minecraft:rabbit_spawn_egg", - "id" : 459 - }, - { - "name" : "minecraft:rabbit_stew", - "id" : 290 - }, - { - "name" : "minecraft:rail", - "id" : 66 - }, - { - "name" : "minecraft:rapid_fertilizer", - "id" : 597 - }, - { - "name" : "minecraft:ravager_spawn_egg", - "id" : 493 - }, - { - "name" : "minecraft:raw_copper", - "id" : 507 - }, - { - "name" : "minecraft:raw_copper_block", - "id" : -452 - }, - { - "name" : "minecraft:raw_gold", - "id" : 506 - }, - { - "name" : "minecraft:raw_gold_block", - "id" : -453 - }, - { - "name" : "minecraft:raw_iron", - "id" : 505 - }, - { - "name" : "minecraft:raw_iron_block", - "id" : -451 - }, - { - "name" : "minecraft:recovery_compass", - "id" : 646 - }, - { - "name" : "minecraft:red_candle", - "id" : -427 - }, - { - "name" : "minecraft:red_candle_cake", - "id" : -444 - }, - { - "name" : "minecraft:red_dye", - "id" : 396 - }, - { - "name" : "minecraft:red_flower", - "id" : 38 - }, - { - "name" : "minecraft:red_glazed_terracotta", - "id" : 234 - }, - { - "name" : "minecraft:red_mushroom", - "id" : 40 - }, - { - "name" : "minecraft:red_mushroom_block", - "id" : 100 - }, - { - "name" : "minecraft:red_nether_brick", - "id" : 215 - }, - { - "name" : "minecraft:red_nether_brick_stairs", - "id" : -184 - }, - { - "name" : "minecraft:red_sandstone", - "id" : 179 - }, - { - "name" : "minecraft:red_sandstone_stairs", - "id" : 180 - }, - { - "name" : "minecraft:redstone", - "id" : 373 - }, - { - "name" : "minecraft:redstone_block", - "id" : 152 - }, - { - "name" : "minecraft:redstone_lamp", - "id" : 123 - }, - { - "name" : "minecraft:redstone_ore", - "id" : 73 - }, - { - "name" : "minecraft:redstone_torch", - "id" : 76 - }, - { - "name" : "minecraft:redstone_wire", - "id" : 55 - }, - { - "name" : "minecraft:reinforced_deepslate", - "id" : -466 - }, - { - "name" : "minecraft:repeater", - "id" : 419 - }, - { - "name" : "minecraft:repeating_command_block", - "id" : 188 - }, - { - "name" : "minecraft:reserved6", - "id" : 255 - }, - { - "name" : "minecraft:respawn_anchor", - "id" : -272 - }, - { - "name" : "minecraft:rotten_flesh", - "id" : 277 - }, - { - "name" : "minecraft:saddle", - "id" : 371 - }, - { - "name" : "minecraft:salmon", - "id" : 265 - }, - { - "name" : "minecraft:salmon_bucket", - "id" : 365 - }, - { - "name" : "minecraft:salmon_spawn_egg", - "id" : 482 - }, - { - "name" : "minecraft:sand", - "id" : 12 - }, - { - "name" : "minecraft:sandstone", - "id" : 24 - }, - { - "name" : "minecraft:sandstone_stairs", - "id" : 128 - }, - { - "name" : "minecraft:sapling", - "id" : 6 - }, - { - "name" : "minecraft:scaffolding", - "id" : -165 - }, - { - "name" : "minecraft:sculk", - "id" : -458 - }, - { - "name" : "minecraft:sculk_catalyst", - "id" : -460 - }, - { - "name" : "minecraft:sculk_sensor", - "id" : -307 - }, - { - "name" : "minecraft:sculk_shrieker", - "id" : -461 - }, - { - "name" : "minecraft:sculk_vein", - "id" : -459 - }, - { - "name" : "minecraft:scute", - "id" : 572 - }, - { - "name" : "minecraft:sea_lantern", - "id" : 169 - }, - { - "name" : "minecraft:sea_pickle", - "id" : -156 - }, - { - "name" : "minecraft:seagrass", - "id" : -130 - }, - { - "name" : "minecraft:shears", - "id" : 421 - }, - { - "name" : "minecraft:sheep_spawn_egg", - "id" : 438 - }, - { - "name" : "minecraft:shield", - "id" : 355 - }, - { - "name" : "minecraft:shroomlight", - "id" : -230 - }, - { - "name" : "minecraft:shulker_box", - "id" : 218 - }, - { - "name" : "minecraft:shulker_shell", - "id" : 566 - }, - { - "name" : "minecraft:shulker_spawn_egg", - "id" : 469 - }, - { - "name" : "minecraft:silver_glazed_terracotta", - "id" : 228 - }, - { - "name" : "minecraft:silverfish_spawn_egg", - "id" : 443 - }, - { - "name" : "minecraft:skeleton_horse_spawn_egg", - "id" : 467 - }, - { - "name" : "minecraft:skeleton_spawn_egg", - "id" : 444 - }, - { - "name" : "minecraft:skull", - "id" : 516 - }, - { - "name" : "minecraft:skull_banner_pattern", - "id" : 583 - }, - { - "name" : "minecraft:slime", - "id" : 165 - }, - { - "name" : "minecraft:slime_ball", - "id" : 388 - }, - { - "name" : "minecraft:slime_spawn_egg", - "id" : 445 - }, - { - "name" : "minecraft:small_amethyst_bud", - "id" : -332 - }, - { - "name" : "minecraft:small_dripleaf_block", - "id" : -336 - }, - { - "name" : "minecraft:smithing_table", - "id" : -202 - }, - { - "name" : "minecraft:smoker", - "id" : -198 - }, - { - "name" : "minecraft:smooth_basalt", - "id" : -377 - }, - { - "name" : "minecraft:smooth_quartz_stairs", - "id" : -185 - }, - { - "name" : "minecraft:smooth_red_sandstone_stairs", - "id" : -176 - }, - { - "name" : "minecraft:smooth_sandstone_stairs", - "id" : -177 - }, - { - "name" : "minecraft:smooth_stone", - "id" : -183 - }, - { - "name" : "minecraft:snow", - "id" : 80 - }, - { - "name" : "minecraft:snow_layer", - "id" : 78 - }, - { - "name" : "minecraft:snowball", - "id" : 374 - }, - { - "name" : "minecraft:soul_campfire", - "id" : 622 - }, - { - "name" : "minecraft:soul_fire", - "id" : -237 - }, - { - "name" : "minecraft:soul_lantern", - "id" : -269 - }, - { - "name" : "minecraft:soul_sand", - "id" : 88 - }, - { - "name" : "minecraft:soul_soil", - "id" : -236 - }, - { - "name" : "minecraft:soul_torch", - "id" : -268 - }, - { - "name" : "minecraft:sparkler", - "id" : 600 - }, - { - "name" : "minecraft:spawn_egg", - "id" : 652 - }, - { - "name" : "minecraft:spider_eye", - "id" : 278 - }, - { - "name" : "minecraft:spider_spawn_egg", - "id" : 446 - }, - { - "name" : "minecraft:splash_potion", - "id" : 561 - }, - { - "name" : "minecraft:sponge", - "id" : 19 - }, - { - "name" : "minecraft:spore_blossom", - "id" : -321 - }, - { - "name" : "minecraft:spruce_boat", - "id" : 378 - }, - { - "name" : "minecraft:spruce_button", - "id" : -144 - }, - { - "name" : "minecraft:spruce_chest_boat", - "id" : 641 - }, - { - "name" : "minecraft:spruce_door", - "id" : 553 - }, - { - "name" : "minecraft:spruce_fence_gate", - "id" : 183 - }, - { - "name" : "minecraft:spruce_pressure_plate", - "id" : -154 - }, - { - "name" : "minecraft:spruce_sign", - "id" : 576 - }, - { - "name" : "minecraft:spruce_stairs", - "id" : 134 - }, - { - "name" : "minecraft:spruce_standing_sign", - "id" : -181 - }, - { - "name" : "minecraft:spruce_trapdoor", - "id" : -149 - }, - { - "name" : "minecraft:spruce_wall_sign", - "id" : -182 - }, - { - "name" : "minecraft:spyglass", - "id" : 625 - }, - { - "name" : "minecraft:squid_spawn_egg", - "id" : 450 - }, - { - "name" : "minecraft:stained_glass", - "id" : 241 - }, - { - "name" : "minecraft:stained_glass_pane", - "id" : 160 - }, - { - "name" : "minecraft:stained_hardened_clay", - "id" : 159 - }, - { - "name" : "minecraft:standing_banner", - "id" : 176 - }, - { - "name" : "minecraft:standing_sign", - "id" : 63 - }, - { - "name" : "minecraft:stick", - "id" : 320 - }, - { - "name" : "minecraft:sticky_piston", - "id" : 29 - }, - { - "name" : "minecraft:sticky_piston_arm_collision", - "id" : -217 - }, - { - "name" : "minecraft:stone", - "id" : 1 - }, - { - "name" : "minecraft:stone_axe", - "id" : 315 - }, - { - "name" : "minecraft:stone_block_slab", - "id" : 44 - }, - { - "name" : "minecraft:stone_block_slab2", - "id" : 182 - }, - { - "name" : "minecraft:stone_block_slab3", - "id" : -162 - }, - { - "name" : "minecraft:stone_block_slab4", - "id" : -166 - }, - { - "name" : "minecraft:stone_brick_stairs", - "id" : 109 - }, - { - "name" : "minecraft:stone_button", - "id" : 77 - }, - { - "name" : "minecraft:stone_hoe", - "id" : 330 - }, - { - "name" : "minecraft:stone_pickaxe", - "id" : 314 - }, - { - "name" : "minecraft:stone_pressure_plate", - "id" : 70 - }, - { - "name" : "minecraft:stone_shovel", - "id" : 313 - }, - { - "name" : "minecraft:stone_stairs", - "id" : 67 - }, - { - "name" : "minecraft:stone_sword", - "id" : 312 - }, - { - "name" : "minecraft:stonebrick", - "id" : 98 - }, - { - "name" : "minecraft:stonecutter", - "id" : 245 - }, - { - "name" : "minecraft:stonecutter_block", - "id" : -197 - }, - { - "name" : "minecraft:stray_spawn_egg", - "id" : 462 - }, - { - "name" : "minecraft:strider_spawn_egg", - "id" : 495 - }, - { - "name" : "minecraft:string", - "id" : 326 - }, - { - "name" : "minecraft:stripped_acacia_log", - "id" : -8 - }, - { - "name" : "minecraft:stripped_birch_log", - "id" : -6 - }, - { - "name" : "minecraft:stripped_crimson_hyphae", - "id" : -300 - }, - { - "name" : "minecraft:stripped_crimson_stem", - "id" : -240 - }, - { - "name" : "minecraft:stripped_dark_oak_log", - "id" : -9 - }, - { - "name" : "minecraft:stripped_jungle_log", - "id" : -7 - }, - { - "name" : "minecraft:stripped_mangrove_log", - "id" : -485 - }, - { - "name" : "minecraft:stripped_mangrove_wood", - "id" : -498 - }, - { - "name" : "minecraft:stripped_oak_log", - "id" : -10 - }, - { - "name" : "minecraft:stripped_spruce_log", - "id" : -5 - }, - { - "name" : "minecraft:stripped_warped_hyphae", - "id" : -301 - }, - { - "name" : "minecraft:stripped_warped_stem", - "id" : -241 - }, - { - "name" : "minecraft:structure_block", - "id" : 252 - }, - { - "name" : "minecraft:structure_void", - "id" : 217 - }, - { - "name" : "minecraft:sugar", - "id" : 416 - }, - { - "name" : "minecraft:sugar_cane", - "id" : 385 - }, - { - "name" : "minecraft:suspicious_stew", - "id" : 590 - }, - { - "name" : "minecraft:sweet_berries", - "id" : 287 - }, - { - "name" : "minecraft:sweet_berry_bush", - "id" : -207 - }, - { - "name" : "minecraft:tadpole_bucket", - "id" : 630 - }, - { - "name" : "minecraft:tadpole_spawn_egg", - "id" : 629 - }, - { - "name" : "minecraft:tallgrass", - "id" : 31 - }, - { - "name" : "minecraft:target", - "id" : -239 - }, - { - "name" : "minecraft:tinted_glass", - "id" : -334 - }, - { - "name" : "minecraft:tnt", - "id" : 46 - }, - { - "name" : "minecraft:tnt_minecart", - "id" : 525 - }, - { - "name" : "minecraft:torch", - "id" : 50 - }, - { - "name" : "minecraft:totem_of_undying", - "id" : 568 - }, - { - "name" : "minecraft:trader_llama_spawn_egg", - "id" : 648 - }, - { - "name" : "minecraft:trapdoor", - "id" : 96 - }, - { - "name" : "minecraft:trapped_chest", - "id" : 146 - }, - { - "name" : "minecraft:trident", - "id" : 546 - }, - { - "name" : "minecraft:trip_wire", - "id" : 132 - }, - { - "name" : "minecraft:tripwire_hook", - "id" : 131 - }, - { - "name" : "minecraft:tropical_fish", - "id" : 266 - }, - { - "name" : "minecraft:tropical_fish_bucket", - "id" : 366 - }, - { - "name" : "minecraft:tropical_fish_spawn_egg", - "id" : 479 - }, - { - "name" : "minecraft:tuff", - "id" : -333 - }, - { - "name" : "minecraft:turtle_egg", - "id" : -159 - }, - { - "name" : "minecraft:turtle_helmet", - "id" : 573 - }, - { - "name" : "minecraft:turtle_spawn_egg", - "id" : 485 - }, - { - "name" : "minecraft:twisting_vines", - "id" : -287 - }, - { - "name" : "minecraft:underwater_torch", - "id" : 239 - }, - { - "name" : "minecraft:undyed_shulker_box", - "id" : 205 - }, - { - "name" : "minecraft:unknown", - "id" : -305 - }, - { - "name" : "minecraft:unlit_redstone_torch", - "id" : 75 - }, - { - "name" : "minecraft:unpowered_comparator", - "id" : 149 - }, - { - "name" : "minecraft:unpowered_repeater", - "id" : 93 - }, - { - "name" : "minecraft:verdant_froglight", - "id" : -470 - }, - { - "name" : "minecraft:vex_spawn_egg", - "id" : 476 - }, - { - "name" : "minecraft:villager_spawn_egg", - "id" : 449 - }, - { - "name" : "minecraft:vindicator_spawn_egg", - "id" : 474 - }, - { - "name" : "minecraft:vine", - "id" : 106 - }, - { - "name" : "minecraft:wall_banner", - "id" : 177 - }, - { - "name" : "minecraft:wall_sign", - "id" : 68 - }, - { - "name" : "minecraft:wandering_trader_spawn_egg", - "id" : 492 - }, - { - "name" : "minecraft:warden_spawn_egg", - "id" : 632 - }, - { - "name" : "minecraft:warped_button", - "id" : -261 - }, - { - "name" : "minecraft:warped_door", - "id" : 617 - }, - { - "name" : "minecraft:warped_double_slab", - "id" : -267 - }, - { - "name" : "minecraft:warped_fence", - "id" : -257 - }, - { - "name" : "minecraft:warped_fence_gate", - "id" : -259 - }, - { - "name" : "minecraft:warped_fungus", - "id" : -229 - }, - { - "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 618 - }, - { - "name" : "minecraft:warped_hyphae", - "id" : -298 - }, - { - "name" : "minecraft:warped_nylium", - "id" : -233 - }, - { - "name" : "minecraft:warped_planks", - "id" : -243 - }, - { - "name" : "minecraft:warped_pressure_plate", - "id" : -263 - }, - { - "name" : "minecraft:warped_roots", - "id" : -224 - }, - { - "name" : "minecraft:warped_sign", - "id" : 615 - }, - { - "name" : "minecraft:warped_slab", - "id" : -265 - }, - { - "name" : "minecraft:warped_stairs", - "id" : -255 - }, - { - "name" : "minecraft:warped_standing_sign", - "id" : -251 - }, - { - "name" : "minecraft:warped_stem", - "id" : -226 - }, - { - "name" : "minecraft:warped_trapdoor", - "id" : -247 - }, - { - "name" : "minecraft:warped_wall_sign", - "id" : -253 - }, - { - "name" : "minecraft:warped_wart_block", - "id" : -227 - }, - { - "name" : "minecraft:water", - "id" : 9 - }, - { - "name" : "minecraft:water_bucket", - "id" : 362 - }, - { - "name" : "minecraft:waterlily", - "id" : 111 - }, - { - "name" : "minecraft:waxed_copper", - "id" : -344 - }, - { - "name" : "minecraft:waxed_cut_copper", - "id" : -351 - }, - { - "name" : "minecraft:waxed_cut_copper_slab", - "id" : -365 - }, - { - "name" : "minecraft:waxed_cut_copper_stairs", - "id" : -358 - }, - { - "name" : "minecraft:waxed_double_cut_copper_slab", - "id" : -372 - }, - { - "name" : "minecraft:waxed_exposed_copper", - "id" : -345 - }, - { - "name" : "minecraft:waxed_exposed_cut_copper", - "id" : -352 - }, - { - "name" : "minecraft:waxed_exposed_cut_copper_slab", - "id" : -366 - }, - { - "name" : "minecraft:waxed_exposed_cut_copper_stairs", - "id" : -359 - }, - { - "name" : "minecraft:waxed_exposed_double_cut_copper_slab", - "id" : -373 - }, - { - "name" : "minecraft:waxed_oxidized_copper", - "id" : -446 - }, - { - "name" : "minecraft:waxed_oxidized_cut_copper", - "id" : -447 - }, - { - "name" : "minecraft:waxed_oxidized_cut_copper_slab", - "id" : -449 - }, - { - "name" : "minecraft:waxed_oxidized_cut_copper_stairs", - "id" : -448 - }, - { - "name" : "minecraft:waxed_oxidized_double_cut_copper_slab", - "id" : -450 - }, - { - "name" : "minecraft:waxed_weathered_copper", - "id" : -346 - }, - { - "name" : "minecraft:waxed_weathered_cut_copper", - "id" : -353 - }, - { - "name" : "minecraft:waxed_weathered_cut_copper_slab", - "id" : -367 - }, - { - "name" : "minecraft:waxed_weathered_cut_copper_stairs", - "id" : -360 - }, - { - "name" : "minecraft:waxed_weathered_double_cut_copper_slab", - "id" : -374 - }, - { - "name" : "minecraft:weathered_copper", - "id" : -342 - }, - { - "name" : "minecraft:weathered_cut_copper", - "id" : -349 - }, - { - "name" : "minecraft:weathered_cut_copper_slab", - "id" : -363 - }, - { - "name" : "minecraft:weathered_cut_copper_stairs", - "id" : -356 - }, - { - "name" : "minecraft:weathered_double_cut_copper_slab", - "id" : -370 - }, - { - "name" : "minecraft:web", - "id" : 30 - }, - { - "name" : "minecraft:weeping_vines", - "id" : -231 - }, - { - "name" : "minecraft:wheat", - "id" : 334 - }, - { - "name" : "minecraft:wheat_seeds", - "id" : 291 - }, - { - "name" : "minecraft:white_candle", - "id" : -413 - }, - { - "name" : "minecraft:white_candle_cake", - "id" : -430 - }, - { - "name" : "minecraft:white_dye", - "id" : 410 - }, - { - "name" : "minecraft:white_glazed_terracotta", - "id" : 220 - }, - { - "name" : "minecraft:witch_spawn_egg", - "id" : 452 - }, - { - "name" : "minecraft:wither_rose", - "id" : -216 - }, - { - "name" : "minecraft:wither_skeleton_spawn_egg", - "id" : 464 - }, - { - "name" : "minecraft:wolf_spawn_egg", - "id" : 439 - }, - { - "name" : "minecraft:wood", - "id" : -212 - }, - { - "name" : "minecraft:wooden_axe", - "id" : 311 - }, - { - "name" : "minecraft:wooden_button", - "id" : 143 - }, - { - "name" : "minecraft:wooden_door", - "id" : 359 - }, - { - "name" : "minecraft:wooden_hoe", - "id" : 329 - }, - { - "name" : "minecraft:wooden_pickaxe", - "id" : 310 - }, - { - "name" : "minecraft:wooden_pressure_plate", - "id" : 72 - }, - { - "name" : "minecraft:wooden_shovel", - "id" : 309 - }, - { - "name" : "minecraft:wooden_slab", - "id" : 158 - }, - { - "name" : "minecraft:wooden_sword", - "id" : 308 - }, - { - "name" : "minecraft:wool", - "id" : 35 - }, - { - "name" : "minecraft:writable_book", - "id" : 510 - }, - { - "name" : "minecraft:written_book", - "id" : 511 - }, - { - "name" : "minecraft:yellow_candle", - "id" : -417 - }, - { - "name" : "minecraft:yellow_candle_cake", - "id" : -434 - }, - { - "name" : "minecraft:yellow_dye", - "id" : 406 - }, - { - "name" : "minecraft:yellow_flower", - "id" : 37 - }, - { - "name" : "minecraft:yellow_glazed_terracotta", - "id" : 224 - }, - { - "name" : "minecraft:zoglin_spawn_egg", - "id" : 498 - }, - { - "name" : "minecraft:zombie_horse_spawn_egg", - "id" : 468 - }, - { - "name" : "minecraft:zombie_pigman_spawn_egg", - "id" : 448 - }, - { - "name" : "minecraft:zombie_spawn_egg", - "id" : 447 - }, - { - "name" : "minecraft:zombie_villager_spawn_egg", - "id" : 477 - } -] \ No newline at end of file From b09caed0f1e19e85a78043fd79348f87385e228e Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Dec 2022 00:29:20 -0500 Subject: [PATCH 328/358] Fix BitSet import --- .../src/main/java/org/geysermc/geyser/session/GeyserSession.java | 1 + 1 file changed, 1 insertion(+) 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 8da5b12735a..8f89e81f180 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -194,6 +194,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; import java.util.HashSet; import java.util.List; From 309f9737bb70dc474c09b81578a2346cd772a9d7 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Dec 2022 01:43:09 -0500 Subject: [PATCH 329/358] Update palettes/mappings --- .../resources/bedrock/biome_definitions.dat | Bin 42385 -> 2605093 bytes .../bedrock/creative_items.1_19_50.json | 93 ++++++++++++++++++ .../resources/bedrock/entity_identifiers.dat | Bin 7762 -> 7823 bytes core/src/main/resources/mappings | 2 +- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/bedrock/biome_definitions.dat b/core/src/main/resources/bedrock/biome_definitions.dat index 0520c61e28fdd87c24bfdfc25fa30f3f52b70eee..47b19ab44fbfbf531f82c14cbceaad74d87afe47 100644 GIT binary patch literal 2605093 zcmeFaZ>%F%b|=Qy@9{NR-Rgf*OXIa?=ebALbdUPAAN%$Had&Gp$_NY0j1*~N1Tc2# zdSvmj+(j0rs@P90$AFC8Z~J8z$R^NyNbCezIEb+UHi(@MUT9z>P8Qo*82Myu5MU7) zgGI0~jC@)I!78VUtb5O8-CK2vMe-F{SA&6`$U3+9=g0H^oS#uMUO6%cN3Prct?}^K zvFh8V-><atg*lPPD*R%ZSfqK^(1^q8yzIeX zA3QNG5A31U@yuTEsN>iJGe|p8JjU>>F23JYf9RgH%}!@L7&~TQyTkUt`~+Ugle+9F zdpLR7fi)Oeo*9fi>+sNjT5q28?Z9eJ-&W1|26~0=(03iX3$L=EAcF(0JIjykpOWf47evc!iGL>085CzG2v1YZ%x)+wu;%l>6NBPF&CFwzK4SMyBt- z4j-W93D<@X8)yBF8Nf5!mpgrP*s6a`pl1CjI77aa~;?9y5VV-Z|rWJ@7jLg*+=8>qu<&#jy=2kL9gdq zL2dhd_#Yn!*63sVQ|nBl0bk6on}C;Q>t1|^^YLTR;c$sPj4o-g*Kj_5D7vCH*A0!^ zcQfy3?BBb?U)A(w6u4)hH`%nFo6ZLwU}y`V0$*i>Z2#&H&7gnlBXihw2iIE%cW&;r z?%uz#b%9?IzTjW3)fgn-=HPOyW3%((k?=!q2cBigK)KK5L;-*7^~{bH3HnSjg5&x4 zxzhzsF)#KDoIkKJe)cc*zKu z*JpkVg68={bjDom^Vz#wc zE85M2;vqpFev_kX;!U>$@R8e~?If_8#p; z{-VTH{LWgsig$Ax<-Kq3-4=a`>$!c2Q&gWu>n`)PQ>-{rpwBF4BvYW*Ik1yzQxM7r z&o>h>%b5knEQJlK+G`SGqYb!^$^cXFgJR=pAi^=yg8&)g*l84vV`BuVsjiHD!ifx5 znE4=8%`o_0e%XzpVvIlC4uu7 zIO0a*!RVPSE!m~o;90qRV0@>NV4SNIGOZ?tC9kq)T=G{<-rzul5?xp73Zj@gsUAhC zd?2i5cw9Mi-E2`ZH*azzPV>(}*vT+*yPvw!THc+6z%+L=X;+BD=ILQ^)+a*f74eeO zEhH1Yyh4S3!L2MDA)lr#=+fS=R4cW?tlT6f|9 z@aJrY#YI%!ev6~MXMZvtwVy+8lMg=+-;TPGVtk_z{w5cZ{xT*f#De;VG1NZ{-r>_E zm1|H+$sAZg|Aim4k1e+=ZT68iUns=pLF2(vecHm{Am){*%J7gGRiVM?F)s=l`*$D2 zg<_$j=kaND^yF`*xW)BX)Tej?y?`FHT^C>mq-~TWO+NT>9nCU zzC9j6v$+hK(gY{&{ZYM9twS8k)7n#CsNs2YED9$Bnj4 z?o^}{vi}CR%h2O!K>#9k_YU}FGd6fSu#gZg8{Agq@@>F~mR-G&k?o2kkaN0bd)tN4`*Ss&^R@89Mz14?}haYZQ|JW-R8a*jxoG1wHsoM?e7 zkGWmijEr&kfHhF5g4yN+rb<#{St`tn%!YAgCt~g)dxUvS zz^HkUR*QK!Dlg60$7;2lfdMK_(5z-c3+0Inn6Qov1WiK*-NQ`KsXcY!j;r`xaU~}@ zYPp5CZQ)Y57U_c=7>(I^uh z*xjzfbH*BUM9@)ov)yLlUTKRAq{0?j{(=>iV|vFNImpO9%tD{~l>GR+vRTn7(avs+ z(xXfVqxrV34Gr@04v}BYMx7{|A8X5P(_CUOCpcCca3Ais z@@@@E3Ir7wBsFyuhD!GY;qimXt{}-FWkcNtc*RSej=EU7@^B-jXh}1CIE!1(MoH2@OOEOQT8St}oKPLmpv%iA zc{eLzyD>Trl?8%vuQyj&KoG3FW+Pf|oPl3sJ4%T{ksa+?*2o85U1@V#rX5X6M`(NF z$m)fDAub^3{Y9l<$}N=J;emsgwq@nC6yFWCoMBi@>Pj7;4Q+Q!59pC)3J;Oha_g#= zj4*g*EM+qSyUwGAXWLm%uJ?3V@#bmL5; z*@1N)(emd%-1+Tm;kqohckR({{Ps0BqMUcVYmZttuleJn-?BQvwMPdxuK|@h{Bf;s z`t2jz9l)99@wJ=RfCGGN!Q+#ymDhgf=KM#v9m^a>Cz!(*MPE;7yx|3luLs^nj;w%6%j`<=fH|1-9)f(D8#5paLChn9WZKXN_) zO!FdxnBTUCfeRR`M>sh*-Deu&aC`S8lnBn$YR%W8PbZ1YiPWnijcjx+k~UCRNW80m zWP0H~Xv_1Bi#->VYJ~N79k=tWu``6HE^_4=b-C4y28_d?3oh^r!mcEE-nUHjJUSp+ zCtNdLfeXIxxUPq;y2MX`k{WP{*S6VZ?^&auf5B(~r!|s5=YoXq!{+v8_=4N;W9z>n zd=gtbo@o!a>PE;Z^C$I27r4TD zpsaem-qZT-gp~fLpMUM< zc;Qc^PuaupO(j3j*?0X7^a|nYa2*@)$=4|P&BipN;v2oj+3DX3C*c@Mv~9=kgcKKX z9tNcjGsh2Q@{z;v`h?m7J0xr2bQX#TgwhDn3pH+22Et0YE<)&&x+<))KDZOoN!(9R zN#JGHh*oOK11aby(C3yk4oC5iC=jdfP>?eSMQ}VHKXxgJ zo&vMg!QP|2)&cx`>vE@W4m;L|me;X{K}ewZo#wd?gj!vD2%fWV>~78Dxh+m{5;b+@ z7$}SuiTw#F&}Wu2k^%L4n%WeE^1<`XrFPgCHmE#*EF{clsU9}qKHLiwq7wcAq-Z@9 z8&3lfZd!X{4>O955u~QNGB(2#nQ}%TL{+nL-F9z6Z#YTB(D<3(-aEVvg&O=skY?~bgUeQg>~HKd4!btA~e}H)s6nhgM>ZTZb&7fj1JeJ zu~wXwD-#+EgM-_dsv6pyU^?<30pA{s{l4eA1DRfvHcQ7~nzTD84#|hC#!_9lmFqd; z4J#K*kk%QCJ+7(nyq6YHOyC(7))ENM2hK|xoHS?f+k0#63sSTvnwA@l2cu_DJ(ra1 zQf=_8Ts|j=0W4ZQhnOO;2`FesOEt4Y72RDfmHV{ zTAqaT?+R;13bjj$;BRr27xf-Sjik_ac|0(M0jyYq$yFbBSrdoEg7bi_n!YP67-1t( zYS@*(!|_$2!|}n*y~CvS#r=&aOd43ueCAdZ)R-F*HJwgxf`8|StuBv|KZqp9VZp9H1NH5_FD;6pdAxh$=Y#o+F8{6*Hj-h926y#3*!QQ;I;Bxp^re zTsF9^%H`XD`>6ap<}IY)38^x+jLV04C27_tQC>yqjnx#$EuIxn2J!CI2McX0F zvE|Sb%w<~7gs?^%k+lsk@j}HtA#+m75LQ52gZ*Suz=nz{m~QQ8?Ad(E6t+}!p_$(9 z*_QW+=UiqIC&ykmJ>5<@Mgx!t!0b(m>%mn?K_F6h34b5qF4M#^VX=w3@|dIAyUHx0 zMmr#;1D)Z*E{TOjgLraPClS|hqp3JjLS(=EMz+~H6&+VJj5xp4N*Gn;WShCgmc%N$ z{hZ})B|0uMibx2TO~Y+KZcIqSEb;yDuvBQj;y<#KKml3Jjyr-SKq|JtOsgpoFB_&- zuE3~ykXDP3e8XxrrKrD(H^~tvgr+EInF2W!86>n&p2&a+>&QURG-S{{%mf`*A*Ic| z@xFu<$bFq>H*g+9VmLFmWy$2Vo})zvNI$63z3Y(xp)ge1)-_6@#(?2OG zk=MEBA8xmo1qW!3Q01l}!Ls>iRP%<@s`9R&#b&__nD~s>c&f4>i@hTu0#V7F1welS_aA~~dNsM`RKT~M7%d_Npn`6C0=I?nnbMWct%mDyvY zI3ho`b~Y=+K~$l}{vBp^tV7TcF$2@SWQig+A1kG$$V@1%o zhE+D*wnRT!z}N=dr-&;ab5Ck>IXl-gZkFmyfl%m*>9qn4y$Om3sX>(ep9ZgC{ zXnW(x3i-Z;U2%GUQTOA(8hvbkYMp5`V#1x(r&@~mr#x^FOHr(xmg2jima}ACsRO*+ z?T+a^hw+!#I3j-zk=1hRI;lv>BqI!78F|*RHJonMj40BLvwp`6VA1CGl+5luYv@8e zV+O9b++;4%&cSxx7#QK!v9ACpQ}-!+26<9n&9}$5Oy_ef)nE+*+kVQma0-^tUUZBl z85R(VzQsdVjv|?i#!qgh&(-5|4*b*%w3UNH0BH$t>{x#Dmq4{x!!GRUu)5H-9YB@I z2YUwlrC}eSai-DixWjI={P_=ee*0RuF3as*d-NN>ea(gOy-=B4d(^sl%^x5AmemQa zJvz8~4frwPk86F?Zy(w20M0azuid-`OWVg5JU&?xeeHK{&VPj4vCLs~f;oIq^!0?s z8(y&Zdf;v3cuO7E=@vSmZ##}(=!AiFY!*9U9Drj~>}?#`UZ;QUcm6i~&)B{S8Yr?v z!2Q)8TJ~}O$o2d)&5I0Te%l@fE?}%4;pE(OeRzn&?cI}5A~;j4)eO|w5wF`HnO?Zk z+46kjVlUhn6T$Ue$L&08>=UvL|KFa1|Vih&3g zTRWa<54Y+@NDkzWLbSq%QfuzO&&qF{--W6$Y_Z#&NY*2f-N`3;b^sea;WKKopMU*a zU=5<*?bx#p5B;b0bJ3gop6j^YcYD3Jga_XJSAX#z3lF^iU;Y<=Q+VK`fAtrC@wk3D zI^)No=78_T8g^bpCpAy{VZ9pOR``11^E)zq(+S#L%Lz=szCC@Se(neq1A1rmdN=&! zzI^%ealO$6(ytz9o$!SE?u6{|%a=cRVjv>+jtQg0@Y#&Lp4qXQm%@em06JK{Yk9Wm z_}gdxvfaD@9GkAkAKUrM_Rs%#Yx@dEPyF1!|AVdVbMRfq|NW2u!4{w(cJ_woJbqP-Wx6P)g8}eR1SVoaet$-!Huvy7HI=>TxXl zD>%StF&Os?1xI*xp*qpgF7y_;=OMODu^d&3=Ze-@N56Slw4=O6V=0@AelxH{ z`akIPd@HDJpH~Ddg?{Hk*_WtT0DdePqEclgO;roZEbp3r)Mmi8LP!!hnQaCM;YjC| zXc77bl>Y@>FY*1T>?Hevq}WM>r$pxoO6g?cDIu)!l&Ub*8A751R#H5njNuU2k|538 zw;*pL%l;vFp>)wyN+u9NN~fEX#SJx>)+Al{D?EUNz%;NUDmtLqVpWAGUs%5wgxej0 zHbnAvFz#AWbQ(X{A48JG4qikLUYTBrQV#I=7xhci$05Vxb%9+jN@vuk37^Bm!zVZ+ zA|?$cxsaM77t#(sQ!&;PP3 zWI^8ltAG1%pWsZ&NB{o+`%j*b(jpMJ*N-f-(@#k!Owu9IN6In_kQ{l!(;+W^ZO#Nm zbj;!t6p)~KDkdl({gWd>@po8)0;w`w{5Xcz^R{DmLh4S5=asouNhTj@w$~@zh4@~& zmMg2}8n-EnZg9IEs_2y{k#QBWeEnM=!QN+gaJ_YKC#1=|pPFCMY< zY8!HlC-SOpD#`Xlj3?b9DC#^~s`GYlLYO+q2-6tf-`+c1Ysz;@w+l*(PYMz;{V1i# zjMKI=VwJ{XVF&;I1f}K?=!iBHg4$7H{yL-VJ#js!`^n8c@qtfoh#e3mVlpOCYNED2 zA2h2Bf@r}j_dB^Fjln30Yba)?gwD8ku|K6JRbA4!e9pF<5d#AqBJlBEZW;g8avV3EA zOUQZ=v$-ozA8W=<8RI6>ozE<1B!lSnG>s_;*p{AtQcn0;E%E&nNhTxw zbR6?VZu|&B`S4SUw$jC^vBF>Q=2=|I$_msbGL4V@kq7NKJiJ9o=hU!xR<2BFEDR2< z&2ob2$b(j}_F(MyJ=Yz`^qO@3HjX4F9rTJr@*%6SR6D8NwXW?4o_#b9d*fE|X{gLC z1)$>CWGX!GrMYbrc!n*W354eZ=OqnJS~l?8duyExq<|AGU>S`Eqi4{NEvc5I+TdBa zd|-U1l3;A*PECuWP}lh=6wymqt(M#oW|$~IL|vYqWp$;lAgZe*6EC7vJ`iqLSVH`+ zE0fA@C`3Ru4_Pmq zx>@utTJ4xpFka$YE8+~I+;P-F5Y5F*8%=Oafy4-_w;$s}V!?U9R!ucn)gEP}=UAJu zxIhnOVWg1>2CU+U2!l@EL~>GDl{c511+`(afxfxrgi}rf55a zIYty9!Ca>GObBbV(N?38mOhqW!!_7XCIxJ$I|@^8s#n}TlGQM9q>yrq1|SiD+2P50 z@D@`Lh}2!e-$%hhrio?3Vh4WZF-LXlDnm+umI+Ne-NWUL5(|qulzBY@t2&9eh8s=A zkrE>NYqG81OrPdv&Dks~_Ew=gzsbExHkZ-*e7R;vYvXnppS*xK z3tu`l57KH8l5be8mQ#bFq-6@^h$<{IGGHo9M+WnO?qMeAxC$w4?u~D%N`c(hd3FQm zF(igFbHjj4Uh6qpbb$1Os>ZM$2@ncvF;a7BHHP~fn(}Cr3Bv}KuEQf*4LTy|DEln% z>dvy-R!WiKn;cD8Q90piA6YKsA-`UZKJ_X2@pomjqEn)Cw=qhO(hrOp)4R6o%}Z-M zE1Mr{%WaC&!RZXMhuTpr5}eY|lu2tGqe;gJLYzR2hAUG+STNjlGIt)hRVkS(&KO2? z7OM2yqbx^Fpk~yXPJkZKQ#1B4<)^%kVC+9EeK&7|RYOuzBCm5mg)}L$;6OdPaO{QD zENwxsY(5&*yy3K}{5w}WHv-CtH#AxsVNfY45L8@{)YMTIh;+aom*gi?)RIHWhB_S? ztRyl(t>dg8QZ#x9U73{x#S!_jwX<0n4ys1mzr!q!Dz95ox`_M*coZanIvUQK;h2@p zk2Rg)`1O!@kZ7EjsFxR%;h04PQec@Gj#ySyfn~!KQJoMHro=Cudc%!y$XGEq#p$Oh zFEZS8hGV_}ppxxstd1$Ex@WelC4|eS+m>iw1dMIKeTvx2YdmY=m?K!E8}@jsr&==O zO64JC!xz<*tvZ__^wrAKCu`yQQ`)_MQ3gJVHf!&mMa3iK@Ni%#ni(AfS zjt0e9%TXP;lZ7~;I-o(9cLyaS#a-+421UoAvOqBI_2w!I2!fT@Y(&eAL)taAqZGx4 z>}c1rMm~_GOPkX&?PyXuLfachRxk7maRDJ~W7~4GX+#XHKJ&41cd^I~dEg+XZ7GeB zzsfBnhQ*|=B;&(G>9>6@%l$dEWQ0M@q`7fSnpxb187TUe3`H^-jUNRMx|#xMO_*3w zJ)Cb6^}@ZQmggH6dtpLQ0t)F4zd8ZZceF1Ua$fMp5rylkB<5CN zG(CT0d!2qebRzs9lfV_YRv*_Hf$L8{|AnWV!1V`D<|J^9j#)f`YjkY$q#u?oO}`&< z&*ma<{mm>TC?jA+rZjWBN+usEL`~pIbRw%u;HoJy$JXLPh{BbIYO*Au1d70MiIgB3 zrRP?cnZI`I8VKT%4y-D3u!dsS^2FT^6pBo=<$g-oh5ed#0Ne{1`90zK7?+_OCweFo zFbm@ZC*e{m2Zir57m)~hL9@KUg@=5p ze)cTQr5;Ape%O-yxK?Aa3$oDyt17b52!~0_EC}UOfSQVR1G+X5egsxkDt-i^eE8Y0 zIz?f_Rm~s+jX!8AR&`of?bTGJr;f6-0f9-uqKfpIw4*L|(Iz!9#Uc6hoTg$8G~4C0 zQAuosO7f^8c;4d?N78MZz*AGPJ_e0B2NiGShzCgvS)57z5gkeu#yLXU0}9vPj7a1~ z8olVb)9vXi9J;P5I+;sVo_4ZXtajuKV-!rj$R4B82=@~eViDvt0dQ^myrM4)u}7-= z<23Os{6z}6%)KUX-S34kB54d~(^Hqvv_co;W8D~^bCo+be9e$earR-jgW zB)%I>01QzE1|8${D(fWL+W2;>@*&mt;r#)k%SIW>K$&MY;I9dQKZz=&i)-W~W>lJ* z0N4!1p4G@>J{fF93tV~gNgU_a1i)4TbD6tP*tWW|T2HFNDKZNv6flTrBP|aoKR2N! z0ETX^=D7|GFm>%A%ntj;?pzV%;!G?RfZ2#&5B?jfm%G!6^+*nS3QNnsl5X#0k`BxH1)l1;b4zbLWAZCIFUhWzqz|QD_h~ zj4vxv&;-C@`e*)WLU2|nhY;99u;>|LZNGelgNr0_-YW+riAQo3yFjQZ_o zH}nwUMs4Q634p^D?_A{&K}SZpCICk2Ewe13 z34rHVuZkh7QfzB5oJ>({$c`o{d=bzx?PyXuvZI;+cu3Z4_ii0T|7@weH;l`Y8dJ)y z8Psx?tSiZASP>320r2VxR_BRD)Jy=$*Uy8{8f<(40@8?h)#qqWi34u%E#nwV87M+) zQF+s&OeWG{nm~Hv2&8Mqn;bYC0>{>HioUitb}YYnb@~YO0l8kg1BO5+q_%eW0cz3ehILUe}5FSS~y{Ux9fBr9jBoZF~^1z*KF$ISGB8IO-8cUxo0Er7NaN%EfC-rb2lgkqad!KXlL#qt`ZbA z?XhIt-4aW+LKI&3K5NNDp=poHvuBZ*)zq}dn)dkPJn18z^*ax)i82%^tN>5{K0NK2 zzgHFccmjhl^6^q>vjrjT*=f%X=iMo-_h{%t(;lnc{?peoi**~-??Sl0VmHeAy+w@dbr33jSgg z#z?Ec3b9ESn%RTaI7Shl#khU2eX-LwhaKxj<3ZS`|=PCwFkY_2A~- z;lYjBLE>LV0SC(cG3p9&{2b}I%!MzCBO;xrX^%5o4oECNn)X=5@)N7evcYXt26hAP zqjK4pbCWVYfK(Yx|+GGV>aZ?5EA zWmfv30nF(fkLyV{$fNlSK6QcTmnGtrS8+t6PD+Sq+GABhq-YqSX^&OgJ2dTaw~cii zOZ>(*xojG419D@6yWaxY;n6IiOc*n?QyJy5a2SC zZ%l>h$Ux9EWYB5a<7E0Rb3+BrgT$!Sdahy#5KVh*`Nr;+pmxe?Ts7^nrD>00Bd(@B zCT<4i;Y3~72uK+wL*o!J*=%eik!y!`P|7oKv$AC~uuBZ)1f9A8_u**DKWI%uQzlDG zj3yl?2;ia`?8;OS77RC?%$)~rn)X;)w92f{dsde-BWO)0K#!Uc*~f$#Xj%{vDj53@ z(WY=TCi8e^Oopmmi}YM^+bVi$58_?sDb1H?iAlIxR@M`u9$lD@I|zbh^U+rE1qdi3 z-cZvXC$nzSwGy~orD>06)Jw%91JpXs`XNQ5hr-RwN`m5u{2EQu9;@o_Yr%b%Gs6LW zLPKCokeSZR49Bc&eylB*qILuHi(s6W_R%Pb2Bf|b{7L{*BHJF7}hn)X;6V$rn61s6^) z&VCb7Ug(A@y++wJgA%gIx{{217AZVUdkn=V%m?z-Ofic1<%g#r)%F!MWlr6*)t}F? zq-l>s-zjzBeoy2k{=fgtzY)2KfB*OP9#grAQ@;01j^Zz0o`1?YiobmMgQxY~e#DwxtJR(w zmpKpRlw~{R#75UW8TL%aiH@JXXvrRUBvoVc|ed9OTZhi$l zy^LcRS^LZhFqwQ5<-9&w5{&(etVO}gE#eaOULmJ!9!0A;iZw?ua77d*izr+S(HzCF zUP;!!f~Nhs=H;_GG2@fwDE925aY!<$q1d%NvHyj_QH*dlWwH~z+o@mM4m?ZRW`*+J zQn?tXp}fy<9vSPB@VXbq4bB^;6gvvvXDw+YG-t8qEN0n3A%%6i;~Ju(q!a^DUFMnP zjF>J%flDgqtRR$60lv8rWG00b;Hng@`aV48B;to5oHp(w&<|7bBM9Zg&xXagg@IJf zpaV^8XwG7FB}Dz}7ZNRnL3sN2QE4;E5C#I1<}CiyI2|v$Rt+QIQ2;LQar)LlQ-g<}6lG^u*J5Q4OR-6xkb)5f;$A#P_3e*_dulsiPs6 zjqN1RoW+{67+G*itRM`Z(?V3@Dviu#XAHJQx3_FqU6(k4t~ra7>l-uIx3C3!Wwo9} z-)V6X0}*YceFvos7z7R1psd$;lXn_plARf{VU!Wc+=-lWOxfJG(gH9$JUO@Dy2HNT z*{rPK)2-r7a_25T0+ESjw8OE+Lb%H`u}oO+Zk)mOQ<}4PrBA`H0a~6y1UKrtLY?I68#4RyrCD_poxDR*JgvtWXouuU8 zkkyos4kJemO_%Z6MLeD@5ic92h(>I2R+AW+YR=+V3n9Q|Mg~lU>BvCPXk^f7&f;YH zEpuZ9&V$6L)p|~I7W+{ZyH;ZxQ&0kgl?h_NAT@WNXX@m3l?e~*Zr9Z*$}zoTjvR!(hx~du`qZc7$KREu4!@Swxg&U!>yh=%ISvss z#Y%J@S|F)Ho$FyXp!`@{Zb5Yef=0n*T;lsF5}cypniMo;Qmn+XI!+M2bgIIvOa)=V zaMQ`$dEizhMxPw41ex`D%~`CT=b~mrG-t8ZK*7Py?BD>A9hc2VqnfwurVO4)UqnZS z<}6NT-85%0Ti;Fu+Y&u?!D=t@K1Ct})H=@kAw~Bd>9Nd8g5rq$8clN+tLpEXvskRZ z&r*iw$C}P?{CddPa{BtRh#o^B=PV+S0?W*B#Im9aEE}fE>X9|8nBj;aBT3Ja;ifYj zg0Doxca<_6#3E?s&{UD60)6`cH*^|AH|k-|+4&wl7K5L9`)*qp$Ovzd${V zqS4H6q=#wh#?$c`pihY`?P9E*xJALRR+ak2R zab)$v**`p;pvmL99|zX*?Dr~uRx00(vTFvloMBj4p9G%S)||yrDpkEnfAyTg^Qh1~ zub*Q{a~5mP;>pgXCF7@Npkce1O>Ja);kr`G^Nou=7gSTk^ZKsicAhnMhVaxyZUp_n z9a=R5O2u7t!3BOn*Y*N<-nUHjJUZY6Or=*dUV#g~@3^jquDZlefy^*W&)2ruW$#&| zpnt(=1nvk)pmRaO2g=$;Gkn4AJ}}Lu5bVwq~QmpgrP*omK6kAczI4%V*vO!5`BbzO710$QQ z35CH`iL#87Jqat*H>=lcB7PXcX9ADMDDXh|5x9Y=_z{G(r>8wV1BT}(`7l2(d6Fg+ zR)+zra_3I}J}PTS*#UuOb2OpwlG>$+*&IzMY`eq6W+rj$x;+^Ceb03VENzF(?!B}G z%cOV=tyRe*@)_Yvo3$86gHsauyW>_w$$!h+z3yjlSx}ZyXQK#DPL^&7MF=Hwmm;ch(w0k=ta++ZjWH$B=D-D zhPhNFO8%TmvRO>ZRfjAQ5^*HK+-NHKDL36rO5K^-oKYqtS%Igvu^e6RmtX z)FFY@S`!M-Ot&$Pu(D3Jt&MM|);2;=y~OvU&}#gJrPR?-=9w)F(1gO8P#Ez*aU9Kd z9B|+Xli{=wmAL0Znoh>^NDQ_`SF0>{JJia}CviMn6ACAHD`xIiVcYM@YCX$}HqtVJ zQZ~v98cq`mkF3Yda~&9nDofFVsO|_=lJ%gLVC*X?&K)wb34cGUVv9{I6V}V}1fQO& zWh=8R4^3-M=Xx}uFhS#}J?welrU`{(uWT+IS2T>!gu<$Y#+p!ATxiVLIAgp}8g-=Ybp{99fS9$o+W1BrO5L$^;TM zrwN6}Ry1R{G|*A@qi90mM{=)D6ACM*V2O5iW0W4H3lkBf#5ArForktNs!*p1g#(y{ zT4LjdV854mpCZ924NaN+CovLroFIJZ)M&Ue6@&%DO(%2bfm@a6cErinXkTbsQMA&8 z!rTvAR-|BPLSaoP9Fi2y7mpI{sU{RoX5BQQaNdmD5S36js&WHKDLre}@*OA#8nOOHA@(ZMiMuOZ?rhO24e?K1K8x3OQ#HffQI~ zh9j00RbbgLMRbq|3DdH<$If@TiAB!tVR+qAt5 zgXXU&shJ}7QWFZN@5Da^+sTGO*4p)F?;@q60}&xQ=E(L{x!O!3#H4iD)EUu~Ee>wf zVmc-ZM*G}Q%aWMIMT?9c&uq({$c}14VG9kJEY6&gHQS15vkSdLl`*BvxuBLaJhY+- zg%SMJGfv?%$dmeNK2NGl=W{G+LSaoPtOJipF6TbQsN^!Fav>B`0N14 z0e~olPADAF-=-1@|M|cCk-#wg{$KyqUp=WKk&piLXFvP3`sIj}_{UJQ^ML`{8g^bp z)WoyQx<)43$RsvnTbd{?I*X zo1M;hFm@tB<~HbgcxCVz1lC{_veJw_3n-5t*Jt1CH+an1fiDJjI|CUukSk34z5Qk* z{>%6~jkD9gZ*U=OhL53e)pqPo-x|j94HR)?4rR&Yqm;_)W)Be4#`ikivF5TmW=3Uc zgb^gC>&kfwr6(3t5hUN{K<1anRIU>-pFuf-VpWNSiHN`Vld zhXPAA(uZh0M4Dz4#4D{9H59v+C+b5A$@QBYU4F+5Lgy|p9j6T)P(C-f2$;9HK+;f4 zL>CQ8ZnmIcIB_ucp6)@)YVXlr>j3_}b-B|whaKxf%j;OfXc_u<%C+8*Xu&H-heP7( zM7mRx4HG*=gs!T>iV{~=P!e;t9K6K)@MNQ?@FO`q*CF zr%?o;V$)TUDHxeu>4LgA1*pk}r(?A9m5OyNA!EWu(?X;1VD!wER!dS-)3b8P> z$$UK*gXS7#rFWp#28ooY{^yf|+BbMo#j~ugH1UG!D#^r)D3uR{%N=|qCOZl>G6|j< z@rISth%_cAlkF&r0b4ff zxe7lM`O?Co%y)4K69-s-v2*yj0WJg5tgq$=oQ|{u^y}^lpPt!#3uZG z6f9(#SSBoX;8z}VR4rSXrR|8o4thYJTaIcg3+hnj^$6_MNyIhWXev(AzQ$hJTsp33 z7@=ujRjFW2`zo%TWt@pIUdyK83O8x0QwKuuumSfel7mB5Q^MPdlvt+Kl!%uNQ&gWx z)4oOpWc4qIBW@=7#fsE`!-oGi&gf*a~bT)R1g*nH=WF#2X0j& zMG8YPSwE!c z-XlGhSxHbFkzb=}+E-QmUDLjb_4k;lGtQ3W$J%nO=LVF;1$S(T?}vL~`A1+-$T^D$ zq`)#W9I>pZ0?UReqJu<8n5rw&!k13HVHGnRF=S!|G!NW#hGQPMRVl-Py1?12lmu~^ zLoW&8vgx)Z+7|(18*rZ@_EOWn79KHKwC!ZWAZzXVvv-k_GDCEAN#a&yKQ-+uN|+Mi zMoiI?X815QY|5Trs&`nV8wx?;MqR$sg~jh!Jpb&(@7>OHC%P=)TI3of7w zU=IU4?^`B%9vu)Zv#c4fzy;rTT-QTaG423|}vh!TNpl{?ln>;qwalS9d}x z)|fH2$v*ZZttsMq{cc3+iXM3PAOAPMBGSYDZ-4e@B0cQy|K9IC&PNaHb}Vy9pojhJ z3r-IUjI2))J?!H->0zT|sPwSF0js2ktqGrFbZh~7*x%Iju%5lTWsvj4!kQko7d+DR zu#K2(b>+ocO%EFmhWJs?RI4$Csp(-gJ?uh$lRU^4yICmHU#O>8;CDsIL$pSMJD1EW zYsbE;ARYxoRb|aCT=3mIvN}x(j#DVwxO8{Iw{>K;$zD48_Q2#Mp!6`ibi~U8eg4 zA&tH4EyU_9{pNX!l%NHT5@}jvH8@sffKUHE+??i*fDxE9t?}ZBd<7dzH0qV(NhA?= zicN)@*7%gzb~HF;No+I?H%m{9+(AuiY!QtlQB%$pbD$o@>m^Ud2vG@1OKMu<#f8K~ z5LQkjYFgu&1)_y>CyUjJh!ayi*>sn!+7TMu7M_+=zqKOKil#MQ0A-Web%-WpPz7XG_pRbhawRB?*&!89tAH@F zF-iY+=8SqmxNLA+m4V%W`*1*sIY7#I0LlTfwFR2i_>>YY&HJnfrS{_PvFYzrFYYuo znWi;fAI1SqYn9U^+O(RYM(6n3!UQj~GP? zn$|dC@*w=XrZt{tKO_-y1jV`@2@p+dygo&SrlvL4w8qeQ zJMF_I=pbUFQq`0mWkw7&rpIKmaWH^fJG5g`9!t#1=FfyIcU*Rf!JOdAZNPns1gA7K zWs?5HXwq?l04{1YT$u{Og5jo~c6}ovft#P>dk80j{ z&Qg_nCB_598){nPWY$g78f#i(O=}F9*XFqn>;&)HLo+BrhK;Jx_V1wSJ~Z!)h=$gJ zlO<<{BZV0>Gs7_}t1}$F71XxRAA5H9nyEc$|FUbn-&u>2!1DCC?)1j?6XW;kM5 zo#B`#^4%!AW>7*F53OigV@+$U zX^j_Dcv2_n!)K5uu4#=Q@xIptt?||ABQQtjdhL#51FlKvvDRrY4?M|7o*bC=v6)Gp{N>9ZJmKWYU%q_# zgp()#;K`ii$8M$iZH|qWC9s zL`)_h<*Q#ed%!Z@#tz6@@YOFa%|q=dxJ?<_DP=e$)PS%4&q|EvXLir5ua=DNT;{lga zkeypN?oR(cDtk)Fh<}r7r>QSD@y-Iz;v1Y0n0|#{FS>D|fOg@aZ#j!2MV%jAOBXIb zTNqMV0vj{ve<55hX8>z!%5GI``f%j;Ofz-S#X-}SQaOw=W^mgA9?BkUxl z^U&m*rZ9#q3FJuRZGS{#Omso#iZsv*(-g+gYH{LvPM0N8ApGre2Qr^V+_12O2*S!~ zL>h%L8g81VV#3;5)SM!-+27_idnO^16k9YZ_nw=cZ5}z+E!%HTLaDu{dtkcv9_>XY zd@E_azoV`9c)^79r{-EBsQM?C6||2m&wJ56f{LyTs;FhADx z7iG1{d`)|n19Nnp{VF31^~p{CGmRej^v2$|_il@x#^xDx)NjzUykWcRjzLkiM~*oR z+9NaQNFjIGeiBd)8zzX^=K<^z} zZdY1pL3W+T;tH_{gjXhQh5p#N+G_x9K{f-$xv*GsC4!>s&UGNvenEPOx&S|R#-4BlIFR^{lKWR^aR?)z4pL79+*O_X{@+Vid#L(hYyJb z=KX+EN&ffXMtjirWN`TJL275Y~n+}t}nNbO!ldXBNPh)ZG+_@p^U+#9VpB7)&1 zd|{Txt0)K$2_S48-n&I<(jfwWK^9in&45(+Oyl%p)Hsbc%`nQ)SaBiHWp0{D2$v0R zt1_?~a37UbWx6>9Pe_%q?}c5rYch$Q8sL?;#V$|cBwbKf17J5l#O1q1(UQi zAkt(EHia-JTHwlKj<{;ZIDEhsW~h9)<^!fmgaOyt$bky;BD<)$vJn3S zahRaPERK1sXF^z`ZQBTUnr8I|Hu2J;_0@0<_LE5g+nYRK?b_zR9Zv9eH1=%hF_5Ub zM_hnsL~sC*a*PHb5rEmcXFa(2CMZ&BPBLPkwHQW<%tZKu#OA_O+yCV!%WaExKRKjSe}4U-%tfPUVux1 z+}C+_1LrX$h7+~X$%6x#yw-CSOMu*uOC>3oLuxLq#&DlQQw}&LJg~c6XtIkzt3gKu z9c9niZ5Hm8w#d+;xN~UZ09I6v=^b!yuP^?S^ zVZm_I$=rG1R;6UFxIMrR+@YmPzh&0vVRryOKx;YydPGmn*r#-eJY+epvW5x`Hfxa* zp1#gK|8OTf3oDVErXF25_FU)TzKpF9dMr$JsDkTMiiVKpOIzrk5 z4n}!EFxihHIizf;+W?PU0QC~zPm#y~wT`oXNYUsabY=D!DUQgGt)0!va8Nbc{vBp< zRBgO#!QPiM!vX4OXa~}~T4si0RyIG@bcQ1jMPNzw7z#OO5rGs~W`-k{6;)u_Fhx`+ zgoLTOOeh#H>J2x-A!Ehd6sLc|aMKx%dEi#13+}Xi$Dy)7Fz)r{ zDhmjL8f3IKH5}5eu^pvEp~#MQEo4g;Ev2jHH+`i1ZswE=~UKx27 zq8(og_^HX0G`T$9gN3%AVk~8#=vy)r$z(Ks!fhTBZB-_Ga$Qtw7*GTPstwy6`f0kT zW}vMcyv&WXgg169zj<}~2-rl|Yj+%b0QS*0&TM0b_Qv+^iDe)6gEO_}Ykk{s{B{84 zC?7=Na(F4mnMVD{b_doav<^DD5J_GgjDyhlDnzd!(aDuvB>bA+53Q_1)`(<}EVI++ zGBpFW1n_05UbuSI@_gfBFYGIcUjMs}+j-X58NyQ+`7bAGajF>&n1w(WT;LakEh+H4 zZ<*+MbU?Ipvu3;k7kuAwT@PJ#iJt=X6j&fo+h&)&XN`jX1)~wTBP4;&1qt7WU3ty$ z1-JXA-=BN{(W2w69nZ9fTXiEOc=1Q@sllbST5~5bkNw8^T__{M+OwVT6Wa|v7e6=) zD_eZ{NSX{M_2{HJ#sD1gMg3g#j=twQuJ_$u@2#i!fp`Dj-`y4-c>kaM$)7yISA6ul zpVgk!FGpwmIKh*5;iS>Fv ze^PIB-IHMt7UqBX^5v8IZa;$UPe1?siE){K;!~KXoG2bM^HJ8X`a}1mZFV~2!5I8W z_yNFr)=%J7Kb1aZ58)~JBm!#yGp}Ya_AGedi~8(a{swx5@Qt{R4d~<>bo^#x>M!_4 zuW@$zx57RgLv^n0*qy#LlzADMlV>vd$X2{QSx$^?(OUQ$g`7blD^3jLjoXx2Ae_-G zP5MY^p1&&Wj6Jw>bFX#xe(Kg(1j#8YA5$#to>(TxpNuW< z#jS}7q_GLSy?3}~bOHiYNtsT>NE&lSMr%L^z;)rd?tp^~ffGS`!X^;=6G^E^OP-IP zKV99pHU+#z69_EvjdN#Q#2%%e>ucP?PrN>vZIfvjO)}+0&dG2&TT=#5)Us!%+sNU*XzJbmKw+?)c?HlJ1R;dZX9U zh1(JGsT3CusjQA;zL23TRb>f6WdfRZ-;VM`*M;0zmCmC85KSY+@$1O5hsRufp|~|Q zsp8Mbl?ja-VAzDD;S%4^g1~g-LFPQk2#>%dZE%fafk}y4aY#O7HJ0j{tz6F$Z&*1J zWfHBt*q4|J&wJdtOGXM4c!njhIAg~wcRp}l(%_`!lGl!zHh($~O*oClgV8fvT5C(S z!LxGt!1zuj!8li>f!Z85k0SXEuKFO5?MhuiRKiHAM^P#t2$viG60K(;cU^Dh7Hn+EK#_Gq}j5M755YgiKOw(WvBP>bBpWgi?D?_aN=G_h_$m0RP^a zaxH&oc^zvQ{P@Ml`cCs)2O4?0A@ee{N$qaU109W3>rA5o9WznhsL-cTB=$j2^-nA- zXdhXg_o96Sbzm7(QOgYKRO{IuaDOZ1Bg`6;i?T|=U#p}suYw-UpJhbGx@URAcGn#r z!8+!VV-5o#gbzAWh@!spi8h@8PY{ldi~G*=z;naQeBp%5TwW+@@wyJhz^?5Fp1pQ? z$FPI8{{r;hvE_E9g%)JjxwEMdi$Hi~(pDH}ld^Q>!BTZ)VQdhqcSKhc5oW7L0`H=& zNC;)K%%w|wYej7FEgrx`ouyHaFf`R34@_aQDpp*`g;XyN#D~O!^MGx`G#?RlusMX4 zVAZfIfB)*MLjUT6n|p@`sokrnzQRaf#N9IpeA1jF?u}L)5y9{hzA#JURTP92ioe!j z>9?dJ6`s-#gBI8@Cem1OA<$(mut^A)4Q@-cLV{1V0ryc^Ri>L$@Pt$u+iB!S&CU{3 zWvVj85oN;`(YzH(I|v}7-tayfS6?%8Dhxo=(tt>lG1wHsoM?e7k2&H78{_Z++ryy( zUGo7`C8@23RG1f83dNP3h`ERCC8lUQggLfjSAw}r>zNSNXrpZvA?-^lWxybyt-*dW zDPTj>)tI$Py&dimk8pWL1P1^q$7lc&0hq0O)`R_ufXnIJl>LlVCZZs8VP!b~hYqG81OrPdv&Dks~_ zEw%tr(IwI>cPr6xnNdVSxNI7319D?R8fJ;_hlizNk0>Q7MOL%pj$r?bigYd0YD&b* zhN+b+Flrv8)gmO{uv$$ip|0Xha>NOtDN0(V#HJ`RNNAxvkpUCdk%6FT$e??e3A)AF zX#@{dwoiRS736poI0bTF=h+RM$B-CK)J7){4rKCL&(Wd-0sz({0YV}CLuxLq#&DlQ zQw}&L414~%&}0{bR)dZRI?A52+brBGZIPixap%6t(S#M1V|vFNImpO9Y= z{;q6RbV{_d8>93n)8A<1EM}UI^%X)LY6MoH&h;>xV1BGEw;ySV!JOdAZNPoF-@?L&}D_4e;0nP%rWQ6p0K_ z>p1I&6pbE2S7wiq;)wj%+S#lO2UVl(-(eOV%LmC4TAD8*YR{#)`QqPXB`8rZXIZ zuSA4fl`B_^6n4%@k@Zl_OIU6NOOkg>x19x~b zS6)yTssk}(D-SxdQoS3K#jJIDgQDY5Ss)nqdUKTp1VIflTALaUY1i0}Qld~~N4u6a z@`0mP+MJeYN0ZVK+TJ*_Lgrv$Gnd|9Bx4a@y}MYX+QtJ1Vd{YYa#DOZl#or?PrK#ol#>HOPuMr*euH$x|HFk#Z)J2ZSsQ0L5K$}Y! zU2uV45Vm^2^S))G=g|StGRvCr3S97g2Ud`yt1j_Vpic4Ja%$V`viGb}(7#|b0(XQY z(77Pt`?Xr58NT3l-}L*J(0c2>?Ku9{j%V7#t-2A?r}!iI>_DJaYwiT*vER6`3q?9u zDz+1f?S_7VAJikcvjbqUhmWVpzKeRambiW{um&&*X$E7@f`h)OpNrnz_gu&IzT4}) z^%Otw?*H@GfBghM@ctkF(H{v9eDn|h!KEkl%h4G>4!_iq=~=_hi|C~0Nk1$h!utwe zbIrJd-rZmvfM?XLZ%?1CpF48K7J7^IdN=(1e){?6PwI^>@FVqLE&Z1-Up}etPH45m zzm3cM^Y1wDQ3O`XiQ+LcA8GxnKXgypW~VbAjKOOIvTs;|`U$-5C(@_vVfdbspXcnG z{swx5@SV7h4OoRQa~i1n%|`r}(aX}p$4~=nJ9ejUN!d;^=euO`kw$)fvH}=;4{Kr7 zSMK8(wCE%HeamJJg ziY1|MC#O$P$D};8e#5c}ECJ}p-uBoqa3qSDzW7`e{ zw_PBwv3s`VNw@OH-t3H6nZQO=ON%qag}>y@qjRT#5aqUgSSu%kAlh=m5zCEu3`Rj* zL(%Sj(ChhDP}@GAJ9OVXC?3YeqLg$bg5opF8OflC%yklgf>1sHzPZ#M<-)3o=n8JY zeN^@|=bZo4mz#KJ0Wc-X!1OCzn~82*D8L=Rd`QwA$WaIKTDoxgIlqv~>Nw^L8Ol;s zmLOCnplSDQ?HIn|_;uvj!(*<#P%M#}RPkrz%7n%ZFl?)8SXCKNN-Tf|$c{Y7oF{qL z5tyWHq;V`TDFiDH$%m}QQeCr^>p9{Lt3a`tKVu?x|E0q79(V4Nk-`L?VQDPR*fGnU z51f}YIBB`$Z||*jDw(1^(HzleJQzK*rM0$H8$2tQ4~*|r5{z?28f#S3$RCuv!Brmw zvR$bwh)Nhq^(ac^1L1PxU!wI~TvnNQ!^)X!8l@PTOq!-*pckVZwXZOPi(E=n>zGN% zB*hku%Dv~NXPZFSearUSlTd2!=^mt=_8#rE4&dKgmpgrP*s(seybcs+e*9u&eW!V@ z18qHBdkCEwzOlP{a%dr{&{(yeo6ZMb*YfhamI$i;iDd=tBg^w%w2#KVl(rqU%%J9^ z33t0vKEkXqxhShm=4+KS=2g(6`3*WU);-G`w!7{a6jgiVn8TnwGJ}p3qNuxjqD?2j z6NIDVgwZD=JU7hD7fvwE<%Oc|@$2(-j~8-u7L+Oq`)T_x{Gfeoxm{_Y1=)4(Y%0Ve z9eHKaRv2fKvUKIaQgvlvY!It=MDvYksfFGwVISXJw0j7lY_@l7iEph4xNh+PCh9DW zx`d&r_IO|l^GmVfLN26wJs&ab)sDu3+tOTouUHSW0UlsaSAKctKJV@yUCrh!O z57|ph(RK)PY?G}7bD7pNA*|6x+ucFhN>s{#LC|mw_LE5g8=7Ip+)(NrY>#+^%QGT4 z07yAT1CR*7?6n%}L99nXAX0Y;e;)-4nQk2u7AG?+k2$JaSDCH3Xt%<23N75vAhED$ zdPuJ7B;p!wG!;|wg6GlRn07SE75V8QA9$x zY#MF@a$`aoW{K~oNDdBJ&5k>QeL5;?vrMZg5ic92R<6LPd5~6%kbJ{xwWMVP{t{DS zQxq8_v{0VNfC=l!K+rT~&^^oq-D2(ZQgUy+3Y-GDuk-8%&SOXnCu*aU2M02Ft>LE3kR%5u&p(zI(6NasSU1+k4L90PW1RZ71*=-i?mA1%0Dr|j|qX{c2 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

    _$5>vWeDnm=+-ais~iGb2nk|k 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 Smn@;KMDP@7D?Fubz4d<}Lt$J1 diff --git a/core/src/main/resources/bedrock/creative_items.1_19_50.json b/core/src/main/resources/bedrock/creative_items.1_19_50.json index 243826c9e70..4ed5f819462 100644 --- a/core/src/main/resources/bedrock/creative_items.1_19_50.json +++ b/core/src/main/resources/bedrock/creative_items.1_19_50.json @@ -28,6 +28,14 @@ "id" : "minecraft:mangrove_planks", "blockRuntimeId" : 1570 }, + { + "id" : "minecraft:bamboo_planks", + "blockRuntimeId" : 8202 + }, + { + "id" : "minecraft:bamboo_mosaic", + "blockRuntimeId" : 12438 + }, { "id" : "minecraft:crimson_planks", "blockRuntimeId" : 7399 @@ -152,6 +160,10 @@ "id" : "minecraft:mangrove_fence", "blockRuntimeId" : 10405 }, + { + "id" : "minecraft:bamboo_fence", + "blockRuntimeId" : 863 + }, { "id" : "minecraft:nether_brick_fence", "blockRuntimeId" : 6071 @@ -192,6 +204,10 @@ "id" : "minecraft:mangrove_fence_gate", "blockRuntimeId" : 6406 }, + { + "id" : "minecraft:bamboo_fence_gate", + "blockRuntimeId" : 7611 + }, { "id" : "minecraft:crimson_fence_gate", "blockRuntimeId" : 6826 @@ -240,6 +256,14 @@ "id" : "minecraft:mangrove_stairs", "blockRuntimeId" : 6376 }, + { + "id" : "minecraft:bamboo_stairs", + "blockRuntimeId" : 1339 + }, + { + "id" : "minecraft:bamboo_mosaic_stairs", + "blockRuntimeId" : 9958 + }, { "id" : "minecraft:stone_brick_stairs", "blockRuntimeId" : 1554 @@ -421,6 +445,9 @@ { "id" : "minecraft:mangrove_door" }, + { + "id" : "minecraft:bamboo_door" + }, { "id" : "minecraft:iron_door" }, @@ -458,6 +485,10 @@ "id" : "minecraft:mangrove_trapdoor", "blockRuntimeId" : 6266 }, + { + "id" : "minecraft:bamboo_trapdoor", + "blockRuntimeId" : 7828 + }, { "id" : "minecraft:iron_trapdoor", "blockRuntimeId" : 549 @@ -666,6 +697,14 @@ "id" : "minecraft:mangrove_slab", "blockRuntimeId" : 1772 }, + { + "id" : "minecraft:bamboo_slab", + "blockRuntimeId" : 10300 + }, + { + "id" : "minecraft:bamboo_mosaic_slab", + "blockRuntimeId" : 4081 + }, { "id" : "minecraft:stone_block_slab", "blockRuntimeId" : 6054 @@ -2707,6 +2746,9 @@ { "id" : "minecraft:trader_llama_spawn_egg" }, + { + "id" : "minecraft:camel_spawn_egg" + }, { "id" : "minecraft:ghast_spawn_egg" }, @@ -4073,6 +4115,10 @@ "id" : "minecraft:bookshelf", "blockRuntimeId" : 10443 }, + { + "id" : "minecraft:chiseled_bookshelf", + "blockRuntimeId" : 326 + }, { "id" : "minecraft:lectern", "blockRuntimeId" : 10718 @@ -4263,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" }, @@ -4979,6 +5058,9 @@ { "id" : "minecraft:mangrove_boat" }, + { + "id" : "minecraft:bamboo_raft" + }, { "id" : "minecraft:oak_chest_boat" }, @@ -5000,6 +5082,9 @@ { "id" : "minecraft:mangrove_chest_boat" }, + { + "id" : "minecraft:bamboo_chest_raft" + }, { "id" : "minecraft:rail", "blockRuntimeId" : 5697 @@ -5071,6 +5156,10 @@ "id" : "minecraft:mangrove_button", "blockRuntimeId" : 10840 }, + { + "id" : "minecraft:bamboo_button", + "blockRuntimeId" : 10238 + }, { "id" : "minecraft:stone_button", "blockRuntimeId" : 826 @@ -5119,6 +5208,10 @@ "id" : "minecraft:mangrove_pressure_plate", "blockRuntimeId" : 5646 }, + { + "id" : "minecraft:bamboo_pressure_plate", + "blockRuntimeId" : 9819 + }, { "id" : "minecraft:crimson_pressure_plate", "blockRuntimeId" : 12447 diff --git a/core/src/main/resources/bedrock/entity_identifiers.dat b/core/src/main/resources/bedrock/entity_identifiers.dat index 297d30537148f5cb80ecd63810b352f3e4c7ab03..8f0c9ce8ef8f468bf6b1f2b530a131b33f6023c2 100644 GIT binary patch delta 58 zcmV-A0LA~(JdZsM3IGWPX=H3^b94&w0h2)i7?aEk3$ZZX9g&a|lfVND3}az!Wo!cj Qa%p6g0+XQ(6O#@eL@PfMnE(I) delta 34 qcmeCTy=247#lXpynUa%PT*CE%ak3+$#N;#F+#3z<$xil`a{&OlNecr2 diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 19094e36080..5bd26dd735b 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 19094e36080d78212534f5f0d073fb5d3f68a89f +Subproject commit 5bd26dd735bd89dd50e5c55a0d022f7c70916300 From cc3037d6c53400b4f1a33e7bc5e4be239e6f1331 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 2 Dec 2022 14:11:56 -0500 Subject: [PATCH 330/358] Update to 1.19.3-rc1; various changes and fixes --- .../manager/GeyserSpigotWorldManager.java | 10 +- .../org/geysermc/geyser/level/GameRule.java | 117 +++++++----------- .../geyser/level/GeyserWorldManager.java | 25 +--- .../geysermc/geyser/level/WorldManager.java | 16 ++- .../protocol/java/JavaCommandsTranslator.java | 33 +++-- .../entity/JavaSoundEntityTranslator.java | 2 +- .../JavaPlayerInfoRemoveTranslator.java | 4 +- .../JavaPlayerInfoUpdateTranslator.java | 66 +++++----- .../java/level/JavaCustomSoundTranslator.java | 49 -------- .../java/level/JavaSoundTranslator.java | 2 +- .../geysermc/geyser/util/SettingsUtils.java | 10 +- .../org/geysermc/geyser/util/SoundUtils.java | 28 ++--- 12 files changed, 134 insertions(+), 228 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java 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 7bb8f1666ee..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 @@ -38,7 +38,7 @@ import org.bukkit.inventory.meta.BookMeta; import org.bukkit.plugin.Plugin; import org.geysermc.geyser.level.GameRule; -import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; @@ -51,7 +51,7 @@ /** * The base world manager to use when there is no supported NMS revision */ -public class GeyserSpigotWorldManager extends GeyserWorldManager { +public class GeyserSpigotWorldManager extends WorldManager { private final Plugin plugin; public GeyserSpigotWorldManager(Plugin plugin) { @@ -151,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 @@ -165,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 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/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index 69f5d5beb20..b3a727d2636 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,8 @@ import com.nukkitx.nbt.NbtMap; import org.geysermc.geyser.session.GeyserSession; +import java.util.Locale; + /** * Class that manages or retrieves various information * from the world. Everything in this class should be @@ -105,7 +107,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 +118,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 +135,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 +145,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 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 8f4e46454da..14ff1a51afc 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 @@ -234,18 +234,18 @@ private static Object mapCommandType(GeyserSession session, CommandNode node) { 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 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; - } + yield switch (resource) { + // minecraft:worldgen/biome is also valid but we currently don't cache biome IDs + case "minecraft:attribute" -> ATTRIBUTES; + case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; + case "minecraft:entity_type" -> Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]); + case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS; + default -> CommandParam.STRING; + }; } default -> CommandParam.STRING; }; @@ -325,7 +325,7 @@ public void buildChildren(GeyserSession session, CommandNode[] allNodes) { 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 @@ -347,6 +347,21 @@ public void buildChildren(GeyserSession session, CommandNode[] 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(); + } + /** * Comparing CommandNode type a and b, determine if they are in the same overload. *

    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/JavaPlayerInfoRemoveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java index a47683bd2e9..4e9f0ca422a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java @@ -58,8 +58,6 @@ public void translate(GeyserSession session, ClientboundPlayerInfoRemovePacket p } } - if (!translate.getEntries().isEmpty()) { - session.sendUpstreamPacket(translate); - } + 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 index 24989c7010c..a69c031699d 100644 --- 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 @@ -50,28 +50,23 @@ public void translate(GeyserSession session, ClientboundPlayerInfoUpdatePacket p translate.setAction(PlayerListPacket.Action.ADD); for (PlayerListEntry entry : packet.getEntries()) { - for (PlayerListEntryAction action : packet.getActions()) { - if (action != PlayerListEntryAction.ADD_PLAYER) { - continue; - } + GameProfile profile = entry.getProfile(); + PlayerEntity playerEntity; + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); - 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()); - } + 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(); + GameProfile.Property textures = profile.getProperty("textures"); + String texturesProperty = textures == null ? null : textures.getValue(); - if (playerEntity == null) { - // It's a new player - playerEntity = new PlayerEntity( + if (playerEntity == null) { + // It's a new player + playerEntity = new PlayerEntity( session, -1, session.getEntityCache().getNextEntityId().incrementAndGet(), @@ -81,28 +76,27 @@ public void translate(GeyserSession session, ClientboundPlayerInfoUpdatePacket p 0, 0, 0, profile.getName(), texturesProperty - ); + ); - session.getEntityCache().addPlayerEntity(playerEntity); - } else { - playerEntity.setUsername(profile.getName()); - playerEntity.setTexturesProperty(texturesProperty); - } + session.getEntityCache().addPlayerEntity(playerEntity); + } else { + playerEntity.setUsername(profile.getName()); + playerEntity.setTexturesProperty(texturesProperty); + } - playerEntity.setPlayerList(true); + 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 -> + // 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); + } else { + playerEntity.setValid(true); + PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - translate.getEntries().add(playerListEntry); - } + translate.getEntries().add(playerListEntry); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java deleted file mode 100644 index 00894bd8bb7..00000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCustomSoundTranslator.java +++ /dev/null @@ -1,49 +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.level; - -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundCustomSoundPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.util.SoundUtils; - -@Translator(packet = ClientboundCustomSoundPacket.class) -public class JavaCustomSoundTranslator 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); - } -} 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 Date: Sat, 3 Dec 2022 17:20:33 -0500 Subject: [PATCH 331/358] Revert "Drop anything below 1.19.50" This reverts commit 58eede37 --- .../geysermc/geyser/network/GameProtocol.java | 18 + .../populator/BlockRegistryPopulator.java | 3 + .../populator/ItemRegistryPopulator.java | 1 + .../BedrockRequestAbilityTranslator.java | 5 + .../geysermc/geyser/util/DimensionUtils.java | 18 +- .../bedrock/block_palette.1_19_20.nbt | Bin 0 -> 55132 bytes .../bedrock/creative_items.1_19_20.json | 5440 +++++++++++++++++ .../bedrock/runtime_item_states.1_19_20.json | 4530 ++++++++++++++ 8 files changed, 10007 insertions(+), 8 deletions(-) create mode 100644 core/src/main/resources/bedrock/block_palette.1_19_20.nbt create mode 100644 core/src/main/resources/bedrock/creative_items.1_19_20.json create mode 100644 core/src/main/resources/bedrock/runtime_item_states.1_19_20.json diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 56159cfa8d8..e85dc689d77 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -60,6 +60,14 @@ public final class GameProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { + 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); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } @@ -77,6 +85,16 @@ public static BedrockPacketCodec getBedrockCodec(int protocolVersion) { return null; } + /* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */ + + 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(); + } + /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * 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 72116f548b6..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 @@ -29,6 +29,8 @@ import com.fasterxml.jackson.databind.node.ArrayNode; 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; @@ -72,6 +74,7 @@ public static void populate() { private static void registerBedrockBlocks() { 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(); 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 645cf17ba4d..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 @@ -77,6 +77,7 @@ private record PaletteVersion(int protocolVersion, Map additiona public static void populate() { Map paletteVersions = new Object2ObjectOpenHashMap<>(); + 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(); 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 index 44953dfdac2..fe8150d406b 100644 --- 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 @@ -43,6 +43,11 @@ public class BedrockRequestAbilityTranslator extends PacketTranslatorX5GfPypkF`Ueo`Ex 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

    N!K?VWkIdC++V5CX^Dc;QTi}}9T=lerttP4d#b zb@t-=s{f05Iog>R1Xnsx(kBjuLM1(5y> z0`qrk9IcJ3+X^bauWCFyv*Q|9W3$obUsIWO6GmRVc&~n)wYRmd-o(d};}KuUSvr_< zo?aev3X z@?O();jB+SQW0T>*lBV|#ERayqI6_69C>_wlrJ2B*~T_zyVA)LGk_H(QOpU&q^aPc zgIaaCmZOZhK#J7-|J%$y!aAdFvA{53{F3{;m8ORSjLa;Yd0HZz1AiZcz$hmDM`Awa zn{-Jc@GLf5N>k!DrZJRc<1Di;v;f9x^n#gwRmlZFpeT_!p?pUzZBF&mka*oEPSx^3 z%4?=6**e=DBe_)h1LL%53-I%rtiTCBzA~3~2^a{$*?Ucsrhl{dEK;Yra)wkk6D;f` zTGJY90D-s%PIKUCK4_XQuZ!y-86EpCGadf7nMeO+rrdvH=H-8HrUaJBX=-j)FB5`v zQIKt9Qv^8%>R&&^`QnSxu&Fp5}1Li)>l8YTv9@r0ZJG;P5AP^?cZ9k;c1oTm@6?}pJ zIzgZEZyyB>sSSb4mB%**{(%8@LZt)k{ASHq_V=ML1%3ZzX666fOlaPgjik=}JWRvd z?+W4I@6-2dtjAl;)4`RdKUK2&n$d0piAM?yg}^}O7o^ZzRG4s>o2^<>lpu37+`n>F zuqyr-85+24$wO9Q)v)eIJ?^yZpk^agqM~c~^5}C*Mt3)pwabX8U4Nd__3uP7Z->8O zKwPFCId^Pi0`xyHR#^k9M&`)F7Hs@kBQ5%K94Lx^#YN~G4y%nvc$ur@K>|$EG zAKNdmu)?UQPP2YS zU=&B@Ra9zNLe2ROO+n^5G1=p$??b{bYTsXUWu8g(qoWFqbDVo8GMsqN@FsYKVTl z8lK9|?b-#)Zwz9VH%lp}R{E-1_D8c(moRQtfWn;P2kME5d;EbqxtsOQ1YUdN?gTlp vX!0f2XX*s~rfjqL4L=}#m+!XCdF;?V!=^KE4f37S^XE`*=^ZZ(bcp{2Ge*tJ literal 0 HcmV?d00001 diff --git a/core/src/main/resources/bedrock/creative_items.1_19_20.json b/core/src/main/resources/bedrock/creative_items.1_19_20.json new file mode 100644 index 00000000000..98d9e007a2e --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_19_20.json @@ -0,0 +1,5440 @@ +{ + "items" : [ + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6073 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6074 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6075 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6076 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6077 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 6078 + }, + { + "id" : "minecraft:mangrove_planks", + "blockRuntimeId" : 949 + }, + { + "id" : "minecraft:crimson_planks", + "blockRuntimeId" : 4852 + }, + { + "id" : "minecraft:warped_planks", + "blockRuntimeId" : 922 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1184 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1185 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1186 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1187 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1188 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1189 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1196 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1191 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1192 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1190 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1193 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1197 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1194 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1195 + }, + { + "id" : "minecraft:blackstone_wall", + "blockRuntimeId" : 3932 + }, + { + "id" : "minecraft:polished_blackstone_wall", + "blockRuntimeId" : 6726 + }, + { + "id" : "minecraft:polished_blackstone_brick_wall", + "blockRuntimeId" : 973 + }, + { + "id" : "minecraft:cobbled_deepslate_wall", + "blockRuntimeId" : 8084 + }, + { + "id" : "minecraft:deepslate_tile_wall", + "blockRuntimeId" : 5073 + }, + { + "id" : "minecraft:polished_deepslate_wall", + "blockRuntimeId" : 7819 + }, + { + "id" : "minecraft:deepslate_brick_wall", + "blockRuntimeId" : 431 + }, + { + "id" : "minecraft:mud_brick_wall", + "blockRuntimeId" : 732 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7366 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7367 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7368 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7369 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7370 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 7371 + }, + { + "id" : "minecraft:mangrove_fence", + "blockRuntimeId" : 6635 + }, + { + "id" : "minecraft:nether_brick_fence", + "blockRuntimeId" : 4292 + }, + { + "id" : "minecraft:crimson_fence", + "blockRuntimeId" : 7998 + }, + { + "id" : "minecraft:warped_fence", + "blockRuntimeId" : 5855 + }, + { + "id" : "minecraft:fence_gate", + "blockRuntimeId" : 76 + }, + { + "id" : "minecraft:spruce_fence_gate", + "blockRuntimeId" : 6586 + }, + { + "id" : "minecraft:birch_fence_gate", + "blockRuntimeId" : 3779 + }, + { + "id" : "minecraft:jungle_fence_gate", + "blockRuntimeId" : 5367 + }, + { + "id" : "minecraft:acacia_fence_gate", + "blockRuntimeId" : 7588 + }, + { + "id" : "minecraft:dark_oak_fence_gate", + "blockRuntimeId" : 4175 + }, + { + "id" : "minecraft:mangrove_fence_gate", + "blockRuntimeId" : 4627 + }, + { + "id" : "minecraft:crimson_fence_gate", + "blockRuntimeId" : 4663 + }, + { + "id" : "minecraft:warped_fence_gate", + "blockRuntimeId" : 5401 + }, + { + "id" : "minecraft:normal_stone_stairs", + "blockRuntimeId" : 635 + }, + { + "id" : "minecraft:stone_stairs", + "blockRuntimeId" : 3710 + }, + { + "id" : "minecraft:mossy_cobblestone_stairs", + "blockRuntimeId" : 4094 + }, + { + "id" : "minecraft:oak_stairs", + "blockRuntimeId" : 273 + }, + { + "id" : "minecraft:spruce_stairs", + "blockRuntimeId" : 128 + }, + { + "id" : "minecraft:birch_stairs", + "blockRuntimeId" : 7005 + }, + { + "id" : "minecraft:jungle_stairs", + "blockRuntimeId" : 6969 + }, + { + "id" : "minecraft:acacia_stairs", + "blockRuntimeId" : 6202 + }, + { + "id" : "minecraft:dark_oak_stairs", + "blockRuntimeId" : 5065 + }, + { + "id" : "minecraft:mangrove_stairs", + "blockRuntimeId" : 4597 + }, + { + "id" : "minecraft:stone_brick_stairs", + "blockRuntimeId" : 933 + }, + { + "id" : "minecraft:mossy_stone_brick_stairs", + "blockRuntimeId" : 5885 + }, + { + "id" : "minecraft:sandstone_stairs", + "blockRuntimeId" : 3589 + }, + { + "id" : "minecraft:smooth_sandstone_stairs", + "blockRuntimeId" : 3629 + }, + { + "id" : "minecraft:red_sandstone_stairs", + "blockRuntimeId" : 5352 + }, + { + "id" : "minecraft:smooth_red_sandstone_stairs", + "blockRuntimeId" : 5548 + }, + { + "id" : "minecraft:granite_stairs", + "blockRuntimeId" : 3539 + }, + { + "id" : "minecraft:polished_granite_stairs", + "blockRuntimeId" : 4152 + }, + { + "id" : "minecraft:diorite_stairs", + "blockRuntimeId" : 4393 + }, + { + "id" : "minecraft:polished_diorite_stairs", + "blockRuntimeId" : 6716 + }, + { + "id" : "minecraft:andesite_stairs", + "blockRuntimeId" : 5310 + }, + { + "id" : "minecraft:polished_andesite_stairs", + "blockRuntimeId" : 7030 + }, + { + "id" : "minecraft:brick_stairs", + "blockRuntimeId" : 6532 + }, + { + "id" : "minecraft:nether_brick_stairs", + "blockRuntimeId" : 106 + }, + { + "id" : "minecraft:red_nether_brick_stairs", + "blockRuntimeId" : 6604 + }, + { + "id" : "minecraft:end_brick_stairs", + "blockRuntimeId" : 6384 + }, + { + "id" : "minecraft:quartz_stairs", + "blockRuntimeId" : 4769 + }, + { + "id" : "minecraft:smooth_quartz_stairs", + "blockRuntimeId" : 7702 + }, + { + "id" : "minecraft:purpur_stairs", + "blockRuntimeId" : 7757 + }, + { + "id" : "minecraft:prismarine_stairs", + "blockRuntimeId" : 7265 + }, + { + "id" : "minecraft:dark_prismarine_stairs", + "blockRuntimeId" : 7432 + }, + { + "id" : "minecraft:prismarine_bricks_stairs", + "blockRuntimeId" : 206 + }, + { + "id" : "minecraft:crimson_stairs", + "blockRuntimeId" : 6282 + }, + { + "id" : "minecraft:warped_stairs", + "blockRuntimeId" : 3720 + }, + { + "id" : "minecraft:blackstone_stairs", + "blockRuntimeId" : 7021 + }, + { + "id" : "minecraft:polished_blackstone_stairs", + "blockRuntimeId" : 4299 + }, + { + "id" : "minecraft:polished_blackstone_brick_stairs", + "blockRuntimeId" : 4479 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 4606 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 4589 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 4307 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 353 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 395 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 3904 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 6169 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_stairs", + "blockRuntimeId" : 5842 + }, + { + "id" : "minecraft:cobbled_deepslate_stairs", + "blockRuntimeId" : 147 + }, + { + "id" : "minecraft:deepslate_tile_stairs", + "blockRuntimeId" : 4655 + }, + { + "id" : "minecraft:polished_deepslate_stairs", + "blockRuntimeId" : 294 + }, + { + "id" : "minecraft:deepslate_brick_stairs", + "blockRuntimeId" : 7424 + }, + { + "id" : "minecraft:mud_brick_stairs", + "blockRuntimeId" : 5524 + }, + { + "id" : "minecraft:wooden_door" + }, + { + "id" : "minecraft:spruce_door" + }, + { + "id" : "minecraft:birch_door" + }, + { + "id" : "minecraft:jungle_door" + }, + { + "id" : "minecraft:acacia_door" + }, + { + "id" : "minecraft:dark_oak_door" + }, + { + "id" : "minecraft:mangrove_door" + }, + { + "id" : "minecraft:iron_door" + }, + { + "id" : "minecraft:crimson_door" + }, + { + "id" : "minecraft:warped_door" + }, + { + "id" : "minecraft:trapdoor", + "blockRuntimeId" : 229 + }, + { + "id" : "minecraft:spruce_trapdoor", + "blockRuntimeId" : 6554 + }, + { + "id" : "minecraft:birch_trapdoor", + "blockRuntimeId" : 6652 + }, + { + "id" : "minecraft:jungle_trapdoor", + "blockRuntimeId" : 5383 + }, + { + "id" : "minecraft:acacia_trapdoor", + "blockRuntimeId" : 5591 + }, + { + "id" : "minecraft:dark_oak_trapdoor", + "blockRuntimeId" : 7504 + }, + { + "id" : "minecraft:mangrove_trapdoor", + "blockRuntimeId" : 4487 + }, + { + "id" : "minecraft:iron_trapdoor", + "blockRuntimeId" : 321 + }, + { + "id" : "minecraft:crimson_trapdoor", + "blockRuntimeId" : 4335 + }, + { + "id" : "minecraft:warped_trapdoor", + "blockRuntimeId" : 4735 + }, + { + "id" : "minecraft:iron_bars", + "blockRuntimeId" : 4803 + }, + { + "id" : "minecraft:glass", + "blockRuntimeId" : 6166 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1135 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1143 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1142 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1150 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1147 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1149 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1136 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1139 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1140 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1148 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1144 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1138 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1146 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1145 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1137 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1141 + }, + { + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 5977 + }, + { + "id" : "minecraft:glass_pane", + "blockRuntimeId" : 5235 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4854 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4862 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4861 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4869 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4866 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4868 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4855 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4858 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4859 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4867 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4863 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4857 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4865 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4864 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4856 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4860 + }, + { + "id" : "minecraft:ladder", + "blockRuntimeId" : 8264 + }, + { + "id" : "minecraft:scaffolding", + "blockRuntimeId" : 3573 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4272 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5824 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4275 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5795 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5272 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5273 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5274 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5275 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5276 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5277 + }, + { + "id" : "minecraft:mangrove_slab", + "blockRuntimeId" : 1151 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4277 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5822 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4273 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5825 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5796 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5790 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5826 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5807 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5812 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5813 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5810 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5811 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5809 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5808 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4276 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4279 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5797 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 5806 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 4278 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 5823 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5791 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5792 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5793 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 5794 + }, + { + "id" : "minecraft:crimson_slab", + "blockRuntimeId" : 5902 + }, + { + "id" : "minecraft:warped_slab", + "blockRuntimeId" : 6486 + }, + { + "id" : "minecraft:blackstone_slab", + "blockRuntimeId" : 912 + }, + { + "id" : "minecraft:polished_blackstone_slab", + "blockRuntimeId" : 6020 + }, + { + "id" : "minecraft:polished_blackstone_brick_slab", + "blockRuntimeId" : 4194 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 5237 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 6602 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 6055 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 5284 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 7817 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 249 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 6547 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 710 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 7312 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 288 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 4293 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 3718 + }, + { + "id" : "minecraft:mud_brick_slab", + "blockRuntimeId" : 3912 + }, + { + "id" : "minecraft:brick_block", + "blockRuntimeId" : 4767 + }, + { + "id" : "minecraft:chiseled_nether_bricks", + "blockRuntimeId" : 7251 + }, + { + "id" : "minecraft:cracked_nether_bricks", + "blockRuntimeId" : 4554 + }, + { + "id" : "minecraft:quartz_bricks", + "blockRuntimeId" : 6353 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6549 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6550 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6551 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6552 + }, + { + "id" : "minecraft:end_bricks", + "blockRuntimeId" : 281 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6089 + }, + { + "id" : "minecraft:polished_blackstone_bricks", + "blockRuntimeId" : 4682 + }, + { + "id" : "minecraft:cracked_polished_blackstone_bricks", + "blockRuntimeId" : 7216 + }, + { + "id" : "minecraft:gilded_blackstone", + "blockRuntimeId" : 4588 + }, + { + "id" : "minecraft:chiseled_polished_blackstone", + "blockRuntimeId" : 5064 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 4583 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 4162 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 5466 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 5366 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 5236 + }, + { + "id" : "minecraft:cobblestone", + "blockRuntimeId" : 3617 + }, + { + "id" : "minecraft:mossy_cobblestone", + "blockRuntimeId" : 252 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 6672 + }, + { + "id" : "minecraft:smooth_stone", + "blockRuntimeId" : 4584 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3655 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3656 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3657 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3658 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6582 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6583 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6584 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6585 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 5400 + }, + { + "id" : "minecraft:dried_kelp_block", + "blockRuntimeId" : 7981 + }, + { + "id" : "minecraft:gold_block", + "blockRuntimeId" : 291 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 8263 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 4653 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 595 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 8248 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 3555 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 7736 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 696 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 709 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 7544 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 4691 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 6168 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 7199 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 5480 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 7295 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 3811 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 4853 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 214 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 1161 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 272 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 4288 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 8262 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 5271 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 363 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3698 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3700 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3699 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3701 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6087 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6088 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 4235 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 894 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 4478 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 697 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 4236 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 7274 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 146 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 3777 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 8261 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3460 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3468 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3467 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3475 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3472 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3474 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3461 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3464 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3465 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3473 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3469 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3463 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3471 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3470 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3462 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3466 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 951 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 959 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 958 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 966 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 963 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 965 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 952 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 955 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 956 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 964 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 960 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 954 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 962 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 961 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 953 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 957 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6266 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6274 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6273 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6281 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6278 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6280 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6267 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6270 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6271 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6279 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6275 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6269 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6277 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6276 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6268 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 6272 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 662 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 670 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 669 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 677 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 674 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 676 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 663 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 666 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 667 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 675 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 671 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 665 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 673 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 672 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 664 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 668 + }, + { + "id" : "minecraft:clay", + "blockRuntimeId" : 7126 + }, + { + "id" : "minecraft:hardened_clay", + "blockRuntimeId" : 643 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6178 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6186 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6185 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6193 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6190 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6192 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6179 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6182 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6183 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6191 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6187 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6181 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6189 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6188 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6180 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6184 + }, + { + "id" : "minecraft:white_glazed_terracotta", + "blockRuntimeId" : 5575 + }, + { + "id" : "minecraft:silver_glazed_terracotta", + "blockRuntimeId" : 3533 + }, + { + "id" : "minecraft:gray_glazed_terracotta", + "blockRuntimeId" : 8255 + }, + { + "id" : "minecraft:black_glazed_terracotta", + "blockRuntimeId" : 5836 + }, + { + "id" : "minecraft:brown_glazed_terracotta", + "blockRuntimeId" : 3549 + }, + { + "id" : "minecraft:red_glazed_terracotta", + "blockRuntimeId" : 4169 + }, + { + "id" : "minecraft:orange_glazed_terracotta", + "blockRuntimeId" : 1153 + }, + { + "id" : "minecraft:yellow_glazed_terracotta", + "blockRuntimeId" : 915 + }, + { + "id" : "minecraft:lime_glazed_terracotta", + "blockRuntimeId" : 223 + }, + { + "id" : "minecraft:green_glazed_terracotta", + "blockRuntimeId" : 6612 + }, + { + "id" : "minecraft:cyan_glazed_terracotta", + "blockRuntimeId" : 5360 + }, + { + "id" : "minecraft:light_blue_glazed_terracotta", + "blockRuntimeId" : 5473 + }, + { + "id" : "minecraft:blue_glazed_terracotta", + "blockRuntimeId" : 5467 + }, + { + "id" : "minecraft:purple_glazed_terracotta", + "blockRuntimeId" : 7013 + }, + { + "id" : "minecraft:magenta_glazed_terracotta", + "blockRuntimeId" : 967 + }, + { + "id" : "minecraft:pink_glazed_terracotta", + "blockRuntimeId" : 6541 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 7716 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 7718 + }, + { + "id" : "minecraft:packed_mud", + "blockRuntimeId" : 283 + }, + { + "id" : "minecraft:mud_bricks", + "blockRuntimeId" : 6891 + }, + { + "id" : "minecraft:nether_wart_block", + "blockRuntimeId" : 4295 + }, + { + "id" : "minecraft:warped_wart_block", + "blockRuntimeId" : 5907 + }, + { + "id" : "minecraft:shroomlight", + "blockRuntimeId" : 5063 + }, + { + "id" : "minecraft:crimson_nylium", + "blockRuntimeId" : 4191 + }, + { + "id" : "minecraft:warped_nylium", + "blockRuntimeId" : 6351 + }, + { + "id" : "minecraft:basalt", + "blockRuntimeId" : 4351 + }, + { + "id" : "minecraft:polished_basalt", + "blockRuntimeId" : 24 + }, + { + "id" : "minecraft:smooth_basalt", + "blockRuntimeId" : 1159 + }, + { + "id" : "minecraft:soul_soil", + "blockRuntimeId" : 5832 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 5753 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 5754 + }, + { + "id" : "minecraft:farmland", + "blockRuntimeId" : 3914 + }, + { + "id" : "minecraft:grass", + "blockRuntimeId" : 6977 + }, + { + "id" : "minecraft:grass_path", + "blockRuntimeId" : 8083 + }, + { + "id" : "minecraft:podzol", + "blockRuntimeId" : 4652 + }, + { + "id" : "minecraft:mycelium", + "blockRuntimeId" : 3685 + }, + { + "id" : "minecraft:mud", + "blockRuntimeId" : 6686 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 655 + }, + { + "id" : "minecraft:iron_ore", + "blockRuntimeId" : 4692 + }, + { + "id" : "minecraft:gold_ore", + "blockRuntimeId" : 914 + }, + { + "id" : "minecraft:diamond_ore", + "blockRuntimeId" : 4363 + }, + { + "id" : "minecraft:lapis_ore", + "blockRuntimeId" : 7701 + }, + { + "id" : "minecraft:redstone_ore", + "blockRuntimeId" : 4291 + }, + { + "id" : "minecraft:coal_ore", + "blockRuntimeId" : 4289 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 3556 + }, + { + "id" : "minecraft:emerald_ore", + "blockRuntimeId" : 7349 + }, + { + "id" : "minecraft:quartz_ore", + "blockRuntimeId" : 4503 + }, + { + "id" : "minecraft:nether_gold_ore", + "blockRuntimeId" : 27 + }, + { + "id" : "minecraft:ancient_debris", + "blockRuntimeId" : 6109 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 7275 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 6108 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 8040 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 7264 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 6618 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 6352 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 7198 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 105 + }, + { + "id" : "minecraft:gravel", + "blockRuntimeId" : 8289 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 656 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 658 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 660 + }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 7587 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 253 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 657 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 659 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 661 + }, + { + "id" : "minecraft:polished_blackstone", + "blockRuntimeId" : 3684 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 7756 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4197 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4198 + }, + { + "id" : "minecraft:cactus", + "blockRuntimeId" : 6988 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6674 + }, + { + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 7545 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6675 + }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 6290 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6676 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 5974 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 6677 + }, + { + "id" : "minecraft:stripped_jungle_log", + "blockRuntimeId" : 644 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 3832 + }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 5850 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 3833 + }, + { + "id" : "minecraft:stripped_dark_oak_log", + "blockRuntimeId" : 216 + }, + { + "id" : "minecraft:mangrove_log", + "blockRuntimeId" : 350 + }, + { + "id" : "minecraft:stripped_mangrove_log", + "blockRuntimeId" : 8286 + }, + { + "id" : "minecraft:crimson_stem", + "blockRuntimeId" : 5899 + }, + { + "id" : "minecraft:stripped_crimson_stem", + "blockRuntimeId" : 6950 + }, + { + "id" : "minecraft:warped_stem", + "blockRuntimeId" : 6488 + }, + { + "id" : "minecraft:stripped_warped_stem", + "blockRuntimeId" : 7402 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3476 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3482 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3477 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3483 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3478 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3484 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3479 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3485 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3480 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3486 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3481 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3487 + }, + { + "id" : "minecraft:mangrove_wood", + "blockRuntimeId" : 4163 + }, + { + "id" : "minecraft:stripped_mangrove_wood", + "blockRuntimeId" : 4231 + }, + { + "id" : "minecraft:crimson_hyphae", + "blockRuntimeId" : 4296 + }, + { + "id" : "minecraft:stripped_crimson_hyphae", + "blockRuntimeId" : 6501 + }, + { + "id" : "minecraft:warped_hyphae", + "blockRuntimeId" : 5904 + }, + { + "id" : "minecraft:stripped_warped_hyphae", + "blockRuntimeId" : 5581 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6092 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6093 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6094 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 6095 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 4355 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 4356 + }, + { + "id" : "minecraft:mangrove_leaves", + "blockRuntimeId" : 6668 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 7712 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 6341 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 714 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 715 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 716 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 717 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 718 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 719 + }, + { + "id" : "minecraft:mangrove_propagule", + "blockRuntimeId" : 6978 + }, + { + "id" : "minecraft:bee_nest", + "blockRuntimeId" : 5756 + }, + { + "id" : "minecraft:wheat_seeds" + }, + { + "id" : "minecraft:pumpkin_seeds" + }, + { + "id" : "minecraft:melon_seeds" + }, + { + "id" : "minecraft:beetroot_seeds" + }, + { + "id" : "minecraft:wheat" + }, + { + "id" : "minecraft:beetroot" + }, + { + "id" : "minecraft:potato" + }, + { + "id" : "minecraft:poisonous_potato" + }, + { + "id" : "minecraft:carrot" + }, + { + "id" : "minecraft:golden_carrot" + }, + { + "id" : "minecraft:apple" + }, + { + "id" : "minecraft:golden_apple" + }, + { + "id" : "minecraft:enchanted_golden_apple" + }, + { + "id" : "minecraft:melon_block", + "blockRuntimeId" : 394 + }, + { + "id" : "minecraft:melon_slice" + }, + { + "id" : "minecraft:glistering_melon_slice" + }, + { + "id" : "minecraft:sweet_berries" + }, + { + "id" : "minecraft:glow_berries" + }, + { + "id" : "minecraft:pumpkin", + "blockRuntimeId" : 4579 + }, + { + "id" : "minecraft:carved_pumpkin", + "blockRuntimeId" : 7380 + }, + { + "id" : "minecraft:lit_pumpkin", + "blockRuntimeId" : 6687 + }, + { + "id" : "minecraft:honeycomb" + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 931 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5457 + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 930 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5456 + }, + { + "id" : "minecraft:nether_sprouts" + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6494 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6492 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6493 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6491 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6495 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6499 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6497 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6498 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6496 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 6500 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4618 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4616 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4617 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4615 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 4619 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 69 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 67 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 68 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 66 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 70 + }, + { + "id" : "minecraft:kelp" + }, + { + "id" : "minecraft:seagrass", + "blockRuntimeId" : 246 + }, + { + "id" : "minecraft:crimson_roots", + "blockRuntimeId" : 7575 + }, + { + "id" : "minecraft:warped_roots", + "blockRuntimeId" : 4364 + }, + { + "id" : "minecraft:yellow_flower", + "blockRuntimeId" : 302 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3618 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3619 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3620 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3621 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3622 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3623 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3624 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3625 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3626 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3627 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 3628 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5454 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5455 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5458 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 5459 + }, + { + "id" : "minecraft:wither_rose", + "blockRuntimeId" : 6167 + }, + { + "id" : "minecraft:white_dye" + }, + { + "id" : "minecraft:light_gray_dye" + }, + { + "id" : "minecraft:gray_dye" + }, + { + "id" : "minecraft:black_dye" + }, + { + "id" : "minecraft:brown_dye" + }, + { + "id" : "minecraft:red_dye" + }, + { + "id" : "minecraft:orange_dye" + }, + { + "id" : "minecraft:yellow_dye" + }, + { + "id" : "minecraft:lime_dye" + }, + { + "id" : "minecraft:green_dye" + }, + { + "id" : "minecraft:cyan_dye" + }, + { + "id" : "minecraft:light_blue_dye" + }, + { + "id" : "minecraft:blue_dye" + }, + { + "id" : "minecraft:purple_dye" + }, + { + "id" : "minecraft:magenta_dye" + }, + { + "id" : "minecraft:pink_dye" + }, + { + "id" : "minecraft:ink_sac" + }, + { + "id" : "minecraft:glow_ink_sac" + }, + { + "id" : "minecraft:cocoa_beans" + }, + { + "id" : "minecraft:lapis_lazuli" + }, + { + "id" : "minecraft:bone_meal" + }, + { + "id" : "minecraft:vine", + "blockRuntimeId" : 896 + }, + { + "id" : "minecraft:weeping_vines", + "blockRuntimeId" : 5481 + }, + { + "id" : "minecraft:twisting_vines", + "blockRuntimeId" : 5693 + }, + { + "id" : "minecraft:waterlily", + "blockRuntimeId" : 1160 + }, + { + "id" : "minecraft:deadbush", + "blockRuntimeId" : 4679 + }, + { + "id" : "minecraft:bamboo", + "blockRuntimeId" : 3686 + }, + { + "id" : "minecraft:snow", + "blockRuntimeId" : 4196 + }, + { + "id" : "minecraft:ice", + "blockRuntimeId" : 6691 + }, + { + "id" : "minecraft:packed_ice", + "blockRuntimeId" : 282 + }, + { + "id" : "minecraft:blue_ice", + "blockRuntimeId" : 7029 + }, + { + "id" : "minecraft:snow_layer", + "blockRuntimeId" : 155 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 7418 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 895 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 286 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 6540 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 5399 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 205 + }, + { + "id" : "minecraft:mangrove_roots", + "blockRuntimeId" : 6177 + }, + { + "id" : "minecraft:muddy_mangrove_roots", + "blockRuntimeId" : 345 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 5982 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 4322 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 7314 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 6890 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 5479 + }, + { + "id" : "minecraft:glow_lichen", + "blockRuntimeId" : 5686 + }, + { + "id" : "minecraft:amethyst_block", + "blockRuntimeId" : 290 + }, + { + "id" : "minecraft:budding_amethyst", + "blockRuntimeId" : 7004 + }, + { + "id" : "minecraft:amethyst_cluster", + "blockRuntimeId" : 7812 + }, + { + "id" : "minecraft:large_amethyst_bud", + "blockRuntimeId" : 4730 + }, + { + "id" : "minecraft:medium_amethyst_bud", + "blockRuntimeId" : 4378 + }, + { + "id" : "minecraft:small_amethyst_bud", + "blockRuntimeId" : 304 + }, + { + "id" : "minecraft:tuff", + "blockRuntimeId" : 349 + }, + { + "id" : "minecraft:calcite", + "blockRuntimeId" : 215 + }, + { + "id" : "minecraft:chicken" + }, + { + "id" : "minecraft:porkchop" + }, + { + "id" : "minecraft:beef" + }, + { + "id" : "minecraft:mutton" + }, + { + "id" : "minecraft:rabbit" + }, + { + "id" : "minecraft:cod" + }, + { + "id" : "minecraft:salmon" + }, + { + "id" : "minecraft:tropical_fish" + }, + { + "id" : "minecraft:pufferfish" + }, + { + "id" : "minecraft:brown_mushroom", + "blockRuntimeId" : 3548 + }, + { + "id" : "minecraft:red_mushroom", + "blockRuntimeId" : 4587 + }, + { + "id" : "minecraft:crimson_fungus", + "blockRuntimeId" : 7755 + }, + { + "id" : "minecraft:warped_fungus", + "blockRuntimeId" : 287 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7364 + }, + { + "id" : "minecraft:red_mushroom_block", + "blockRuntimeId" : 3613 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7365 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 7350 + }, + { + "id" : "minecraft:egg" + }, + { + "id" : "minecraft:sugar_cane" + }, + { + "id" : "minecraft:sugar" + }, + { + "id" : "minecraft:rotten_flesh" + }, + { + "id" : "minecraft:bone" + }, + { + "id" : "minecraft:web", + "blockRuntimeId" : 6715 + }, + { + "id" : "minecraft:spider_eye" + }, + { + "id" : "minecraft:mob_spawner", + "blockRuntimeId" : 403 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4146 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4147 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4148 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4149 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4150 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 4151 + }, + { + "id" : "minecraft:infested_deepslate", + "blockRuntimeId" : 4643 + }, + { + "id" : "minecraft:dragon_egg", + "blockRuntimeId" : 7273 + }, + { + "id" : "minecraft:turtle_egg", + "blockRuntimeId" : 7999 + }, + { + "id" : "minecraft:frog_spawn", + "blockRuntimeId" : 4401 + }, + { + "id" : "minecraft:pearlescent_froglight", + "blockRuntimeId" : 6437 + }, + { + "id" : "minecraft:verdant_froglight", + "blockRuntimeId" : 6483 + }, + { + "id" : "minecraft:ochre_froglight", + "blockRuntimeId" : 3512 + }, + { + "id" : "minecraft:chicken_spawn_egg" + }, + { + "id" : "minecraft:bee_spawn_egg" + }, + { + "id" : "minecraft:cow_spawn_egg" + }, + { + "id" : "minecraft:pig_spawn_egg" + }, + { + "id" : "minecraft:sheep_spawn_egg" + }, + { + "id" : "minecraft:wolf_spawn_egg" + }, + { + "id" : "minecraft:polar_bear_spawn_egg" + }, + { + "id" : "minecraft:ocelot_spawn_egg" + }, + { + "id" : "minecraft:cat_spawn_egg" + }, + { + "id" : "minecraft:mooshroom_spawn_egg" + }, + { + "id" : "minecraft:bat_spawn_egg" + }, + { + "id" : "minecraft:parrot_spawn_egg" + }, + { + "id" : "minecraft:rabbit_spawn_egg" + }, + { + "id" : "minecraft:llama_spawn_egg" + }, + { + "id" : "minecraft:horse_spawn_egg" + }, + { + "id" : "minecraft:donkey_spawn_egg" + }, + { + "id" : "minecraft:mule_spawn_egg" + }, + { + "id" : "minecraft:skeleton_horse_spawn_egg" + }, + { + "id" : "minecraft:zombie_horse_spawn_egg" + }, + { + "id" : "minecraft:tropical_fish_spawn_egg" + }, + { + "id" : "minecraft:cod_spawn_egg" + }, + { + "id" : "minecraft:pufferfish_spawn_egg" + }, + { + "id" : "minecraft:salmon_spawn_egg" + }, + { + "id" : "minecraft:dolphin_spawn_egg" + }, + { + "id" : "minecraft:turtle_spawn_egg" + }, + { + "id" : "minecraft:panda_spawn_egg" + }, + { + "id" : "minecraft:fox_spawn_egg" + }, + { + "id" : "minecraft:creeper_spawn_egg" + }, + { + "id" : "minecraft:enderman_spawn_egg" + }, + { + "id" : "minecraft:silverfish_spawn_egg" + }, + { + "id" : "minecraft:skeleton_spawn_egg" + }, + { + "id" : "minecraft:wither_skeleton_spawn_egg" + }, + { + "id" : "minecraft:stray_spawn_egg" + }, + { + "id" : "minecraft:slime_spawn_egg" + }, + { + "id" : "minecraft:spider_spawn_egg" + }, + { + "id" : "minecraft:zombie_spawn_egg" + }, + { + "id" : "minecraft:zombie_pigman_spawn_egg" + }, + { + "id" : "minecraft:husk_spawn_egg" + }, + { + "id" : "minecraft:drowned_spawn_egg" + }, + { + "id" : "minecraft:squid_spawn_egg" + }, + { + "id" : "minecraft:glow_squid_spawn_egg" + }, + { + "id" : "minecraft:cave_spider_spawn_egg" + }, + { + "id" : "minecraft:witch_spawn_egg" + }, + { + "id" : "minecraft:guardian_spawn_egg" + }, + { + "id" : "minecraft:elder_guardian_spawn_egg" + }, + { + "id" : "minecraft:endermite_spawn_egg" + }, + { + "id" : "minecraft:magma_cube_spawn_egg" + }, + { + "id" : "minecraft:strider_spawn_egg" + }, + { + "id" : "minecraft:hoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_spawn_egg" + }, + { + "id" : "minecraft:zoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_brute_spawn_egg" + }, + { + "id" : "minecraft:goat_spawn_egg" + }, + { + "id" : "minecraft:axolotl_spawn_egg" + }, + { + "id" : "minecraft:warden_spawn_egg" + }, + { + "id" : "minecraft:allay_spawn_egg" + }, + { + "id" : "minecraft:frog_spawn_egg" + }, + { + "id" : "minecraft:tadpole_spawn_egg" + }, + { + "id" : "minecraft:trader_llama_spawn_egg" + }, + { + "id" : "minecraft:ghast_spawn_egg" + }, + { + "id" : "minecraft:blaze_spawn_egg" + }, + { + "id" : "minecraft:shulker_spawn_egg" + }, + { + "id" : "minecraft:vindicator_spawn_egg" + }, + { + "id" : "minecraft:evoker_spawn_egg" + }, + { + "id" : "minecraft:vex_spawn_egg" + }, + { + "id" : "minecraft:villager_spawn_egg" + }, + { + "id" : "minecraft:wandering_trader_spawn_egg" + }, + { + "id" : "minecraft:zombie_villager_spawn_egg" + }, + { + "id" : "minecraft:phantom_spawn_egg" + }, + { + "id" : "minecraft:pillager_spawn_egg" + }, + { + "id" : "minecraft:ravager_spawn_egg" + }, + { + "id" : "minecraft:obsidian", + "blockRuntimeId" : 430 + }, + { + "id" : "minecraft:crying_obsidian", + "blockRuntimeId" : 6724 + }, + { + "id" : "minecraft:bedrock", + "blockRuntimeId" : 7019 + }, + { + "id" : "minecraft:soul_sand", + "blockRuntimeId" : 5833 + }, + { + "id" : "minecraft:netherrack", + "blockRuntimeId" : 7039 + }, + { + "id" : "minecraft:magma", + "blockRuntimeId" : 8011 + }, + { + "id" : "minecraft:nether_wart" + }, + { + "id" : "minecraft:end_stone", + "blockRuntimeId" : 3838 + }, + { + "id" : "minecraft:chorus_flower", + "blockRuntimeId" : 4532 + }, + { + "id" : "minecraft:chorus_plant", + "blockRuntimeId" : 5507 + }, + { + "id" : "minecraft:chorus_fruit" + }, + { + "id" : "minecraft:popped_chorus_fruit" + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 631 + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 632 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5239 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5240 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5241 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5242 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5243 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5244 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5245 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5246 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5247 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 5248 + }, + { + "id" : "minecraft:sculk", + "blockRuntimeId" : 7038 + }, + { + "id" : "minecraft:sculk_vein", + "blockRuntimeId" : 7134 + }, + { + "id" : "minecraft:sculk_catalyst", + "blockRuntimeId" : 3615 + }, + { + "id" : "minecraft:sculk_shrieker", + "blockRuntimeId" : 219 + }, + { + "id" : "minecraft:sculk_sensor", + "blockRuntimeId" : 4391 + }, + { + "id" : "minecraft:reinforced_deepslate", + "blockRuntimeId" : 5834 + }, + { + "id" : "minecraft:leather_helmet" + }, + { + "id" : "minecraft:chainmail_helmet" + }, + { + "id" : "minecraft:iron_helmet" + }, + { + "id" : "minecraft:golden_helmet" + }, + { + "id" : "minecraft:diamond_helmet" + }, + { + "id" : "minecraft:netherite_helmet" + }, + { + "id" : "minecraft:leather_chestplate" + }, + { + "id" : "minecraft:chainmail_chestplate" + }, + { + "id" : "minecraft:iron_chestplate" + }, + { + "id" : "minecraft:golden_chestplate" + }, + { + "id" : "minecraft:diamond_chestplate" + }, + { + "id" : "minecraft:netherite_chestplate" + }, + { + "id" : "minecraft:leather_leggings" + }, + { + "id" : "minecraft:chainmail_leggings" + }, + { + "id" : "minecraft:iron_leggings" + }, + { + "id" : "minecraft:golden_leggings" + }, + { + "id" : "minecraft:diamond_leggings" + }, + { + "id" : "minecraft:netherite_leggings" + }, + { + "id" : "minecraft:leather_boots" + }, + { + "id" : "minecraft:chainmail_boots" + }, + { + "id" : "minecraft:iron_boots" + }, + { + "id" : "minecraft:golden_boots" + }, + { + "id" : "minecraft:diamond_boots" + }, + { + "id" : "minecraft:netherite_boots" + }, + { + "id" : "minecraft:wooden_sword" + }, + { + "id" : "minecraft:stone_sword" + }, + { + "id" : "minecraft:iron_sword" + }, + { + "id" : "minecraft:golden_sword" + }, + { + "id" : "minecraft:diamond_sword" + }, + { + "id" : "minecraft:netherite_sword" + }, + { + "id" : "minecraft:wooden_axe" + }, + { + "id" : "minecraft:stone_axe" + }, + { + "id" : "minecraft:iron_axe" + }, + { + "id" : "minecraft:golden_axe" + }, + { + "id" : "minecraft:diamond_axe" + }, + { + "id" : "minecraft:netherite_axe" + }, + { + "id" : "minecraft:wooden_pickaxe" + }, + { + "id" : "minecraft:stone_pickaxe" + }, + { + "id" : "minecraft:iron_pickaxe" + }, + { + "id" : "minecraft:golden_pickaxe" + }, + { + "id" : "minecraft:diamond_pickaxe" + }, + { + "id" : "minecraft:netherite_pickaxe" + }, + { + "id" : "minecraft:wooden_shovel" + }, + { + "id" : "minecraft:stone_shovel" + }, + { + "id" : "minecraft:iron_shovel" + }, + { + "id" : "minecraft:golden_shovel" + }, + { + "id" : "minecraft:diamond_shovel" + }, + { + "id" : "minecraft:netherite_shovel" + }, + { + "id" : "minecraft:wooden_hoe" + }, + { + "id" : "minecraft:stone_hoe" + }, + { + "id" : "minecraft:iron_hoe" + }, + { + "id" : "minecraft:golden_hoe" + }, + { + "id" : "minecraft:diamond_hoe" + }, + { + "id" : "minecraft:netherite_hoe" + }, + { + "id" : "minecraft:bow" + }, + { + "id" : "minecraft:crossbow" + }, + { + "id" : "minecraft:arrow" + }, + { + "id" : "minecraft:arrow", + "damage" : 6 + }, + { + "id" : "minecraft:arrow", + "damage" : 7 + }, + { + "id" : "minecraft:arrow", + "damage" : 8 + }, + { + "id" : "minecraft:arrow", + "damage" : 9 + }, + { + "id" : "minecraft:arrow", + "damage" : 10 + }, + { + "id" : "minecraft:arrow", + "damage" : 11 + }, + { + "id" : "minecraft:arrow", + "damage" : 12 + }, + { + "id" : "minecraft:arrow", + "damage" : 13 + }, + { + "id" : "minecraft:arrow", + "damage" : 14 + }, + { + "id" : "minecraft:arrow", + "damage" : 15 + }, + { + "id" : "minecraft:arrow", + "damage" : 16 + }, + { + "id" : "minecraft:arrow", + "damage" : 17 + }, + { + "id" : "minecraft:arrow", + "damage" : 18 + }, + { + "id" : "minecraft:arrow", + "damage" : 19 + }, + { + "id" : "minecraft:arrow", + "damage" : 20 + }, + { + "id" : "minecraft:arrow", + "damage" : 21 + }, + { + "id" : "minecraft:arrow", + "damage" : 22 + }, + { + "id" : "minecraft:arrow", + "damage" : 23 + }, + { + "id" : "minecraft:arrow", + "damage" : 24 + }, + { + "id" : "minecraft:arrow", + "damage" : 25 + }, + { + "id" : "minecraft:arrow", + "damage" : 26 + }, + { + "id" : "minecraft:arrow", + "damage" : 27 + }, + { + "id" : "minecraft:arrow", + "damage" : 28 + }, + { + "id" : "minecraft:arrow", + "damage" : 29 + }, + { + "id" : "minecraft:arrow", + "damage" : 30 + }, + { + "id" : "minecraft:arrow", + "damage" : 31 + }, + { + "id" : "minecraft:arrow", + "damage" : 32 + }, + { + "id" : "minecraft:arrow", + "damage" : 33 + }, + { + "id" : "minecraft:arrow", + "damage" : 34 + }, + { + "id" : "minecraft:arrow", + "damage" : 35 + }, + { + "id" : "minecraft:arrow", + "damage" : 36 + }, + { + "id" : "minecraft:arrow", + "damage" : 37 + }, + { + "id" : "minecraft:arrow", + "damage" : 38 + }, + { + "id" : "minecraft:arrow", + "damage" : 39 + }, + { + "id" : "minecraft:arrow", + "damage" : 40 + }, + { + "id" : "minecraft:arrow", + "damage" : 41 + }, + { + "id" : "minecraft:arrow", + "damage" : 42 + }, + { + "id" : "minecraft:arrow", + "damage" : 43 + }, + { + "id" : "minecraft:shield" + }, + { + "id" : "minecraft:cooked_chicken" + }, + { + "id" : "minecraft:cooked_porkchop" + }, + { + "id" : "minecraft:cooked_beef" + }, + { + "id" : "minecraft:cooked_mutton" + }, + { + "id" : "minecraft:cooked_rabbit" + }, + { + "id" : "minecraft:cooked_cod" + }, + { + "id" : "minecraft:cooked_salmon" + }, + { + "id" : "minecraft:bread" + }, + { + "id" : "minecraft:mushroom_stew" + }, + { + "id" : "minecraft:beetroot_soup" + }, + { + "id" : "minecraft:rabbit_stew" + }, + { + "id" : "minecraft:baked_potato" + }, + { + "id" : "minecraft:cookie" + }, + { + "id" : "minecraft:pumpkin_pie" + }, + { + "id" : "minecraft:cake" + }, + { + "id" : "minecraft:dried_kelp" + }, + { + "id" : "minecraft:fishing_rod" + }, + { + "id" : "minecraft:carrot_on_a_stick" + }, + { + "id" : "minecraft:warped_fungus_on_a_stick" + }, + { + "id" : "minecraft:snowball" + }, + { + "id" : "minecraft:shears" + }, + { + "id" : "minecraft:flint_and_steel" + }, + { + "id" : "minecraft:lead" + }, + { + "id" : "minecraft:clock" + }, + { + "id" : "minecraft:compass" + }, + { + "id" : "minecraft:recovery_compass" + }, + { + "id" : "minecraft:goat_horn" + }, + { + "id" : "minecraft:goat_horn", + "damage" : 1 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 2 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 3 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 4 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 5 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 6 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 7 + }, + { + "id" : "minecraft:empty_map" + }, + { + "id" : "minecraft:empty_map", + "damage" : 2 + }, + { + "id" : "minecraft:saddle" + }, + { + "id" : "minecraft:leather_horse_armor" + }, + { + "id" : "minecraft:iron_horse_armor" + }, + { + "id" : "minecraft:golden_horse_armor" + }, + { + "id" : "minecraft:diamond_horse_armor" + }, + { + "id" : "minecraft:trident" + }, + { + "id" : "minecraft:turtle_helmet" + }, + { + "id" : "minecraft:elytra" + }, + { + "id" : "minecraft:totem_of_undying" + }, + { + "id" : "minecraft:glass_bottle" + }, + { + "id" : "minecraft:experience_bottle" + }, + { + "id" : "minecraft:potion" + }, + { + "id" : "minecraft:potion", + "damage" : 1 + }, + { + "id" : "minecraft:potion", + "damage" : 2 + }, + { + "id" : "minecraft:potion", + "damage" : 3 + }, + { + "id" : "minecraft:potion", + "damage" : 4 + }, + { + "id" : "minecraft:potion", + "damage" : 5 + }, + { + "id" : "minecraft:potion", + "damage" : 6 + }, + { + "id" : "minecraft:potion", + "damage" : 7 + }, + { + "id" : "minecraft:potion", + "damage" : 8 + }, + { + "id" : "minecraft:potion", + "damage" : 9 + }, + { + "id" : "minecraft:potion", + "damage" : 10 + }, + { + "id" : "minecraft:potion", + "damage" : 11 + }, + { + "id" : "minecraft:potion", + "damage" : 12 + }, + { + "id" : "minecraft:potion", + "damage" : 13 + }, + { + "id" : "minecraft:potion", + "damage" : 14 + }, + { + "id" : "minecraft:potion", + "damage" : 15 + }, + { + "id" : "minecraft:potion", + "damage" : 16 + }, + { + "id" : "minecraft:potion", + "damage" : 17 + }, + { + "id" : "minecraft:potion", + "damage" : 18 + }, + { + "id" : "minecraft:potion", + "damage" : 19 + }, + { + "id" : "minecraft:potion", + "damage" : 20 + }, + { + "id" : "minecraft:potion", + "damage" : 21 + }, + { + "id" : "minecraft:potion", + "damage" : 22 + }, + { + "id" : "minecraft:potion", + "damage" : 23 + }, + { + "id" : "minecraft:potion", + "damage" : 24 + }, + { + "id" : "minecraft:potion", + "damage" : 25 + }, + { + "id" : "minecraft:potion", + "damage" : 26 + }, + { + "id" : "minecraft:potion", + "damage" : 27 + }, + { + "id" : "minecraft:potion", + "damage" : 28 + }, + { + "id" : "minecraft:potion", + "damage" : 29 + }, + { + "id" : "minecraft:potion", + "damage" : 30 + }, + { + "id" : "minecraft:potion", + "damage" : 31 + }, + { + "id" : "minecraft:potion", + "damage" : 32 + }, + { + "id" : "minecraft:potion", + "damage" : 33 + }, + { + "id" : "minecraft:potion", + "damage" : 34 + }, + { + "id" : "minecraft:potion", + "damage" : 35 + }, + { + "id" : "minecraft:potion", + "damage" : 36 + }, + { + "id" : "minecraft:potion", + "damage" : 37 + }, + { + "id" : "minecraft:potion", + "damage" : 38 + }, + { + "id" : "minecraft:potion", + "damage" : 39 + }, + { + "id" : "minecraft:potion", + "damage" : 40 + }, + { + "id" : "minecraft:potion", + "damage" : 41 + }, + { + "id" : "minecraft:potion", + "damage" : 42 + }, + { + "id" : "minecraft:splash_potion" + }, + { + "id" : "minecraft:splash_potion", + "damage" : 1 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 2 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 3 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 4 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 5 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 6 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 7 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 8 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 9 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 10 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 11 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 12 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 13 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 14 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 15 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 16 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 17 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 18 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 19 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 20 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 21 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 22 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 23 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 24 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 25 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 26 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 27 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 28 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 29 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 30 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 31 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 32 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 33 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 34 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 35 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 36 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 37 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 38 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 39 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 40 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 41 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 42 + }, + { + "id" : "minecraft:lingering_potion" + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 1 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 2 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 3 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 4 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 5 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 6 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 7 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 8 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 9 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 10 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 11 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 12 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 13 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 14 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 15 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 16 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 17 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 18 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 19 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 20 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 21 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 22 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 23 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 24 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 25 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 26 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 27 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 28 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 29 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 30 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 31 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 32 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 33 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 34 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 35 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 36 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 37 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 38 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 39 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 40 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 41 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 42 + }, + { + "id" : "minecraft:spyglass" + }, + { + "id" : "minecraft:stick" + }, + { + "id" : "minecraft:bed" + }, + { + "id" : "minecraft:bed", + "damage" : 8 + }, + { + "id" : "minecraft:bed", + "damage" : 7 + }, + { + "id" : "minecraft:bed", + "damage" : 15 + }, + { + "id" : "minecraft:bed", + "damage" : 12 + }, + { + "id" : "minecraft:bed", + "damage" : 14 + }, + { + "id" : "minecraft:bed", + "damage" : 1 + }, + { + "id" : "minecraft:bed", + "damage" : 4 + }, + { + "id" : "minecraft:bed", + "damage" : 5 + }, + { + "id" : "minecraft:bed", + "damage" : 13 + }, + { + "id" : "minecraft:bed", + "damage" : 9 + }, + { + "id" : "minecraft:bed", + "damage" : 3 + }, + { + "id" : "minecraft:bed", + "damage" : 11 + }, + { + "id" : "minecraft:bed", + "damage" : 10 + }, + { + "id" : "minecraft:bed", + "damage" : 2 + }, + { + "id" : "minecraft:bed", + "damage" : 6 + }, + { + "id" : "minecraft:torch", + "blockRuntimeId" : 726 + }, + { + "id" : "minecraft:soul_torch", + "blockRuntimeId" : 4646 + }, + { + "id" : "minecraft:sea_pickle", + "blockRuntimeId" : 5857 + }, + { + "id" : "minecraft:lantern", + "blockRuntimeId" : 7076 + }, + { + "id" : "minecraft:soul_lantern", + "blockRuntimeId" : 5751 + }, + { + "id" : "minecraft:candle", + "blockRuntimeId" : 7405 + }, + { + "id" : "minecraft:white_candle", + "blockRuntimeId" : 5302 + }, + { + "id" : "minecraft:orange_candle", + "blockRuntimeId" : 364 + }, + { + "id" : "minecraft:magenta_candle", + "blockRuntimeId" : 420 + }, + { + "id" : "minecraft:light_blue_candle", + "blockRuntimeId" : 4571 + }, + { + "id" : "minecraft:yellow_candle", + "blockRuntimeId" : 6194 + }, + { + "id" : "minecraft:lime_candle", + "blockRuntimeId" : 6370 + }, + { + "id" : "minecraft:pink_candle", + "blockRuntimeId" : 7372 + }, + { + "id" : "minecraft:gray_candle", + "blockRuntimeId" : 941 + }, + { + "id" : "minecraft:light_gray_candle", + "blockRuntimeId" : 6226 + }, + { + "id" : "minecraft:cyan_candle", + "blockRuntimeId" : 7728 + }, + { + "id" : "minecraft:purple_candle", + "blockRuntimeId" : 7040 + }, + { + "id" : "minecraft:blue_candle" + }, + { + "id" : "minecraft:brown_candle", + "blockRuntimeId" : 5877 + }, + { + "id" : "minecraft:green_candle", + "blockRuntimeId" : 688 + }, + { + "id" : "minecraft:red_candle", + "blockRuntimeId" : 4683 + }, + { + "id" : "minecraft:black_candle", + "blockRuntimeId" : 171 + }, + { + "id" : "minecraft:crafting_table", + "blockRuntimeId" : 5856 + }, + { + "id" : "minecraft:cartography_table", + "blockRuntimeId" : 8290 + }, + { + "id" : "minecraft:fletching_table", + "blockRuntimeId" : 5835 + }, + { + "id" : "minecraft:smithing_table", + "blockRuntimeId" : 3728 + }, + { + "id" : "minecraft:beehive", + "blockRuntimeId" : 6110 + }, + { + "id" : "minecraft:campfire" + }, + { + "id" : "minecraft:soul_campfire" + }, + { + "id" : "minecraft:furnace", + "blockRuntimeId" : 7804 + }, + { + "id" : "minecraft:blast_furnace", + "blockRuntimeId" : 7569 + }, + { + "id" : "minecraft:smoker", + "blockRuntimeId" : 649 + }, + { + "id" : "minecraft:respawn_anchor", + "blockRuntimeId" : 683 + }, + { + "id" : "minecraft:brewing_stand" + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6636 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6640 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 6644 + }, + { + "id" : "minecraft:grindstone", + "blockRuntimeId" : 8041 + }, + { + "id" : "minecraft:enchanting_table", + "blockRuntimeId" : 6725 + }, + { + "id" : "minecraft:bookshelf", + "blockRuntimeId" : 6673 + }, + { + "id" : "minecraft:lectern", + "blockRuntimeId" : 6942 + }, + { + "id" : "minecraft:cauldron" + }, + { + "id" : "minecraft:composter", + "blockRuntimeId" : 5417 + }, + { + "id" : "minecraft:chest", + "blockRuntimeId" : 7117 + }, + { + "id" : "minecraft:trapped_chest", + "blockRuntimeId" : 5585 + }, + { + "id" : "minecraft:ender_chest", + "blockRuntimeId" : 4371 + }, + { + "id" : "minecraft:barrel", + "blockRuntimeId" : 4520 + }, + { + "id" : "minecraft:undyed_shulker_box", + "blockRuntimeId" : 3683 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5318 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5326 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5325 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5333 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5330 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5332 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5319 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5322 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5323 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5331 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5327 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5321 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5329 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5328 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5320 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 5324 + }, + { + "id" : "minecraft:armor_stand" + }, + { + "id" : "minecraft:noteblock", + "blockRuntimeId" : 348 + }, + { + "id" : "minecraft:jukebox", + "blockRuntimeId" : 4876 + }, + { + "id" : "minecraft:music_disc_13" + }, + { + "id" : "minecraft:music_disc_cat" + }, + { + "id" : "minecraft:music_disc_blocks" + }, + { + "id" : "minecraft:music_disc_chirp" + }, + { + "id" : "minecraft:music_disc_far" + }, + { + "id" : "minecraft:music_disc_mall" + }, + { + "id" : "minecraft:music_disc_mellohi" + }, + { + "id" : "minecraft:music_disc_stal" + }, + { + "id" : "minecraft:music_disc_strad" + }, + { + "id" : "minecraft:music_disc_ward" + }, + { + "id" : "minecraft:music_disc_11" + }, + { + "id" : "minecraft:music_disc_wait" + }, + { + "id" : "minecraft:music_disc_otherside" + }, + { + "id" : "minecraft:music_disc_5" + }, + { + "id" : "minecraft:music_disc_pigstep" + }, + { + "id" : "minecraft:disc_fragment_5" + }, + { + "id" : "minecraft:glowstone_dust" + }, + { + "id" : "minecraft:glowstone", + "blockRuntimeId" : 3887 + }, + { + "id" : "minecraft:redstone_lamp", + "blockRuntimeId" : 251 + }, + { + "id" : "minecraft:sea_lantern", + "blockRuntimeId" : 7548 + }, + { + "id" : "minecraft:oak_sign" + }, + { + "id" : "minecraft:spruce_sign" + }, + { + "id" : "minecraft:birch_sign" + }, + { + "id" : "minecraft:jungle_sign" + }, + { + "id" : "minecraft:acacia_sign" + }, + { + "id" : "minecraft:dark_oak_sign" + }, + { + "id" : "minecraft:mangrove_sign" + }, + { + "id" : "minecraft:crimson_sign" + }, + { + "id" : "minecraft:warped_sign" + }, + { + "id" : "minecraft:painting" + }, + { + "id" : "minecraft:frame" + }, + { + "id" : "minecraft:glow_frame" + }, + { + "id" : "minecraft:honey_bottle" + }, + { + "id" : "minecraft:flower_pot" + }, + { + "id" : "minecraft:bowl" + }, + { + "id" : "minecraft:bucket" + }, + { + "id" : "minecraft:milk_bucket" + }, + { + "id" : "minecraft:water_bucket" + }, + { + "id" : "minecraft:lava_bucket" + }, + { + "id" : "minecraft:cod_bucket" + }, + { + "id" : "minecraft:salmon_bucket" + }, + { + "id" : "minecraft:tropical_fish_bucket" + }, + { + "id" : "minecraft:pufferfish_bucket" + }, + { + "id" : "minecraft:powder_snow_bucket" + }, + { + "id" : "minecraft:axolotl_bucket" + }, + { + "id" : "minecraft:tadpole_bucket" + }, + { + "id" : "minecraft:skull", + "damage" : 3 + }, + { + "id" : "minecraft:skull", + "damage" : 2 + }, + { + "id" : "minecraft:skull", + "damage" : 4 + }, + { + "id" : "minecraft:skull", + "damage" : 5 + }, + { + "id" : "minecraft:skull" + }, + { + "id" : "minecraft:skull", + "damage" : 1 + }, + { + "id" : "minecraft:beacon", + "blockRuntimeId" : 145 + }, + { + "id" : "minecraft:bell", + "blockRuntimeId" : 6910 + }, + { + "id" : "minecraft:conduit", + "blockRuntimeId" : 4234 + }, + { + "id" : "minecraft:stonecutter_block", + "blockRuntimeId" : 7576 + }, + { + "id" : "minecraft:end_portal_frame", + "blockRuntimeId" : 6079 + }, + { + "id" : "minecraft:coal" + }, + { + "id" : "minecraft:charcoal" + }, + { + "id" : "minecraft:diamond" + }, + { + "id" : "minecraft:iron_nugget" + }, + { + "id" : "minecraft:raw_iron" + }, + { + "id" : "minecraft:raw_gold" + }, + { + "id" : "minecraft:raw_copper" + }, + { + "id" : "minecraft:copper_ingot" + }, + { + "id" : "minecraft:iron_ingot" + }, + { + "id" : "minecraft:netherite_scrap" + }, + { + "id" : "minecraft:netherite_ingot" + }, + { + "id" : "minecraft:gold_nugget" + }, + { + "id" : "minecraft:gold_ingot" + }, + { + "id" : "minecraft:emerald" + }, + { + "id" : "minecraft:quartz" + }, + { + "id" : "minecraft:clay_ball" + }, + { + "id" : "minecraft:brick" + }, + { + "id" : "minecraft:netherbrick" + }, + { + "id" : "minecraft:prismarine_shard" + }, + { + "id" : "minecraft:amethyst_shard" + }, + { + "id" : "minecraft:prismarine_crystals" + }, + { + "id" : "minecraft:nautilus_shell" + }, + { + "id" : "minecraft:heart_of_the_sea" + }, + { + "id" : "minecraft:scute" + }, + { + "id" : "minecraft:phantom_membrane" + }, + { + "id" : "minecraft:string" + }, + { + "id" : "minecraft:feather" + }, + { + "id" : "minecraft:flint" + }, + { + "id" : "minecraft:gunpowder" + }, + { + "id" : "minecraft:leather" + }, + { + "id" : "minecraft:rabbit_hide" + }, + { + "id" : "minecraft:rabbit_foot" + }, + { + "id" : "minecraft:fire_charge" + }, + { + "id" : "minecraft:blaze_rod" + }, + { + "id" : "minecraft:blaze_powder" + }, + { + "id" : "minecraft:magma_cream" + }, + { + "id" : "minecraft:fermented_spider_eye" + }, + { + "id" : "minecraft:echo_shard" + }, + { + "id" : "minecraft:dragon_breath" + }, + { + "id" : "minecraft:shulker_shell" + }, + { + "id" : "minecraft:ghast_tear" + }, + { + "id" : "minecraft:slime_ball" + }, + { + "id" : "minecraft:ender_pearl" + }, + { + "id" : "minecraft:ender_eye" + }, + { + "id" : "minecraft:nether_star" + }, + { + "id" : "minecraft:end_rod", + "blockRuntimeId" : 5893 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 1178 + }, + { + "id" : "minecraft:end_crystal" + }, + { + "id" : "minecraft:paper" + }, + { + "id" : "minecraft:book" + }, + { + "id" : "minecraft:writable_book" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:oak_boat" + }, + { + "id" : "minecraft:spruce_boat" + }, + { + "id" : "minecraft:birch_boat" + }, + { + "id" : "minecraft:jungle_boat" + }, + { + "id" : "minecraft:acacia_boat" + }, + { + "id" : "minecraft:dark_oak_boat" + }, + { + "id" : "minecraft:mangrove_boat" + }, + { + "id" : "minecraft:oak_chest_boat" + }, + { + "id" : "minecraft:spruce_chest_boat" + }, + { + "id" : "minecraft:birch_chest_boat" + }, + { + "id" : "minecraft:jungle_chest_boat" + }, + { + "id" : "minecraft:acacia_chest_boat" + }, + { + "id" : "minecraft:dark_oak_chest_boat" + }, + { + "id" : "minecraft:mangrove_chest_boat" + }, + { + "id" : "minecraft:rail", + "blockRuntimeId" : 3922 + }, + { + "id" : "minecraft:golden_rail", + "blockRuntimeId" : 5334 + }, + { + "id" : "minecraft:detector_rail", + "blockRuntimeId" : 4134 + }, + { + "id" : "minecraft:activator_rail", + "blockRuntimeId" : 309 + }, + { + "id" : "minecraft:minecart" + }, + { + "id" : "minecraft:chest_minecart" + }, + { + "id" : "minecraft:hopper_minecart" + }, + { + "id" : "minecraft:tnt_minecart" + }, + { + "id" : "minecraft:redstone" + }, + { + "id" : "minecraft:redstone_block", + "blockRuntimeId" : 3778 + }, + { + "id" : "minecraft:redstone_torch", + "blockRuntimeId" : 3527 + }, + { + "id" : "minecraft:lever", + "blockRuntimeId" : 6516 + }, + { + "id" : "minecraft:wooden_button", + "blockRuntimeId" : 6393 + }, + { + "id" : "minecraft:spruce_button", + "blockRuntimeId" : 4323 + }, + { + "id" : "minecraft:birch_button", + "blockRuntimeId" : 7768 + }, + { + "id" : "minecraft:jungle_button", + "blockRuntimeId" : 116 + }, + { + "id" : "minecraft:acacia_button", + "blockRuntimeId" : 7233 + }, + { + "id" : "minecraft:dark_oak_button", + "blockRuntimeId" : 93 + }, + { + "id" : "minecraft:mangrove_button", + "blockRuntimeId" : 7064 + }, + { + "id" : "minecraft:stone_button", + "blockRuntimeId" : 598 + }, + { + "id" : "minecraft:crimson_button", + "blockRuntimeId" : 4434 + }, + { + "id" : "minecraft:warped_button", + "blockRuntimeId" : 7252 + }, + { + "id" : "minecraft:polished_blackstone_button", + "blockRuntimeId" : 7792 + }, + { + "id" : "minecraft:tripwire_hook", + "blockRuntimeId" : 5916 + }, + { + "id" : "minecraft:wooden_pressure_plate", + "blockRuntimeId" : 8065 + }, + { + "id" : "minecraft:spruce_pressure_plate", + "blockRuntimeId" : 3761 + }, + { + "id" : "minecraft:birch_pressure_plate", + "blockRuntimeId" : 3557 + }, + { + "id" : "minecraft:jungle_pressure_plate", + "blockRuntimeId" : 3637 + }, + { + "id" : "minecraft:acacia_pressure_plate", + "blockRuntimeId" : 5249 + }, + { + "id" : "minecraft:dark_oak_pressure_plate", + "blockRuntimeId" : 5958 + }, + { + "id" : "minecraft:mangrove_pressure_plate", + "blockRuntimeId" : 3871 + }, + { + "id" : "minecraft:crimson_pressure_plate", + "blockRuntimeId" : 8270 + }, + { + "id" : "minecraft:warped_pressure_plate", + "blockRuntimeId" : 256 + }, + { + "id" : "minecraft:stone_pressure_plate", + "blockRuntimeId" : 3888 + }, + { + "id" : "minecraft:light_weighted_pressure_plate", + "blockRuntimeId" : 3667 + }, + { + "id" : "minecraft:heavy_weighted_pressure_plate", + "blockRuntimeId" : 1162 + }, + { + "id" : "minecraft:polished_blackstone_pressure_plate", + "blockRuntimeId" : 6234 + }, + { + "id" : "minecraft:observer", + "blockRuntimeId" : 3515 + }, + { + "id" : "minecraft:daylight_detector", + "blockRuntimeId" : 4199 + }, + { + "id" : "minecraft:repeater" + }, + { + "id" : "minecraft:comparator" + }, + { + "id" : "minecraft:hopper" + }, + { + "id" : "minecraft:dropper", + "blockRuntimeId" : 7387 + }, + { + "id" : "minecraft:dispenser", + "blockRuntimeId" : 8015 + }, + { + "id" : "minecraft:piston", + "blockRuntimeId" : 924 + }, + { + "id" : "minecraft:sticky_piston", + "blockRuntimeId" : 4366 + }, + { + "id" : "minecraft:tnt", + "blockRuntimeId" : 6709 + }, + { + "id" : "minecraft:name_tag" + }, + { + "id" : "minecraft:loom", + "blockRuntimeId" : 3828 + }, + { + "id" : "minecraft:banner" + }, + { + "id" : "minecraft:banner", + "damage" : 8 + }, + { + "id" : "minecraft:banner", + "damage" : 7 + }, + { + "id" : "minecraft:banner", + "damage" : 15 + }, + { + "id" : "minecraft:banner", + "damage" : 12 + }, + { + "id" : "minecraft:banner", + "damage" : 14 + }, + { + "id" : "minecraft:banner", + "damage" : 1 + }, + { + "id" : "minecraft:banner", + "damage" : 4 + }, + { + "id" : "minecraft:banner", + "damage" : 5 + }, + { + "id" : "minecraft:banner", + "damage" : 13 + }, + { + "id" : "minecraft:banner", + "damage" : 9 + }, + { + "id" : "minecraft:banner", + "damage" : 3 + }, + { + "id" : "minecraft:banner", + "damage" : 11 + }, + { + "id" : "minecraft:banner", + "damage" : 10 + }, + { + "id" : "minecraft:banner", + "damage" : 2 + }, + { + "id" : "minecraft:banner", + "damage" : 6 + }, + { + "id" : "minecraft:banner", + "damage" : 15, + "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" + }, + { + "id" : "minecraft:creeper_banner_pattern" + }, + { + "id" : "minecraft:skull_banner_pattern" + }, + { + "id" : "minecraft:flower_banner_pattern" + }, + { + "id" : "minecraft:mojang_banner_pattern" + }, + { + "id" : "minecraft:field_masoned_banner_pattern" + }, + { + "id" : "minecraft:bordure_indented_banner_pattern" + }, + { + "id" : "minecraft:piglin_banner_pattern" + }, + { + "id" : "minecraft:globe_banner_pattern" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_star", + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 8, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 7, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 15, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 12, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 14, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 1, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 4, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 5, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 13, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 9, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 3, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 11, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 10, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 2, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 6, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : "minecraft:chain" + }, + { + "id" : "minecraft:target", + "blockRuntimeId" : 6392 + }, + { + "id" : "minecraft:lodestone_compass" + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json new file mode 100644 index 00000000000..00be1af06ec --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_19_20.json @@ -0,0 +1,4530 @@ +[ + { + "name" : "minecraft:acacia_boat", + "id" : 379 + }, + { + "name" : "minecraft:acacia_button", + "id" : -140 + }, + { + "name" : "minecraft:acacia_chest_boat", + "id" : 642 + }, + { + "name" : "minecraft:acacia_door", + "id" : 556 + }, + { + "name" : "minecraft:acacia_fence_gate", + "id" : 187 + }, + { + "name" : "minecraft:acacia_pressure_plate", + "id" : -150 + }, + { + "name" : "minecraft:acacia_sign", + "id" : 579 + }, + { + "name" : "minecraft:acacia_stairs", + "id" : 163 + }, + { + "name" : "minecraft:acacia_standing_sign", + "id" : -190 + }, + { + "name" : "minecraft:acacia_trapdoor", + "id" : -145 + }, + { + "name" : "minecraft:acacia_wall_sign", + "id" : -191 + }, + { + "name" : "minecraft:activator_rail", + "id" : 126 + }, + { + "name" : "minecraft:agent_spawn_egg", + "id" : 487 + }, + { + "name" : "minecraft:air", + "id" : -158 + }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 631 + }, + { + "name" : "minecraft:allow", + "id" : 210 + }, + { + "name" : "minecraft:amethyst_block", + "id" : -327 + }, + { + "name" : "minecraft:amethyst_cluster", + "id" : -329 + }, + { + "name" : "minecraft:amethyst_shard", + "id" : 624 + }, + { + "name" : "minecraft:ancient_debris", + "id" : -271 + }, + { + "name" : "minecraft:andesite_stairs", + "id" : -171 + }, + { + "name" : "minecraft:anvil", + "id" : 145 + }, + { + "name" : "minecraft:apple", + "id" : 257 + }, + { + "name" : "minecraft:armor_stand", + "id" : 552 + }, + { + "name" : "minecraft:arrow", + "id" : 301 + }, + { + "name" : "minecraft:axolotl_bucket", + "id" : 369 + }, + { + "name" : "minecraft:axolotl_spawn_egg", + "id" : 500 + }, + { + "name" : "minecraft:azalea", + "id" : -337 + }, + { + "name" : "minecraft:azalea_leaves", + "id" : -324 + }, + { + "name" : "minecraft:azalea_leaves_flowered", + "id" : -325 + }, + { + "name" : "minecraft:baked_potato", + "id" : 281 + }, + { + "name" : "minecraft:balloon", + "id" : 598 + }, + { + "name" : "minecraft:bamboo", + "id" : -163 + }, + { + "name" : "minecraft:bamboo_sapling", + "id" : -164 + }, + { + "name" : "minecraft:banner", + "id" : 567 + }, + { + "name" : "minecraft:banner_pattern", + "id" : 651 + }, + { + "name" : "minecraft:barrel", + "id" : -203 + }, + { + "name" : "minecraft:barrier", + "id" : -161 + }, + { + "name" : "minecraft:basalt", + "id" : -234 + }, + { + "name" : "minecraft:bat_spawn_egg", + "id" : 453 + }, + { + "name" : "minecraft:beacon", + "id" : 138 + }, + { + "name" : "minecraft:bed", + "id" : 418 + }, + { + "name" : "minecraft:bedrock", + "id" : 7 + }, + { + "name" : "minecraft:bee_nest", + "id" : -218 + }, + { + "name" : "minecraft:bee_spawn_egg", + "id" : 494 + }, + { + "name" : "minecraft:beef", + "id" : 273 + }, + { + "name" : "minecraft:beehive", + "id" : -219 + }, + { + "name" : "minecraft:beetroot", + "id" : 285 + }, + { + "name" : "minecraft:beetroot_seeds", + "id" : 295 + }, + { + "name" : "minecraft:beetroot_soup", + "id" : 286 + }, + { + "name" : "minecraft:bell", + "id" : -206 + }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, + { + "name" : "minecraft:birch_boat", + "id" : 376 + }, + { + "name" : "minecraft:birch_button", + "id" : -141 + }, + { + "name" : "minecraft:birch_chest_boat", + "id" : 639 + }, + { + "name" : "minecraft:birch_door", + "id" : 554 + }, + { + "name" : "minecraft:birch_fence_gate", + "id" : 184 + }, + { + "name" : "minecraft:birch_pressure_plate", + "id" : -151 + }, + { + "name" : "minecraft:birch_sign", + "id" : 577 + }, + { + "name" : "minecraft:birch_stairs", + "id" : 135 + }, + { + "name" : "minecraft:birch_standing_sign", + "id" : -186 + }, + { + "name" : "minecraft:birch_trapdoor", + "id" : -146 + }, + { + "name" : "minecraft:birch_wall_sign", + "id" : -187 + }, + { + "name" : "minecraft:black_candle", + "id" : -428 + }, + { + "name" : "minecraft:black_candle_cake", + "id" : -445 + }, + { + "name" : "minecraft:black_dye", + "id" : 395 + }, + { + "name" : "minecraft:black_glazed_terracotta", + "id" : 235 + }, + { + "name" : "minecraft:blackstone", + "id" : -273 + }, + { + "name" : "minecraft:blackstone_double_slab", + "id" : -283 + }, + { + "name" : "minecraft:blackstone_slab", + "id" : -282 + }, + { + "name" : "minecraft:blackstone_stairs", + "id" : -276 + }, + { + "name" : "minecraft:blackstone_wall", + "id" : -277 + }, + { + "name" : "minecraft:blast_furnace", + "id" : -196 + }, + { + "name" : "minecraft:blaze_powder", + "id" : 429 + }, + { + "name" : "minecraft:blaze_rod", + "id" : 423 + }, + { + "name" : "minecraft:blaze_spawn_egg", + "id" : 456 + }, + { + "name" : "minecraft:bleach", + "id" : 596 + }, + { + "name" : "minecraft:blue_candle", + "id" : -424 + }, + { + "name" : "minecraft:blue_candle_cake", + "id" : -441 + }, + { + "name" : "minecraft:blue_dye", + "id" : 399 + }, + { + "name" : "minecraft:blue_glazed_terracotta", + "id" : 231 + }, + { + "name" : "minecraft:blue_ice", + "id" : -11 + }, + { + "name" : "minecraft:boat", + "id" : 649 + }, + { + "name" : "minecraft:bone", + "id" : 415 + }, + { + "name" : "minecraft:bone_block", + "id" : 216 + }, + { + "name" : "minecraft:bone_meal", + "id" : 411 + }, + { + "name" : "minecraft:book", + "id" : 387 + }, + { + "name" : "minecraft:bookshelf", + "id" : 47 + }, + { + "name" : "minecraft:border_block", + "id" : 212 + }, + { + "name" : "minecraft:bordure_indented_banner_pattern", + "id" : 586 + }, + { + "name" : "minecraft:bow", + "id" : 300 + }, + { + "name" : "minecraft:bowl", + "id" : 321 + }, + { + "name" : "minecraft:bread", + "id" : 261 + }, + { + "name" : "minecraft:brewing_stand", + "id" : 431 + }, + { + "name" : "minecraft:brick", + "id" : 383 + }, + { + "name" : "minecraft:brick_block", + "id" : 45 + }, + { + "name" : "minecraft:brick_stairs", + "id" : 108 + }, + { + "name" : "minecraft:brown_candle", + "id" : -425 + }, + { + "name" : "minecraft:brown_candle_cake", + "id" : -442 + }, + { + "name" : "minecraft:brown_dye", + "id" : 398 + }, + { + "name" : "minecraft:brown_glazed_terracotta", + "id" : 232 + }, + { + "name" : "minecraft:brown_mushroom", + "id" : 39 + }, + { + "name" : "minecraft:brown_mushroom_block", + "id" : 99 + }, + { + "name" : "minecraft:bubble_column", + "id" : -160 + }, + { + "name" : "minecraft:bucket", + "id" : 360 + }, + { + "name" : "minecraft:budding_amethyst", + "id" : -328 + }, + { + "name" : "minecraft:cactus", + "id" : 81 + }, + { + "name" : "minecraft:cake", + "id" : 417 + }, + { + "name" : "minecraft:calcite", + "id" : -326 + }, + { + "name" : "minecraft:camera", + "id" : 593 + }, + { + "name" : "minecraft:campfire", + "id" : 589 + }, + { + "name" : "minecraft:candle", + "id" : -412 + }, + { + "name" : "minecraft:candle_cake", + "id" : -429 + }, + { + "name" : "minecraft:carpet", + "id" : 171 + }, + { + "name" : "minecraft:carrot", + "id" : 279 + }, + { + "name" : "minecraft:carrot_on_a_stick", + "id" : 517 + }, + { + "name" : "minecraft:carrots", + "id" : 141 + }, + { + "name" : "minecraft:cartography_table", + "id" : -200 + }, + { + "name" : "minecraft:carved_pumpkin", + "id" : -155 + }, + { + "name" : "minecraft:cat_spawn_egg", + "id" : 488 + }, + { + "name" : "minecraft:cauldron", + "id" : 432 + }, + { + "name" : "minecraft:cave_spider_spawn_egg", + "id" : 457 + }, + { + "name" : "minecraft:cave_vines", + "id" : -322 + }, + { + "name" : "minecraft:cave_vines_body_with_berries", + "id" : -375 + }, + { + "name" : "minecraft:cave_vines_head_with_berries", + "id" : -376 + }, + { + "name" : "minecraft:chain", + "id" : 619 + }, + { + "name" : "minecraft:chain_command_block", + "id" : 189 + }, + { + "name" : "minecraft:chainmail_boots", + "id" : 342 + }, + { + "name" : "minecraft:chainmail_chestplate", + "id" : 340 + }, + { + "name" : "minecraft:chainmail_helmet", + "id" : 339 + }, + { + "name" : "minecraft:chainmail_leggings", + "id" : 341 + }, + { + "name" : "minecraft:charcoal", + "id" : 303 + }, + { + "name" : "minecraft:chemical_heat", + "id" : 192 + }, + { + "name" : "minecraft:chemistry_table", + "id" : 238 + }, + { + "name" : "minecraft:chest", + "id" : 54 + }, + { + "name" : "minecraft:chest_boat", + "id" : 645 + }, + { + "name" : "minecraft:chest_minecart", + "id" : 389 + }, + { + "name" : "minecraft:chicken", + "id" : 275 + }, + { + "name" : "minecraft:chicken_spawn_egg", + "id" : 435 + }, + { + "name" : "minecraft:chiseled_deepslate", + "id" : -395 + }, + { + "name" : "minecraft:chiseled_nether_bricks", + "id" : -302 + }, + { + "name" : "minecraft:chiseled_polished_blackstone", + "id" : -279 + }, + { + "name" : "minecraft:chorus_flower", + "id" : 200 + }, + { + "name" : "minecraft:chorus_fruit", + "id" : 558 + }, + { + "name" : "minecraft:chorus_plant", + "id" : 240 + }, + { + "name" : "minecraft:clay", + "id" : 82 + }, + { + "name" : "minecraft:clay_ball", + "id" : 384 + }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, + { + "name" : "minecraft:clock", + "id" : 393 + }, + { + "name" : "minecraft:coal", + "id" : 302 + }, + { + "name" : "minecraft:coal_block", + "id" : 173 + }, + { + "name" : "minecraft:coal_ore", + "id" : 16 + }, + { + "name" : "minecraft:cobbled_deepslate", + "id" : -379 + }, + { + "name" : "minecraft:cobbled_deepslate_double_slab", + "id" : -396 + }, + { + "name" : "minecraft:cobbled_deepslate_slab", + "id" : -380 + }, + { + "name" : "minecraft:cobbled_deepslate_stairs", + "id" : -381 + }, + { + "name" : "minecraft:cobbled_deepslate_wall", + "id" : -382 + }, + { + "name" : "minecraft:cobblestone", + "id" : 4 + }, + { + "name" : "minecraft:cobblestone_wall", + "id" : 139 + }, + { + "name" : "minecraft:cocoa", + "id" : 127 + }, + { + "name" : "minecraft:cocoa_beans", + "id" : 412 + }, + { + "name" : "minecraft:cod", + "id" : 264 + }, + { + "name" : "minecraft:cod_bucket", + "id" : 364 + }, + { + "name" : "minecraft:cod_spawn_egg", + "id" : 480 + }, + { + "name" : "minecraft:colored_torch_bp", + "id" : 204 + }, + { + "name" : "minecraft:colored_torch_rg", + "id" : 202 + }, + { + "name" : "minecraft:command_block", + "id" : 137 + }, + { + "name" : "minecraft:command_block_minecart", + "id" : 563 + }, + { + "name" : "minecraft:comparator", + "id" : 522 + }, + { + "name" : "minecraft:compass", + "id" : 391 + }, + { + "name" : "minecraft:composter", + "id" : -213 + }, + { + "name" : "minecraft:compound", + "id" : 594 + }, + { + "name" : "minecraft:concrete", + "id" : 236 + }, + { + "name" : "minecraft:concrete_powder", + "id" : 237 + }, + { + "name" : "minecraft:conduit", + "id" : -157 + }, + { + "name" : "minecraft:cooked_beef", + "id" : 274 + }, + { + "name" : "minecraft:cooked_chicken", + "id" : 276 + }, + { + "name" : "minecraft:cooked_cod", + "id" : 268 + }, + { + "name" : "minecraft:cooked_mutton", + "id" : 551 + }, + { + "name" : "minecraft:cooked_porkchop", + "id" : 263 + }, + { + "name" : "minecraft:cooked_rabbit", + "id" : 289 + }, + { + "name" : "minecraft:cooked_salmon", + "id" : 269 + }, + { + "name" : "minecraft:cookie", + "id" : 271 + }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 504 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, + { + "name" : "minecraft:coral", + "id" : -131 + }, + { + "name" : "minecraft:coral_block", + "id" : -132 + }, + { + "name" : "minecraft:coral_fan", + "id" : -133 + }, + { + "name" : "minecraft:coral_fan_dead", + "id" : -134 + }, + { + "name" : "minecraft:coral_fan_hang", + "id" : -135 + }, + { + "name" : "minecraft:coral_fan_hang2", + "id" : -136 + }, + { + "name" : "minecraft:coral_fan_hang3", + "id" : -137 + }, + { + "name" : "minecraft:cow_spawn_egg", + "id" : 436 + }, + { + "name" : "minecraft:cracked_deepslate_bricks", + "id" : -410 + }, + { + "name" : "minecraft:cracked_deepslate_tiles", + "id" : -409 + }, + { + "name" : "minecraft:cracked_nether_bricks", + "id" : -303 + }, + { + "name" : "minecraft:cracked_polished_blackstone_bricks", + "id" : -280 + }, + { + "name" : "minecraft:crafting_table", + "id" : 58 + }, + { + "name" : "minecraft:creeper_banner_pattern", + "id" : 582 + }, + { + "name" : "minecraft:creeper_spawn_egg", + "id" : 441 + }, + { + "name" : "minecraft:crimson_button", + "id" : -260 + }, + { + "name" : "minecraft:crimson_door", + "id" : 616 + }, + { + "name" : "minecraft:crimson_double_slab", + "id" : -266 + }, + { + "name" : "minecraft:crimson_fence", + "id" : -256 + }, + { + "name" : "minecraft:crimson_fence_gate", + "id" : -258 + }, + { + "name" : "minecraft:crimson_fungus", + "id" : -228 + }, + { + "name" : "minecraft:crimson_hyphae", + "id" : -299 + }, + { + "name" : "minecraft:crimson_nylium", + "id" : -232 + }, + { + "name" : "minecraft:crimson_planks", + "id" : -242 + }, + { + "name" : "minecraft:crimson_pressure_plate", + "id" : -262 + }, + { + "name" : "minecraft:crimson_roots", + "id" : -223 + }, + { + "name" : "minecraft:crimson_sign", + "id" : 614 + }, + { + "name" : "minecraft:crimson_slab", + "id" : -264 + }, + { + "name" : "minecraft:crimson_stairs", + "id" : -254 + }, + { + "name" : "minecraft:crimson_standing_sign", + "id" : -250 + }, + { + "name" : "minecraft:crimson_stem", + "id" : -225 + }, + { + "name" : "minecraft:crimson_trapdoor", + "id" : -246 + }, + { + "name" : "minecraft:crimson_wall_sign", + "id" : -252 + }, + { + "name" : "minecraft:crossbow", + "id" : 575 + }, + { + "name" : "minecraft:crying_obsidian", + "id" : -289 + }, + { + "name" : "minecraft:cut_copper", + "id" : -347 + }, + { + "name" : "minecraft:cut_copper_slab", + "id" : -361 + }, + { + "name" : "minecraft:cut_copper_stairs", + "id" : -354 + }, + { + "name" : "minecraft:cyan_candle", + "id" : -422 + }, + { + "name" : "minecraft:cyan_candle_cake", + "id" : -439 + }, + { + "name" : "minecraft:cyan_dye", + "id" : 401 + }, + { + "name" : "minecraft:cyan_glazed_terracotta", + "id" : 229 + }, + { + "name" : "minecraft:dark_oak_boat", + "id" : 380 + }, + { + "name" : "minecraft:dark_oak_button", + "id" : -142 + }, + { + "name" : "minecraft:dark_oak_chest_boat", + "id" : 643 + }, + { + "name" : "minecraft:dark_oak_door", + "id" : 557 + }, + { + "name" : "minecraft:dark_oak_fence_gate", + "id" : 186 + }, + { + "name" : "minecraft:dark_oak_pressure_plate", + "id" : -152 + }, + { + "name" : "minecraft:dark_oak_sign", + "id" : 580 + }, + { + "name" : "minecraft:dark_oak_stairs", + "id" : 164 + }, + { + "name" : "minecraft:dark_oak_trapdoor", + "id" : -147 + }, + { + "name" : "minecraft:dark_prismarine_stairs", + "id" : -3 + }, + { + "name" : "minecraft:darkoak_standing_sign", + "id" : -192 + }, + { + "name" : "minecraft:darkoak_wall_sign", + "id" : -193 + }, + { + "name" : "minecraft:daylight_detector", + "id" : 151 + }, + { + "name" : "minecraft:daylight_detector_inverted", + "id" : 178 + }, + { + "name" : "minecraft:deadbush", + "id" : 32 + }, + { + "name" : "minecraft:deepslate", + "id" : -378 + }, + { + "name" : "minecraft:deepslate_brick_double_slab", + "id" : -399 + }, + { + "name" : "minecraft:deepslate_brick_slab", + "id" : -392 + }, + { + "name" : "minecraft:deepslate_brick_stairs", + "id" : -393 + }, + { + "name" : "minecraft:deepslate_brick_wall", + "id" : -394 + }, + { + "name" : "minecraft:deepslate_bricks", + "id" : -391 + }, + { + "name" : "minecraft:deepslate_coal_ore", + "id" : -406 + }, + { + "name" : "minecraft:deepslate_copper_ore", + "id" : -408 + }, + { + "name" : "minecraft:deepslate_diamond_ore", + "id" : -405 + }, + { + "name" : "minecraft:deepslate_emerald_ore", + "id" : -407 + }, + { + "name" : "minecraft:deepslate_gold_ore", + "id" : -402 + }, + { + "name" : "minecraft:deepslate_iron_ore", + "id" : -401 + }, + { + "name" : "minecraft:deepslate_lapis_ore", + "id" : -400 + }, + { + "name" : "minecraft:deepslate_redstone_ore", + "id" : -403 + }, + { + "name" : "minecraft:deepslate_tile_double_slab", + "id" : -398 + }, + { + "name" : "minecraft:deepslate_tile_slab", + "id" : -388 + }, + { + "name" : "minecraft:deepslate_tile_stairs", + "id" : -389 + }, + { + "name" : "minecraft:deepslate_tile_wall", + "id" : -390 + }, + { + "name" : "minecraft:deepslate_tiles", + "id" : -387 + }, + { + "name" : "minecraft:deny", + "id" : 211 + }, + { + "name" : "minecraft:detector_rail", + "id" : 28 + }, + { + "name" : "minecraft:diamond", + "id" : 304 + }, + { + "name" : "minecraft:diamond_axe", + "id" : 319 + }, + { + "name" : "minecraft:diamond_block", + "id" : 57 + }, + { + "name" : "minecraft:diamond_boots", + "id" : 350 + }, + { + "name" : "minecraft:diamond_chestplate", + "id" : 348 + }, + { + "name" : "minecraft:diamond_helmet", + "id" : 347 + }, + { + "name" : "minecraft:diamond_hoe", + "id" : 332 + }, + { + "name" : "minecraft:diamond_horse_armor", + "id" : 533 + }, + { + "name" : "minecraft:diamond_leggings", + "id" : 349 + }, + { + "name" : "minecraft:diamond_ore", + "id" : 56 + }, + { + "name" : "minecraft:diamond_pickaxe", + "id" : 318 + }, + { + "name" : "minecraft:diamond_shovel", + "id" : 317 + }, + { + "name" : "minecraft:diamond_sword", + "id" : 316 + }, + { + "name" : "minecraft:diorite_stairs", + "id" : -170 + }, + { + "name" : "minecraft:dirt", + "id" : 3 + }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, + { + "name" : "minecraft:disc_fragment_5", + "id" : 637 + }, + { + "name" : "minecraft:dispenser", + "id" : 23 + }, + { + "name" : "minecraft:dolphin_spawn_egg", + "id" : 484 + }, + { + "name" : "minecraft:donkey_spawn_egg", + "id" : 465 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 + }, + { + "name" : "minecraft:double_plant", + "id" : 175 + }, + { + "name" : "minecraft:double_stone_block_slab", + "id" : 43 + }, + { + "name" : "minecraft:double_stone_block_slab2", + "id" : 181 + }, + { + "name" : "minecraft:double_stone_block_slab3", + "id" : -167 + }, + { + "name" : "minecraft:double_stone_block_slab4", + "id" : -168 + }, + { + "name" : "minecraft:double_wooden_slab", + "id" : 157 + }, + { + "name" : "minecraft:dragon_breath", + "id" : 560 + }, + { + "name" : "minecraft:dragon_egg", + "id" : 122 + }, + { + "name" : "minecraft:dried_kelp", + "id" : 270 + }, + { + "name" : "minecraft:dried_kelp_block", + "id" : -139 + }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, + { + "name" : "minecraft:dropper", + "id" : 125 + }, + { + "name" : "minecraft:drowned_spawn_egg", + "id" : 483 + }, + { + "name" : "minecraft:dye", + "id" : 650 + }, + { + "name" : "minecraft:echo_shard", + "id" : 647 + }, + { + "name" : "minecraft:egg", + "id" : 390 + }, + { + "name" : "minecraft:elder_guardian_spawn_egg", + "id" : 471 + }, + { + "name" : "minecraft:element_0", + "id" : 36 + }, + { + "name" : "minecraft:element_1", + "id" : -12 + }, + { + "name" : "minecraft:element_10", + "id" : -21 + }, + { + "name" : "minecraft:element_100", + "id" : -111 + }, + { + "name" : "minecraft:element_101", + "id" : -112 + }, + { + "name" : "minecraft:element_102", + "id" : -113 + }, + { + "name" : "minecraft:element_103", + "id" : -114 + }, + { + "name" : "minecraft:element_104", + "id" : -115 + }, + { + "name" : "minecraft:element_105", + "id" : -116 + }, + { + "name" : "minecraft:element_106", + "id" : -117 + }, + { + "name" : "minecraft:element_107", + "id" : -118 + }, + { + "name" : "minecraft:element_108", + "id" : -119 + }, + { + "name" : "minecraft:element_109", + "id" : -120 + }, + { + "name" : "minecraft:element_11", + "id" : -22 + }, + { + "name" : "minecraft:element_110", + "id" : -121 + }, + { + "name" : "minecraft:element_111", + "id" : -122 + }, + { + "name" : "minecraft:element_112", + "id" : -123 + }, + { + "name" : "minecraft:element_113", + "id" : -124 + }, + { + "name" : "minecraft:element_114", + "id" : -125 + }, + { + "name" : "minecraft:element_115", + "id" : -126 + }, + { + "name" : "minecraft:element_116", + "id" : -127 + }, + { + "name" : "minecraft:element_117", + "id" : -128 + }, + { + "name" : "minecraft:element_118", + "id" : -129 + }, + { + "name" : "minecraft:element_12", + "id" : -23 + }, + { + "name" : "minecraft:element_13", + "id" : -24 + }, + { + "name" : "minecraft:element_14", + "id" : -25 + }, + { + "name" : "minecraft:element_15", + "id" : -26 + }, + { + "name" : "minecraft:element_16", + "id" : -27 + }, + { + "name" : "minecraft:element_17", + "id" : -28 + }, + { + "name" : "minecraft:element_18", + "id" : -29 + }, + { + "name" : "minecraft:element_19", + "id" : -30 + }, + { + "name" : "minecraft:element_2", + "id" : -13 + }, + { + "name" : "minecraft:element_20", + "id" : -31 + }, + { + "name" : "minecraft:element_21", + "id" : -32 + }, + { + "name" : "minecraft:element_22", + "id" : -33 + }, + { + "name" : "minecraft:element_23", + "id" : -34 + }, + { + "name" : "minecraft:element_24", + "id" : -35 + }, + { + "name" : "minecraft:element_25", + "id" : -36 + }, + { + "name" : "minecraft:element_26", + "id" : -37 + }, + { + "name" : "minecraft:element_27", + "id" : -38 + }, + { + "name" : "minecraft:element_28", + "id" : -39 + }, + { + "name" : "minecraft:element_29", + "id" : -40 + }, + { + "name" : "minecraft:element_3", + "id" : -14 + }, + { + "name" : "minecraft:element_30", + "id" : -41 + }, + { + "name" : "minecraft:element_31", + "id" : -42 + }, + { + "name" : "minecraft:element_32", + "id" : -43 + }, + { + "name" : "minecraft:element_33", + "id" : -44 + }, + { + "name" : "minecraft:element_34", + "id" : -45 + }, + { + "name" : "minecraft:element_35", + "id" : -46 + }, + { + "name" : "minecraft:element_36", + "id" : -47 + }, + { + "name" : "minecraft:element_37", + "id" : -48 + }, + { + "name" : "minecraft:element_38", + "id" : -49 + }, + { + "name" : "minecraft:element_39", + "id" : -50 + }, + { + "name" : "minecraft:element_4", + "id" : -15 + }, + { + "name" : "minecraft:element_40", + "id" : -51 + }, + { + "name" : "minecraft:element_41", + "id" : -52 + }, + { + "name" : "minecraft:element_42", + "id" : -53 + }, + { + "name" : "minecraft:element_43", + "id" : -54 + }, + { + "name" : "minecraft:element_44", + "id" : -55 + }, + { + "name" : "minecraft:element_45", + "id" : -56 + }, + { + "name" : "minecraft:element_46", + "id" : -57 + }, + { + "name" : "minecraft:element_47", + "id" : -58 + }, + { + "name" : "minecraft:element_48", + "id" : -59 + }, + { + "name" : "minecraft:element_49", + "id" : -60 + }, + { + "name" : "minecraft:element_5", + "id" : -16 + }, + { + "name" : "minecraft:element_50", + "id" : -61 + }, + { + "name" : "minecraft:element_51", + "id" : -62 + }, + { + "name" : "minecraft:element_52", + "id" : -63 + }, + { + "name" : "minecraft:element_53", + "id" : -64 + }, + { + "name" : "minecraft:element_54", + "id" : -65 + }, + { + "name" : "minecraft:element_55", + "id" : -66 + }, + { + "name" : "minecraft:element_56", + "id" : -67 + }, + { + "name" : "minecraft:element_57", + "id" : -68 + }, + { + "name" : "minecraft:element_58", + "id" : -69 + }, + { + "name" : "minecraft:element_59", + "id" : -70 + }, + { + "name" : "minecraft:element_6", + "id" : -17 + }, + { + "name" : "minecraft:element_60", + "id" : -71 + }, + { + "name" : "minecraft:element_61", + "id" : -72 + }, + { + "name" : "minecraft:element_62", + "id" : -73 + }, + { + "name" : "minecraft:element_63", + "id" : -74 + }, + { + "name" : "minecraft:element_64", + "id" : -75 + }, + { + "name" : "minecraft:element_65", + "id" : -76 + }, + { + "name" : "minecraft:element_66", + "id" : -77 + }, + { + "name" : "minecraft:element_67", + "id" : -78 + }, + { + "name" : "minecraft:element_68", + "id" : -79 + }, + { + "name" : "minecraft:element_69", + "id" : -80 + }, + { + "name" : "minecraft:element_7", + "id" : -18 + }, + { + "name" : "minecraft:element_70", + "id" : -81 + }, + { + "name" : "minecraft:element_71", + "id" : -82 + }, + { + "name" : "minecraft:element_72", + "id" : -83 + }, + { + "name" : "minecraft:element_73", + "id" : -84 + }, + { + "name" : "minecraft:element_74", + "id" : -85 + }, + { + "name" : "minecraft:element_75", + "id" : -86 + }, + { + "name" : "minecraft:element_76", + "id" : -87 + }, + { + "name" : "minecraft:element_77", + "id" : -88 + }, + { + "name" : "minecraft:element_78", + "id" : -89 + }, + { + "name" : "minecraft:element_79", + "id" : -90 + }, + { + "name" : "minecraft:element_8", + "id" : -19 + }, + { + "name" : "minecraft:element_80", + "id" : -91 + }, + { + "name" : "minecraft:element_81", + "id" : -92 + }, + { + "name" : "minecraft:element_82", + "id" : -93 + }, + { + "name" : "minecraft:element_83", + "id" : -94 + }, + { + "name" : "minecraft:element_84", + "id" : -95 + }, + { + "name" : "minecraft:element_85", + "id" : -96 + }, + { + "name" : "minecraft:element_86", + "id" : -97 + }, + { + "name" : "minecraft:element_87", + "id" : -98 + }, + { + "name" : "minecraft:element_88", + "id" : -99 + }, + { + "name" : "minecraft:element_89", + "id" : -100 + }, + { + "name" : "minecraft:element_9", + "id" : -20 + }, + { + "name" : "minecraft:element_90", + "id" : -101 + }, + { + "name" : "minecraft:element_91", + "id" : -102 + }, + { + "name" : "minecraft:element_92", + "id" : -103 + }, + { + "name" : "minecraft:element_93", + "id" : -104 + }, + { + "name" : "minecraft:element_94", + "id" : -105 + }, + { + "name" : "minecraft:element_95", + "id" : -106 + }, + { + "name" : "minecraft:element_96", + "id" : -107 + }, + { + "name" : "minecraft:element_97", + "id" : -108 + }, + { + "name" : "minecraft:element_98", + "id" : -109 + }, + { + "name" : "minecraft:element_99", + "id" : -110 + }, + { + "name" : "minecraft:elytra", + "id" : 564 + }, + { + "name" : "minecraft:emerald", + "id" : 512 + }, + { + "name" : "minecraft:emerald_block", + "id" : 133 + }, + { + "name" : "minecraft:emerald_ore", + "id" : 129 + }, + { + "name" : "minecraft:empty_map", + "id" : 515 + }, + { + "name" : "minecraft:enchanted_book", + "id" : 521 + }, + { + "name" : "minecraft:enchanted_golden_apple", + "id" : 259 + }, + { + "name" : "minecraft:enchanting_table", + "id" : 116 + }, + { + "name" : "minecraft:end_brick_stairs", + "id" : -178 + }, + { + "name" : "minecraft:end_bricks", + "id" : 206 + }, + { + "name" : "minecraft:end_crystal", + "id" : 653 + }, + { + "name" : "minecraft:end_gateway", + "id" : 209 + }, + { + "name" : "minecraft:end_portal", + "id" : 119 + }, + { + "name" : "minecraft:end_portal_frame", + "id" : 120 + }, + { + "name" : "minecraft:end_rod", + "id" : 208 + }, + { + "name" : "minecraft:end_stone", + "id" : 121 + }, + { + "name" : "minecraft:ender_chest", + "id" : 130 + }, + { + "name" : "minecraft:ender_eye", + "id" : 433 + }, + { + "name" : "minecraft:ender_pearl", + "id" : 422 + }, + { + "name" : "minecraft:enderman_spawn_egg", + "id" : 442 + }, + { + "name" : "minecraft:endermite_spawn_egg", + "id" : 460 + }, + { + "name" : "minecraft:evoker_spawn_egg", + "id" : 475 + }, + { + "name" : "minecraft:experience_bottle", + "id" : 508 + }, + { + "name" : "minecraft:exposed_copper", + "id" : -341 + }, + { + "name" : "minecraft:exposed_cut_copper", + "id" : -348 + }, + { + "name" : "minecraft:exposed_cut_copper_slab", + "id" : -362 + }, + { + "name" : "minecraft:exposed_cut_copper_stairs", + "id" : -355 + }, + { + "name" : "minecraft:exposed_double_cut_copper_slab", + "id" : -369 + }, + { + "name" : "minecraft:farmland", + "id" : 60 + }, + { + "name" : "minecraft:feather", + "id" : 327 + }, + { + "name" : "minecraft:fence", + "id" : 85 + }, + { + "name" : "minecraft:fence_gate", + "id" : 107 + }, + { + "name" : "minecraft:fermented_spider_eye", + "id" : 428 + }, + { + "name" : "minecraft:field_masoned_banner_pattern", + "id" : 585 + }, + { + "name" : "minecraft:filled_map", + "id" : 420 + }, + { + "name" : "minecraft:fire", + "id" : 51 + }, + { + "name" : "minecraft:fire_charge", + "id" : 509 + }, + { + "name" : "minecraft:firework_rocket", + "id" : 519 + }, + { + "name" : "minecraft:firework_star", + "id" : 520 + }, + { + "name" : "minecraft:fishing_rod", + "id" : 392 + }, + { + "name" : "minecraft:fletching_table", + "id" : -201 + }, + { + "name" : "minecraft:flint", + "id" : 356 + }, + { + "name" : "minecraft:flint_and_steel", + "id" : 299 + }, + { + "name" : "minecraft:flower_banner_pattern", + "id" : 581 + }, + { + "name" : "minecraft:flower_pot", + "id" : 514 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 + }, + { + "name" : "minecraft:flowing_lava", + "id" : 10 + }, + { + "name" : "minecraft:flowing_water", + "id" : 8 + }, + { + "name" : "minecraft:fox_spawn_egg", + "id" : 490 + }, + { + "name" : "minecraft:frame", + "id" : 513 + }, + { + "name" : "minecraft:frog_spawn", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 628 + }, + { + "name" : "minecraft:frosted_ice", + "id" : 207 + }, + { + "name" : "minecraft:furnace", + "id" : 61 + }, + { + "name" : "minecraft:ghast_spawn_egg", + "id" : 454 + }, + { + "name" : "minecraft:ghast_tear", + "id" : 424 + }, + { + "name" : "minecraft:gilded_blackstone", + "id" : -281 + }, + { + "name" : "minecraft:glass", + "id" : 20 + }, + { + "name" : "minecraft:glass_bottle", + "id" : 427 + }, + { + "name" : "minecraft:glass_pane", + "id" : 102 + }, + { + "name" : "minecraft:glistering_melon_slice", + "id" : 434 + }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, + { + "name" : "minecraft:glow_berries", + "id" : 654 + }, + { + "name" : "minecraft:glow_frame", + "id" : 623 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 503 + }, + { + "name" : "minecraft:glow_lichen", + "id" : -411 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 502 + }, + { + "name" : "minecraft:glow_stick", + "id" : 601 + }, + { + "name" : "minecraft:glowingobsidian", + "id" : 246 + }, + { + "name" : "minecraft:glowstone", + "id" : 89 + }, + { + "name" : "minecraft:glowstone_dust", + "id" : 394 + }, + { + "name" : "minecraft:goat_horn", + "id" : 627 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 501 + }, + { + "name" : "minecraft:gold_block", + "id" : 41 + }, + { + "name" : "minecraft:gold_ingot", + "id" : 306 + }, + { + "name" : "minecraft:gold_nugget", + "id" : 425 + }, + { + "name" : "minecraft:gold_ore", + "id" : 14 + }, + { + "name" : "minecraft:golden_apple", + "id" : 258 + }, + { + "name" : "minecraft:golden_axe", + "id" : 325 + }, + { + "name" : "minecraft:golden_boots", + "id" : 354 + }, + { + "name" : "minecraft:golden_carrot", + "id" : 283 + }, + { + "name" : "minecraft:golden_chestplate", + "id" : 352 + }, + { + "name" : "minecraft:golden_helmet", + "id" : 351 + }, + { + "name" : "minecraft:golden_hoe", + "id" : 333 + }, + { + "name" : "minecraft:golden_horse_armor", + "id" : 532 + }, + { + "name" : "minecraft:golden_leggings", + "id" : 353 + }, + { + "name" : "minecraft:golden_pickaxe", + "id" : 324 + }, + { + "name" : "minecraft:golden_rail", + "id" : 27 + }, + { + "name" : "minecraft:golden_shovel", + "id" : 323 + }, + { + "name" : "minecraft:golden_sword", + "id" : 322 + }, + { + "name" : "minecraft:granite_stairs", + "id" : -169 + }, + { + "name" : "minecraft:grass", + "id" : 2 + }, + { + "name" : "minecraft:grass_path", + "id" : 198 + }, + { + "name" : "minecraft:gravel", + "id" : 13 + }, + { + "name" : "minecraft:gray_candle", + "id" : -420 + }, + { + "name" : "minecraft:gray_candle_cake", + "id" : -437 + }, + { + "name" : "minecraft:gray_dye", + "id" : 403 + }, + { + "name" : "minecraft:gray_glazed_terracotta", + "id" : 227 + }, + { + "name" : "minecraft:green_candle", + "id" : -426 + }, + { + "name" : "minecraft:green_candle_cake", + "id" : -443 + }, + { + "name" : "minecraft:green_dye", + "id" : 397 + }, + { + "name" : "minecraft:green_glazed_terracotta", + "id" : 233 + }, + { + "name" : "minecraft:grindstone", + "id" : -195 + }, + { + "name" : "minecraft:guardian_spawn_egg", + "id" : 461 + }, + { + "name" : "minecraft:gunpowder", + "id" : 328 + }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, + { + "name" : "minecraft:hard_glass", + "id" : 253 + }, + { + "name" : "minecraft:hard_glass_pane", + "id" : 190 + }, + { + "name" : "minecraft:hard_stained_glass", + "id" : 254 + }, + { + "name" : "minecraft:hard_stained_glass_pane", + "id" : 191 + }, + { + "name" : "minecraft:hardened_clay", + "id" : 172 + }, + { + "name" : "minecraft:hay_block", + "id" : 170 + }, + { + "name" : "minecraft:heart_of_the_sea", + "id" : 571 + }, + { + "name" : "minecraft:heavy_weighted_pressure_plate", + "id" : 148 + }, + { + "name" : "minecraft:hoglin_spawn_egg", + "id" : 496 + }, + { + "name" : "minecraft:honey_block", + "id" : -220 + }, + { + "name" : "minecraft:honey_bottle", + "id" : 592 + }, + { + "name" : "minecraft:honeycomb", + "id" : 591 + }, + { + "name" : "minecraft:honeycomb_block", + "id" : -221 + }, + { + "name" : "minecraft:hopper", + "id" : 527 + }, + { + "name" : "minecraft:hopper_minecart", + "id" : 526 + }, + { + "name" : "minecraft:horse_spawn_egg", + "id" : 458 + }, + { + "name" : "minecraft:husk_spawn_egg", + "id" : 463 + }, + { + "name" : "minecraft:ice", + "id" : 79 + }, + { + "name" : "minecraft:ice_bomb", + "id" : 595 + }, + { + "name" : "minecraft:infested_deepslate", + "id" : -454 + }, + { + "name" : "minecraft:info_update", + "id" : 248 + }, + { + "name" : "minecraft:info_update2", + "id" : 249 + }, + { + "name" : "minecraft:ink_sac", + "id" : 413 + }, + { + "name" : "minecraft:invisible_bedrock", + "id" : 95 + }, + { + "name" : "minecraft:iron_axe", + "id" : 298 + }, + { + "name" : "minecraft:iron_bars", + "id" : 101 + }, + { + "name" : "minecraft:iron_block", + "id" : 42 + }, + { + "name" : "minecraft:iron_boots", + "id" : 346 + }, + { + "name" : "minecraft:iron_chestplate", + "id" : 344 + }, + { + "name" : "minecraft:iron_door", + "id" : 372 + }, + { + "name" : "minecraft:iron_helmet", + "id" : 343 + }, + { + "name" : "minecraft:iron_hoe", + "id" : 331 + }, + { + "name" : "minecraft:iron_horse_armor", + "id" : 531 + }, + { + "name" : "minecraft:iron_ingot", + "id" : 305 + }, + { + "name" : "minecraft:iron_leggings", + "id" : 345 + }, + { + "name" : "minecraft:iron_nugget", + "id" : 569 + }, + { + "name" : "minecraft:iron_ore", + "id" : 15 + }, + { + "name" : "minecraft:iron_pickaxe", + "id" : 297 + }, + { + "name" : "minecraft:iron_shovel", + "id" : 296 + }, + { + "name" : "minecraft:iron_sword", + "id" : 307 + }, + { + "name" : "minecraft:iron_trapdoor", + "id" : 167 + }, + { + "name" : "minecraft:item.acacia_door", + "id" : 196 + }, + { + "name" : "minecraft:item.bed", + "id" : 26 + }, + { + "name" : "minecraft:item.beetroot", + "id" : 244 + }, + { + "name" : "minecraft:item.birch_door", + "id" : 194 + }, + { + "name" : "minecraft:item.brewing_stand", + "id" : 117 + }, + { + "name" : "minecraft:item.cake", + "id" : 92 + }, + { + "name" : "minecraft:item.camera", + "id" : 242 + }, + { + "name" : "minecraft:item.campfire", + "id" : -209 + }, + { + "name" : "minecraft:item.cauldron", + "id" : 118 + }, + { + "name" : "minecraft:item.chain", + "id" : -286 + }, + { + "name" : "minecraft:item.crimson_door", + "id" : -244 + }, + { + "name" : "minecraft:item.dark_oak_door", + "id" : 197 + }, + { + "name" : "minecraft:item.flower_pot", + "id" : 140 + }, + { + "name" : "minecraft:item.frame", + "id" : 199 + }, + { + "name" : "minecraft:item.glow_frame", + "id" : -339 + }, + { + "name" : "minecraft:item.hopper", + "id" : 154 + }, + { + "name" : "minecraft:item.iron_door", + "id" : 71 + }, + { + "name" : "minecraft:item.jungle_door", + "id" : 195 + }, + { + "name" : "minecraft:item.kelp", + "id" : -138 + }, + { + "name" : "minecraft:item.mangrove_door", + "id" : -493 + }, + { + "name" : "minecraft:item.nether_sprouts", + "id" : -238 + }, + { + "name" : "minecraft:item.nether_wart", + "id" : 115 + }, + { + "name" : "minecraft:item.reeds", + "id" : 83 + }, + { + "name" : "minecraft:item.skull", + "id" : 144 + }, + { + "name" : "minecraft:item.soul_campfire", + "id" : -290 + }, + { + "name" : "minecraft:item.spruce_door", + "id" : 193 + }, + { + "name" : "minecraft:item.warped_door", + "id" : -245 + }, + { + "name" : "minecraft:item.wheat", + "id" : 59 + }, + { + "name" : "minecraft:item.wooden_door", + "id" : 64 + }, + { + "name" : "minecraft:jigsaw", + "id" : -211 + }, + { + "name" : "minecraft:jukebox", + "id" : 84 + }, + { + "name" : "minecraft:jungle_boat", + "id" : 377 + }, + { + "name" : "minecraft:jungle_button", + "id" : -143 + }, + { + "name" : "minecraft:jungle_chest_boat", + "id" : 640 + }, + { + "name" : "minecraft:jungle_door", + "id" : 555 + }, + { + "name" : "minecraft:jungle_fence_gate", + "id" : 185 + }, + { + "name" : "minecraft:jungle_pressure_plate", + "id" : -153 + }, + { + "name" : "minecraft:jungle_sign", + "id" : 578 + }, + { + "name" : "minecraft:jungle_stairs", + "id" : 136 + }, + { + "name" : "minecraft:jungle_standing_sign", + "id" : -188 + }, + { + "name" : "minecraft:jungle_trapdoor", + "id" : -148 + }, + { + "name" : "minecraft:jungle_wall_sign", + "id" : -189 + }, + { + "name" : "minecraft:kelp", + "id" : 382 + }, + { + "name" : "minecraft:ladder", + "id" : 65 + }, + { + "name" : "minecraft:lantern", + "id" : -208 + }, + { + "name" : "minecraft:lapis_block", + "id" : 22 + }, + { + "name" : "minecraft:lapis_lazuli", + "id" : 414 + }, + { + "name" : "minecraft:lapis_ore", + "id" : 21 + }, + { + "name" : "minecraft:large_amethyst_bud", + "id" : -330 + }, + { + "name" : "minecraft:lava", + "id" : 11 + }, + { + "name" : "minecraft:lava_bucket", + "id" : 363 + }, + { + "name" : "minecraft:lava_cauldron", + "id" : -210 + }, + { + "name" : "minecraft:lead", + "id" : 547 + }, + { + "name" : "minecraft:leather", + "id" : 381 + }, + { + "name" : "minecraft:leather_boots", + "id" : 338 + }, + { + "name" : "minecraft:leather_chestplate", + "id" : 336 + }, + { + "name" : "minecraft:leather_helmet", + "id" : 335 + }, + { + "name" : "minecraft:leather_horse_armor", + "id" : 530 + }, + { + "name" : "minecraft:leather_leggings", + "id" : 337 + }, + { + "name" : "minecraft:leaves", + "id" : 18 + }, + { + "name" : "minecraft:leaves2", + "id" : 161 + }, + { + "name" : "minecraft:lectern", + "id" : -194 + }, + { + "name" : "minecraft:lever", + "id" : 69 + }, + { + "name" : "minecraft:light_block", + "id" : -215 + }, + { + "name" : "minecraft:light_blue_candle", + "id" : -416 + }, + { + "name" : "minecraft:light_blue_candle_cake", + "id" : -433 + }, + { + "name" : "minecraft:light_blue_dye", + "id" : 407 + }, + { + "name" : "minecraft:light_blue_glazed_terracotta", + "id" : 223 + }, + { + "name" : "minecraft:light_gray_candle", + "id" : -421 + }, + { + "name" : "minecraft:light_gray_candle_cake", + "id" : -438 + }, + { + "name" : "minecraft:light_gray_dye", + "id" : 402 + }, + { + "name" : "minecraft:light_weighted_pressure_plate", + "id" : 147 + }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, + { + "name" : "minecraft:lime_candle", + "id" : -418 + }, + { + "name" : "minecraft:lime_candle_cake", + "id" : -435 + }, + { + "name" : "minecraft:lime_dye", + "id" : 405 + }, + { + "name" : "minecraft:lime_glazed_terracotta", + "id" : 225 + }, + { + "name" : "minecraft:lingering_potion", + "id" : 562 + }, + { + "name" : "minecraft:lit_blast_furnace", + "id" : -214 + }, + { + "name" : "minecraft:lit_deepslate_redstone_ore", + "id" : -404 + }, + { + "name" : "minecraft:lit_furnace", + "id" : 62 + }, + { + "name" : "minecraft:lit_pumpkin", + "id" : 91 + }, + { + "name" : "minecraft:lit_redstone_lamp", + "id" : 124 + }, + { + "name" : "minecraft:lit_redstone_ore", + "id" : 74 + }, + { + "name" : "minecraft:lit_smoker", + "id" : -199 + }, + { + "name" : "minecraft:llama_spawn_egg", + "id" : 473 + }, + { + "name" : "minecraft:lodestone", + "id" : -222 + }, + { + "name" : "minecraft:lodestone_compass", + "id" : 602 + }, + { + "name" : "minecraft:log", + "id" : 17 + }, + { + "name" : "minecraft:log2", + "id" : 162 + }, + { + "name" : "minecraft:loom", + "id" : -204 + }, + { + "name" : "minecraft:magenta_candle", + "id" : -415 + }, + { + "name" : "minecraft:magenta_candle_cake", + "id" : -432 + }, + { + "name" : "minecraft:magenta_dye", + "id" : 408 + }, + { + "name" : "minecraft:magenta_glazed_terracotta", + "id" : 222 + }, + { + "name" : "minecraft:magma", + "id" : 213 + }, + { + "name" : "minecraft:magma_cream", + "id" : 430 + }, + { + "name" : "minecraft:magma_cube_spawn_egg", + "id" : 455 + }, + { + "name" : "minecraft:mangrove_boat", + "id" : 635 + }, + { + "name" : "minecraft:mangrove_button", + "id" : -487 + }, + { + "name" : "minecraft:mangrove_chest_boat", + "id" : 644 + }, + { + "name" : "minecraft:mangrove_door", + "id" : 633 + }, + { + "name" : "minecraft:mangrove_double_slab", + "id" : -499 + }, + { + "name" : "minecraft:mangrove_fence", + "id" : -491 + }, + { + "name" : "minecraft:mangrove_fence_gate", + "id" : -492 + }, + { + "name" : "minecraft:mangrove_leaves", + "id" : -472 + }, + { + "name" : "minecraft:mangrove_log", + "id" : -484 + }, + { + "name" : "minecraft:mangrove_planks", + "id" : -486 + }, + { + "name" : "minecraft:mangrove_pressure_plate", + "id" : -490 + }, + { + "name" : "minecraft:mangrove_propagule", + "id" : -474 + }, + { + "name" : "minecraft:mangrove_roots", + "id" : -482 + }, + { + "name" : "minecraft:mangrove_sign", + "id" : 634 + }, + { + "name" : "minecraft:mangrove_slab", + "id" : -489 + }, + { + "name" : "minecraft:mangrove_stairs", + "id" : -488 + }, + { + "name" : "minecraft:mangrove_standing_sign", + "id" : -494 + }, + { + "name" : "minecraft:mangrove_trapdoor", + "id" : -496 + }, + { + "name" : "minecraft:mangrove_wall_sign", + "id" : -495 + }, + { + "name" : "minecraft:mangrove_wood", + "id" : -497 + }, + { + "name" : "minecraft:medicine", + "id" : 599 + }, + { + "name" : "minecraft:medium_amethyst_bud", + "id" : -331 + }, + { + "name" : "minecraft:melon_block", + "id" : 103 + }, + { + "name" : "minecraft:melon_seeds", + "id" : 293 + }, + { + "name" : "minecraft:melon_slice", + "id" : 272 + }, + { + "name" : "minecraft:melon_stem", + "id" : 105 + }, + { + "name" : "minecraft:milk_bucket", + "id" : 361 + }, + { + "name" : "minecraft:minecart", + "id" : 370 + }, + { + "name" : "minecraft:mob_spawner", + "id" : 52 + }, + { + "name" : "minecraft:mojang_banner_pattern", + "id" : 584 + }, + { + "name" : "minecraft:monster_egg", + "id" : 97 + }, + { + "name" : "minecraft:mooshroom_spawn_egg", + "id" : 440 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 + }, + { + "name" : "minecraft:mossy_cobblestone", + "id" : 48 + }, + { + "name" : "minecraft:mossy_cobblestone_stairs", + "id" : -179 + }, + { + "name" : "minecraft:mossy_stone_brick_stairs", + "id" : -175 + }, + { + "name" : "minecraft:moving_block", + "id" : 250 + }, + { + "name" : "minecraft:mud", + "id" : -473 + }, + { + "name" : "minecraft:mud_brick_double_slab", + "id" : -479 + }, + { + "name" : "minecraft:mud_brick_slab", + "id" : -478 + }, + { + "name" : "minecraft:mud_brick_stairs", + "id" : -480 + }, + { + "name" : "minecraft:mud_brick_wall", + "id" : -481 + }, + { + "name" : "minecraft:mud_bricks", + "id" : -475 + }, + { + "name" : "minecraft:muddy_mangrove_roots", + "id" : -483 + }, + { + "name" : "minecraft:mule_spawn_egg", + "id" : 466 + }, + { + "name" : "minecraft:mushroom_stew", + "id" : 260 + }, + { + "name" : "minecraft:music_disc_11", + "id" : 544 + }, + { + "name" : "minecraft:music_disc_13", + "id" : 534 + }, + { + "name" : "minecraft:music_disc_5", + "id" : 636 + }, + { + "name" : "minecraft:music_disc_blocks", + "id" : 536 + }, + { + "name" : "minecraft:music_disc_cat", + "id" : 535 + }, + { + "name" : "minecraft:music_disc_chirp", + "id" : 537 + }, + { + "name" : "minecraft:music_disc_far", + "id" : 538 + }, + { + "name" : "minecraft:music_disc_mall", + "id" : 539 + }, + { + "name" : "minecraft:music_disc_mellohi", + "id" : 540 + }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 626 + }, + { + "name" : "minecraft:music_disc_pigstep", + "id" : 620 + }, + { + "name" : "minecraft:music_disc_stal", + "id" : 541 + }, + { + "name" : "minecraft:music_disc_strad", + "id" : 542 + }, + { + "name" : "minecraft:music_disc_wait", + "id" : 545 + }, + { + "name" : "minecraft:music_disc_ward", + "id" : 543 + }, + { + "name" : "minecraft:mutton", + "id" : 550 + }, + { + "name" : "minecraft:mycelium", + "id" : 110 + }, + { + "name" : "minecraft:name_tag", + "id" : 548 + }, + { + "name" : "minecraft:nautilus_shell", + "id" : 570 + }, + { + "name" : "minecraft:nether_brick", + "id" : 112 + }, + { + "name" : "minecraft:nether_brick_fence", + "id" : 113 + }, + { + "name" : "minecraft:nether_brick_stairs", + "id" : 114 + }, + { + "name" : "minecraft:nether_gold_ore", + "id" : -288 + }, + { + "name" : "minecraft:nether_sprouts", + "id" : 621 + }, + { + "name" : "minecraft:nether_star", + "id" : 518 + }, + { + "name" : "minecraft:nether_wart", + "id" : 294 + }, + { + "name" : "minecraft:nether_wart_block", + "id" : 214 + }, + { + "name" : "minecraft:netherbrick", + "id" : 523 + }, + { + "name" : "minecraft:netherite_axe", + "id" : 607 + }, + { + "name" : "minecraft:netherite_block", + "id" : -270 + }, + { + "name" : "minecraft:netherite_boots", + "id" : 612 + }, + { + "name" : "minecraft:netherite_chestplate", + "id" : 610 + }, + { + "name" : "minecraft:netherite_helmet", + "id" : 609 + }, + { + "name" : "minecraft:netherite_hoe", + "id" : 608 + }, + { + "name" : "minecraft:netherite_ingot", + "id" : 603 + }, + { + "name" : "minecraft:netherite_leggings", + "id" : 611 + }, + { + "name" : "minecraft:netherite_pickaxe", + "id" : 606 + }, + { + "name" : "minecraft:netherite_scrap", + "id" : 613 + }, + { + "name" : "minecraft:netherite_shovel", + "id" : 605 + }, + { + "name" : "minecraft:netherite_sword", + "id" : 604 + }, + { + "name" : "minecraft:netherrack", + "id" : 87 + }, + { + "name" : "minecraft:netherreactor", + "id" : 247 + }, + { + "name" : "minecraft:normal_stone_stairs", + "id" : -180 + }, + { + "name" : "minecraft:noteblock", + "id" : 25 + }, + { + "name" : "minecraft:npc_spawn_egg", + "id" : 470 + }, + { + "name" : "minecraft:oak_boat", + "id" : 375 + }, + { + "name" : "minecraft:oak_chest_boat", + "id" : 638 + }, + { + "name" : "minecraft:oak_sign", + "id" : 358 + }, + { + "name" : "minecraft:oak_stairs", + "id" : 53 + }, + { + "name" : "minecraft:observer", + "id" : 251 + }, + { + "name" : "minecraft:obsidian", + "id" : 49 + }, + { + "name" : "minecraft:ocelot_spawn_egg", + "id" : 451 + }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, + { + "name" : "minecraft:orange_candle", + "id" : -414 + }, + { + "name" : "minecraft:orange_candle_cake", + "id" : -431 + }, + { + "name" : "minecraft:orange_dye", + "id" : 409 + }, + { + "name" : "minecraft:orange_glazed_terracotta", + "id" : 221 + }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "name" : "minecraft:oxidized_cut_copper", + "id" : -350 + }, + { + "name" : "minecraft:oxidized_cut_copper_slab", + "id" : -364 + }, + { + "name" : "minecraft:oxidized_cut_copper_stairs", + "id" : -357 + }, + { + "name" : "minecraft:oxidized_double_cut_copper_slab", + "id" : -371 + }, + { + "name" : "minecraft:packed_ice", + "id" : 174 + }, + { + "name" : "minecraft:packed_mud", + "id" : -477 + }, + { + "name" : "minecraft:painting", + "id" : 357 + }, + { + "name" : "minecraft:panda_spawn_egg", + "id" : 489 + }, + { + "name" : "minecraft:paper", + "id" : 386 + }, + { + "name" : "minecraft:parrot_spawn_egg", + "id" : 478 + }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, + { + "name" : "minecraft:phantom_membrane", + "id" : 574 + }, + { + "name" : "minecraft:phantom_spawn_egg", + "id" : 486 + }, + { + "name" : "minecraft:pig_spawn_egg", + "id" : 437 + }, + { + "name" : "minecraft:piglin_banner_pattern", + "id" : 587 + }, + { + "name" : "minecraft:piglin_brute_spawn_egg", + "id" : 499 + }, + { + "name" : "minecraft:piglin_spawn_egg", + "id" : 497 + }, + { + "name" : "minecraft:pillager_spawn_egg", + "id" : 491 + }, + { + "name" : "minecraft:pink_candle", + "id" : -419 + }, + { + "name" : "minecraft:pink_candle_cake", + "id" : -436 + }, + { + "name" : "minecraft:pink_dye", + "id" : 404 + }, + { + "name" : "minecraft:pink_glazed_terracotta", + "id" : 226 + }, + { + "name" : "minecraft:piston", + "id" : 33 + }, + { + "name" : "minecraft:piston_arm_collision", + "id" : 34 + }, + { + "name" : "minecraft:planks", + "id" : 5 + }, + { + "name" : "minecraft:podzol", + "id" : 243 + }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, + { + "name" : "minecraft:poisonous_potato", + "id" : 282 + }, + { + "name" : "minecraft:polar_bear_spawn_egg", + "id" : 472 + }, + { + "name" : "minecraft:polished_andesite_stairs", + "id" : -174 + }, + { + "name" : "minecraft:polished_basalt", + "id" : -235 + }, + { + "name" : "minecraft:polished_blackstone", + "id" : -291 + }, + { + "name" : "minecraft:polished_blackstone_brick_double_slab", + "id" : -285 + }, + { + "name" : "minecraft:polished_blackstone_brick_slab", + "id" : -284 + }, + { + "name" : "minecraft:polished_blackstone_brick_stairs", + "id" : -275 + }, + { + "name" : "minecraft:polished_blackstone_brick_wall", + "id" : -278 + }, + { + "name" : "minecraft:polished_blackstone_bricks", + "id" : -274 + }, + { + "name" : "minecraft:polished_blackstone_button", + "id" : -296 + }, + { + "name" : "minecraft:polished_blackstone_double_slab", + "id" : -294 + }, + { + "name" : "minecraft:polished_blackstone_pressure_plate", + "id" : -295 + }, + { + "name" : "minecraft:polished_blackstone_slab", + "id" : -293 + }, + { + "name" : "minecraft:polished_blackstone_stairs", + "id" : -292 + }, + { + "name" : "minecraft:polished_blackstone_wall", + "id" : -297 + }, + { + "name" : "minecraft:polished_deepslate", + "id" : -383 + }, + { + "name" : "minecraft:polished_deepslate_double_slab", + "id" : -397 + }, + { + "name" : "minecraft:polished_deepslate_slab", + "id" : -384 + }, + { + "name" : "minecraft:polished_deepslate_stairs", + "id" : -385 + }, + { + "name" : "minecraft:polished_deepslate_wall", + "id" : -386 + }, + { + "name" : "minecraft:polished_diorite_stairs", + "id" : -173 + }, + { + "name" : "minecraft:polished_granite_stairs", + "id" : -172 + }, + { + "name" : "minecraft:popped_chorus_fruit", + "id" : 559 + }, + { + "name" : "minecraft:porkchop", + "id" : 262 + }, + { + "name" : "minecraft:portal", + "id" : 90 + }, + { + "name" : "minecraft:potato", + "id" : 280 + }, + { + "name" : "minecraft:potatoes", + "id" : 142 + }, + { + "name" : "minecraft:potion", + "id" : 426 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 + }, + { + "name" : "minecraft:powered_comparator", + "id" : 150 + }, + { + "name" : "minecraft:powered_repeater", + "id" : 94 + }, + { + "name" : "minecraft:prismarine", + "id" : 168 + }, + { + "name" : "minecraft:prismarine_bricks_stairs", + "id" : -4 + }, + { + "name" : "minecraft:prismarine_crystals", + "id" : 549 + }, + { + "name" : "minecraft:prismarine_shard", + "id" : 565 + }, + { + "name" : "minecraft:prismarine_stairs", + "id" : -2 + }, + { + "name" : "minecraft:pufferfish", + "id" : 267 + }, + { + "name" : "minecraft:pufferfish_bucket", + "id" : 367 + }, + { + "name" : "minecraft:pufferfish_spawn_egg", + "id" : 481 + }, + { + "name" : "minecraft:pumpkin", + "id" : 86 + }, + { + "name" : "minecraft:pumpkin_pie", + "id" : 284 + }, + { + "name" : "minecraft:pumpkin_seeds", + "id" : 292 + }, + { + "name" : "minecraft:pumpkin_stem", + "id" : 104 + }, + { + "name" : "minecraft:purple_candle", + "id" : -423 + }, + { + "name" : "minecraft:purple_candle_cake", + "id" : -440 + }, + { + "name" : "minecraft:purple_dye", + "id" : 400 + }, + { + "name" : "minecraft:purple_glazed_terracotta", + "id" : 219 + }, + { + "name" : "minecraft:purpur_block", + "id" : 201 + }, + { + "name" : "minecraft:purpur_stairs", + "id" : 203 + }, + { + "name" : "minecraft:quartz", + "id" : 524 + }, + { + "name" : "minecraft:quartz_block", + "id" : 155 + }, + { + "name" : "minecraft:quartz_bricks", + "id" : -304 + }, + { + "name" : "minecraft:quartz_ore", + "id" : 153 + }, + { + "name" : "minecraft:quartz_stairs", + "id" : 156 + }, + { + "name" : "minecraft:rabbit", + "id" : 288 + }, + { + "name" : "minecraft:rabbit_foot", + "id" : 528 + }, + { + "name" : "minecraft:rabbit_hide", + "id" : 529 + }, + { + "name" : "minecraft:rabbit_spawn_egg", + "id" : 459 + }, + { + "name" : "minecraft:rabbit_stew", + "id" : 290 + }, + { + "name" : "minecraft:rail", + "id" : 66 + }, + { + "name" : "minecraft:rapid_fertilizer", + "id" : 597 + }, + { + "name" : "minecraft:ravager_spawn_egg", + "id" : 493 + }, + { + "name" : "minecraft:raw_copper", + "id" : 507 + }, + { + "name" : "minecraft:raw_copper_block", + "id" : -452 + }, + { + "name" : "minecraft:raw_gold", + "id" : 506 + }, + { + "name" : "minecraft:raw_gold_block", + "id" : -453 + }, + { + "name" : "minecraft:raw_iron", + "id" : 505 + }, + { + "name" : "minecraft:raw_iron_block", + "id" : -451 + }, + { + "name" : "minecraft:recovery_compass", + "id" : 646 + }, + { + "name" : "minecraft:red_candle", + "id" : -427 + }, + { + "name" : "minecraft:red_candle_cake", + "id" : -444 + }, + { + "name" : "minecraft:red_dye", + "id" : 396 + }, + { + "name" : "minecraft:red_flower", + "id" : 38 + }, + { + "name" : "minecraft:red_glazed_terracotta", + "id" : 234 + }, + { + "name" : "minecraft:red_mushroom", + "id" : 40 + }, + { + "name" : "minecraft:red_mushroom_block", + "id" : 100 + }, + { + "name" : "minecraft:red_nether_brick", + "id" : 215 + }, + { + "name" : "minecraft:red_nether_brick_stairs", + "id" : -184 + }, + { + "name" : "minecraft:red_sandstone", + "id" : 179 + }, + { + "name" : "minecraft:red_sandstone_stairs", + "id" : 180 + }, + { + "name" : "minecraft:redstone", + "id" : 373 + }, + { + "name" : "minecraft:redstone_block", + "id" : 152 + }, + { + "name" : "minecraft:redstone_lamp", + "id" : 123 + }, + { + "name" : "minecraft:redstone_ore", + "id" : 73 + }, + { + "name" : "minecraft:redstone_torch", + "id" : 76 + }, + { + "name" : "minecraft:redstone_wire", + "id" : 55 + }, + { + "name" : "minecraft:reinforced_deepslate", + "id" : -466 + }, + { + "name" : "minecraft:repeater", + "id" : 419 + }, + { + "name" : "minecraft:repeating_command_block", + "id" : 188 + }, + { + "name" : "minecraft:reserved6", + "id" : 255 + }, + { + "name" : "minecraft:respawn_anchor", + "id" : -272 + }, + { + "name" : "minecraft:rotten_flesh", + "id" : 277 + }, + { + "name" : "minecraft:saddle", + "id" : 371 + }, + { + "name" : "minecraft:salmon", + "id" : 265 + }, + { + "name" : "minecraft:salmon_bucket", + "id" : 365 + }, + { + "name" : "minecraft:salmon_spawn_egg", + "id" : 482 + }, + { + "name" : "minecraft:sand", + "id" : 12 + }, + { + "name" : "minecraft:sandstone", + "id" : 24 + }, + { + "name" : "minecraft:sandstone_stairs", + "id" : 128 + }, + { + "name" : "minecraft:sapling", + "id" : 6 + }, + { + "name" : "minecraft:scaffolding", + "id" : -165 + }, + { + "name" : "minecraft:sculk", + "id" : -458 + }, + { + "name" : "minecraft:sculk_catalyst", + "id" : -460 + }, + { + "name" : "minecraft:sculk_sensor", + "id" : -307 + }, + { + "name" : "minecraft:sculk_shrieker", + "id" : -461 + }, + { + "name" : "minecraft:sculk_vein", + "id" : -459 + }, + { + "name" : "minecraft:scute", + "id" : 572 + }, + { + "name" : "minecraft:sea_lantern", + "id" : 169 + }, + { + "name" : "minecraft:sea_pickle", + "id" : -156 + }, + { + "name" : "minecraft:seagrass", + "id" : -130 + }, + { + "name" : "minecraft:shears", + "id" : 421 + }, + { + "name" : "minecraft:sheep_spawn_egg", + "id" : 438 + }, + { + "name" : "minecraft:shield", + "id" : 355 + }, + { + "name" : "minecraft:shroomlight", + "id" : -230 + }, + { + "name" : "minecraft:shulker_box", + "id" : 218 + }, + { + "name" : "minecraft:shulker_shell", + "id" : 566 + }, + { + "name" : "minecraft:shulker_spawn_egg", + "id" : 469 + }, + { + "name" : "minecraft:silver_glazed_terracotta", + "id" : 228 + }, + { + "name" : "minecraft:silverfish_spawn_egg", + "id" : 443 + }, + { + "name" : "minecraft:skeleton_horse_spawn_egg", + "id" : 467 + }, + { + "name" : "minecraft:skeleton_spawn_egg", + "id" : 444 + }, + { + "name" : "minecraft:skull", + "id" : 516 + }, + { + "name" : "minecraft:skull_banner_pattern", + "id" : 583 + }, + { + "name" : "minecraft:slime", + "id" : 165 + }, + { + "name" : "minecraft:slime_ball", + "id" : 388 + }, + { + "name" : "minecraft:slime_spawn_egg", + "id" : 445 + }, + { + "name" : "minecraft:small_amethyst_bud", + "id" : -332 + }, + { + "name" : "minecraft:small_dripleaf_block", + "id" : -336 + }, + { + "name" : "minecraft:smithing_table", + "id" : -202 + }, + { + "name" : "minecraft:smoker", + "id" : -198 + }, + { + "name" : "minecraft:smooth_basalt", + "id" : -377 + }, + { + "name" : "minecraft:smooth_quartz_stairs", + "id" : -185 + }, + { + "name" : "minecraft:smooth_red_sandstone_stairs", + "id" : -176 + }, + { + "name" : "minecraft:smooth_sandstone_stairs", + "id" : -177 + }, + { + "name" : "minecraft:smooth_stone", + "id" : -183 + }, + { + "name" : "minecraft:snow", + "id" : 80 + }, + { + "name" : "minecraft:snow_layer", + "id" : 78 + }, + { + "name" : "minecraft:snowball", + "id" : 374 + }, + { + "name" : "minecraft:soul_campfire", + "id" : 622 + }, + { + "name" : "minecraft:soul_fire", + "id" : -237 + }, + { + "name" : "minecraft:soul_lantern", + "id" : -269 + }, + { + "name" : "minecraft:soul_sand", + "id" : 88 + }, + { + "name" : "minecraft:soul_soil", + "id" : -236 + }, + { + "name" : "minecraft:soul_torch", + "id" : -268 + }, + { + "name" : "minecraft:sparkler", + "id" : 600 + }, + { + "name" : "minecraft:spawn_egg", + "id" : 652 + }, + { + "name" : "minecraft:spider_eye", + "id" : 278 + }, + { + "name" : "minecraft:spider_spawn_egg", + "id" : 446 + }, + { + "name" : "minecraft:splash_potion", + "id" : 561 + }, + { + "name" : "minecraft:sponge", + "id" : 19 + }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, + { + "name" : "minecraft:spruce_boat", + "id" : 378 + }, + { + "name" : "minecraft:spruce_button", + "id" : -144 + }, + { + "name" : "minecraft:spruce_chest_boat", + "id" : 641 + }, + { + "name" : "minecraft:spruce_door", + "id" : 553 + }, + { + "name" : "minecraft:spruce_fence_gate", + "id" : 183 + }, + { + "name" : "minecraft:spruce_pressure_plate", + "id" : -154 + }, + { + "name" : "minecraft:spruce_sign", + "id" : 576 + }, + { + "name" : "minecraft:spruce_stairs", + "id" : 134 + }, + { + "name" : "minecraft:spruce_standing_sign", + "id" : -181 + }, + { + "name" : "minecraft:spruce_trapdoor", + "id" : -149 + }, + { + "name" : "minecraft:spruce_wall_sign", + "id" : -182 + }, + { + "name" : "minecraft:spyglass", + "id" : 625 + }, + { + "name" : "minecraft:squid_spawn_egg", + "id" : 450 + }, + { + "name" : "minecraft:stained_glass", + "id" : 241 + }, + { + "name" : "minecraft:stained_glass_pane", + "id" : 160 + }, + { + "name" : "minecraft:stained_hardened_clay", + "id" : 159 + }, + { + "name" : "minecraft:standing_banner", + "id" : 176 + }, + { + "name" : "minecraft:standing_sign", + "id" : 63 + }, + { + "name" : "minecraft:stick", + "id" : 320 + }, + { + "name" : "minecraft:sticky_piston", + "id" : 29 + }, + { + "name" : "minecraft:sticky_piston_arm_collision", + "id" : -217 + }, + { + "name" : "minecraft:stone", + "id" : 1 + }, + { + "name" : "minecraft:stone_axe", + "id" : 315 + }, + { + "name" : "minecraft:stone_block_slab", + "id" : 44 + }, + { + "name" : "minecraft:stone_block_slab2", + "id" : 182 + }, + { + "name" : "minecraft:stone_block_slab3", + "id" : -162 + }, + { + "name" : "minecraft:stone_block_slab4", + "id" : -166 + }, + { + "name" : "minecraft:stone_brick_stairs", + "id" : 109 + }, + { + "name" : "minecraft:stone_button", + "id" : 77 + }, + { + "name" : "minecraft:stone_hoe", + "id" : 330 + }, + { + "name" : "minecraft:stone_pickaxe", + "id" : 314 + }, + { + "name" : "minecraft:stone_pressure_plate", + "id" : 70 + }, + { + "name" : "minecraft:stone_shovel", + "id" : 313 + }, + { + "name" : "minecraft:stone_stairs", + "id" : 67 + }, + { + "name" : "minecraft:stone_sword", + "id" : 312 + }, + { + "name" : "minecraft:stonebrick", + "id" : 98 + }, + { + "name" : "minecraft:stonecutter", + "id" : 245 + }, + { + "name" : "minecraft:stonecutter_block", + "id" : -197 + }, + { + "name" : "minecraft:stray_spawn_egg", + "id" : 462 + }, + { + "name" : "minecraft:strider_spawn_egg", + "id" : 495 + }, + { + "name" : "minecraft:string", + "id" : 326 + }, + { + "name" : "minecraft:stripped_acacia_log", + "id" : -8 + }, + { + "name" : "minecraft:stripped_birch_log", + "id" : -6 + }, + { + "name" : "minecraft:stripped_crimson_hyphae", + "id" : -300 + }, + { + "name" : "minecraft:stripped_crimson_stem", + "id" : -240 + }, + { + "name" : "minecraft:stripped_dark_oak_log", + "id" : -9 + }, + { + "name" : "minecraft:stripped_jungle_log", + "id" : -7 + }, + { + "name" : "minecraft:stripped_mangrove_log", + "id" : -485 + }, + { + "name" : "minecraft:stripped_mangrove_wood", + "id" : -498 + }, + { + "name" : "minecraft:stripped_oak_log", + "id" : -10 + }, + { + "name" : "minecraft:stripped_spruce_log", + "id" : -5 + }, + { + "name" : "minecraft:stripped_warped_hyphae", + "id" : -301 + }, + { + "name" : "minecraft:stripped_warped_stem", + "id" : -241 + }, + { + "name" : "minecraft:structure_block", + "id" : 252 + }, + { + "name" : "minecraft:structure_void", + "id" : 217 + }, + { + "name" : "minecraft:sugar", + "id" : 416 + }, + { + "name" : "minecraft:sugar_cane", + "id" : 385 + }, + { + "name" : "minecraft:suspicious_stew", + "id" : 590 + }, + { + "name" : "minecraft:sweet_berries", + "id" : 287 + }, + { + "name" : "minecraft:sweet_berry_bush", + "id" : -207 + }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 630 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 629 + }, + { + "name" : "minecraft:tallgrass", + "id" : 31 + }, + { + "name" : "minecraft:target", + "id" : -239 + }, + { + "name" : "minecraft:tinted_glass", + "id" : -334 + }, + { + "name" : "minecraft:tnt", + "id" : 46 + }, + { + "name" : "minecraft:tnt_minecart", + "id" : 525 + }, + { + "name" : "minecraft:torch", + "id" : 50 + }, + { + "name" : "minecraft:totem_of_undying", + "id" : 568 + }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 648 + }, + { + "name" : "minecraft:trapdoor", + "id" : 96 + }, + { + "name" : "minecraft:trapped_chest", + "id" : 146 + }, + { + "name" : "minecraft:trident", + "id" : 546 + }, + { + "name" : "minecraft:trip_wire", + "id" : 132 + }, + { + "name" : "minecraft:tripwire_hook", + "id" : 131 + }, + { + "name" : "minecraft:tropical_fish", + "id" : 266 + }, + { + "name" : "minecraft:tropical_fish_bucket", + "id" : 366 + }, + { + "name" : "minecraft:tropical_fish_spawn_egg", + "id" : 479 + }, + { + "name" : "minecraft:tuff", + "id" : -333 + }, + { + "name" : "minecraft:turtle_egg", + "id" : -159 + }, + { + "name" : "minecraft:turtle_helmet", + "id" : 573 + }, + { + "name" : "minecraft:turtle_spawn_egg", + "id" : 485 + }, + { + "name" : "minecraft:twisting_vines", + "id" : -287 + }, + { + "name" : "minecraft:underwater_torch", + "id" : 239 + }, + { + "name" : "minecraft:undyed_shulker_box", + "id" : 205 + }, + { + "name" : "minecraft:unknown", + "id" : -305 + }, + { + "name" : "minecraft:unlit_redstone_torch", + "id" : 75 + }, + { + "name" : "minecraft:unpowered_comparator", + "id" : 149 + }, + { + "name" : "minecraft:unpowered_repeater", + "id" : 93 + }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, + { + "name" : "minecraft:vex_spawn_egg", + "id" : 476 + }, + { + "name" : "minecraft:villager_spawn_egg", + "id" : 449 + }, + { + "name" : "minecraft:vindicator_spawn_egg", + "id" : 474 + }, + { + "name" : "minecraft:vine", + "id" : 106 + }, + { + "name" : "minecraft:wall_banner", + "id" : 177 + }, + { + "name" : "minecraft:wall_sign", + "id" : 68 + }, + { + "name" : "minecraft:wandering_trader_spawn_egg", + "id" : 492 + }, + { + "name" : "minecraft:warden_spawn_egg", + "id" : 632 + }, + { + "name" : "minecraft:warped_button", + "id" : -261 + }, + { + "name" : "minecraft:warped_door", + "id" : 617 + }, + { + "name" : "minecraft:warped_double_slab", + "id" : -267 + }, + { + "name" : "minecraft:warped_fence", + "id" : -257 + }, + { + "name" : "minecraft:warped_fence_gate", + "id" : -259 + }, + { + "name" : "minecraft:warped_fungus", + "id" : -229 + }, + { + "name" : "minecraft:warped_fungus_on_a_stick", + "id" : 618 + }, + { + "name" : "minecraft:warped_hyphae", + "id" : -298 + }, + { + "name" : "minecraft:warped_nylium", + "id" : -233 + }, + { + "name" : "minecraft:warped_planks", + "id" : -243 + }, + { + "name" : "minecraft:warped_pressure_plate", + "id" : -263 + }, + { + "name" : "minecraft:warped_roots", + "id" : -224 + }, + { + "name" : "minecraft:warped_sign", + "id" : 615 + }, + { + "name" : "minecraft:warped_slab", + "id" : -265 + }, + { + "name" : "minecraft:warped_stairs", + "id" : -255 + }, + { + "name" : "minecraft:warped_standing_sign", + "id" : -251 + }, + { + "name" : "minecraft:warped_stem", + "id" : -226 + }, + { + "name" : "minecraft:warped_trapdoor", + "id" : -247 + }, + { + "name" : "minecraft:warped_wall_sign", + "id" : -253 + }, + { + "name" : "minecraft:warped_wart_block", + "id" : -227 + }, + { + "name" : "minecraft:water", + "id" : 9 + }, + { + "name" : "minecraft:water_bucket", + "id" : 362 + }, + { + "name" : "minecraft:waterlily", + "id" : 111 + }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "name" : "minecraft:waxed_cut_copper", + "id" : -351 + }, + { + "name" : "minecraft:waxed_cut_copper_slab", + "id" : -365 + }, + { + "name" : "minecraft:waxed_cut_copper_stairs", + "id" : -358 + }, + { + "name" : "minecraft:waxed_double_cut_copper_slab", + "id" : -372 + }, + { + "name" : "minecraft:waxed_exposed_copper", + "id" : -345 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper", + "id" : -352 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_slab", + "id" : -366 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_stairs", + "id" : -359 + }, + { + "name" : "minecraft:waxed_exposed_double_cut_copper_slab", + "id" : -373 + }, + { + "name" : "minecraft:waxed_oxidized_copper", + "id" : -446 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper", + "id" : -447 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_slab", + "id" : -449 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_stairs", + "id" : -448 + }, + { + "name" : "minecraft:waxed_oxidized_double_cut_copper_slab", + "id" : -450 + }, + { + "name" : "minecraft:waxed_weathered_copper", + "id" : -346 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper", + "id" : -353 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_slab", + "id" : -367 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_stairs", + "id" : -360 + }, + { + "name" : "minecraft:waxed_weathered_double_cut_copper_slab", + "id" : -374 + }, + { + "name" : "minecraft:weathered_copper", + "id" : -342 + }, + { + "name" : "minecraft:weathered_cut_copper", + "id" : -349 + }, + { + "name" : "minecraft:weathered_cut_copper_slab", + "id" : -363 + }, + { + "name" : "minecraft:weathered_cut_copper_stairs", + "id" : -356 + }, + { + "name" : "minecraft:weathered_double_cut_copper_slab", + "id" : -370 + }, + { + "name" : "minecraft:web", + "id" : 30 + }, + { + "name" : "minecraft:weeping_vines", + "id" : -231 + }, + { + "name" : "minecraft:wheat", + "id" : 334 + }, + { + "name" : "minecraft:wheat_seeds", + "id" : 291 + }, + { + "name" : "minecraft:white_candle", + "id" : -413 + }, + { + "name" : "minecraft:white_candle_cake", + "id" : -430 + }, + { + "name" : "minecraft:white_dye", + "id" : 410 + }, + { + "name" : "minecraft:white_glazed_terracotta", + "id" : 220 + }, + { + "name" : "minecraft:witch_spawn_egg", + "id" : 452 + }, + { + "name" : "minecraft:wither_rose", + "id" : -216 + }, + { + "name" : "minecraft:wither_skeleton_spawn_egg", + "id" : 464 + }, + { + "name" : "minecraft:wolf_spawn_egg", + "id" : 439 + }, + { + "name" : "minecraft:wood", + "id" : -212 + }, + { + "name" : "minecraft:wooden_axe", + "id" : 311 + }, + { + "name" : "minecraft:wooden_button", + "id" : 143 + }, + { + "name" : "minecraft:wooden_door", + "id" : 359 + }, + { + "name" : "minecraft:wooden_hoe", + "id" : 329 + }, + { + "name" : "minecraft:wooden_pickaxe", + "id" : 310 + }, + { + "name" : "minecraft:wooden_pressure_plate", + "id" : 72 + }, + { + "name" : "minecraft:wooden_shovel", + "id" : 309 + }, + { + "name" : "minecraft:wooden_slab", + "id" : 158 + }, + { + "name" : "minecraft:wooden_sword", + "id" : 308 + }, + { + "name" : "minecraft:wool", + "id" : 35 + }, + { + "name" : "minecraft:writable_book", + "id" : 510 + }, + { + "name" : "minecraft:written_book", + "id" : 511 + }, + { + "name" : "minecraft:yellow_candle", + "id" : -417 + }, + { + "name" : "minecraft:yellow_candle_cake", + "id" : -434 + }, + { + "name" : "minecraft:yellow_dye", + "id" : 406 + }, + { + "name" : "minecraft:yellow_flower", + "id" : 37 + }, + { + "name" : "minecraft:yellow_glazed_terracotta", + "id" : 224 + }, + { + "name" : "minecraft:zoglin_spawn_egg", + "id" : 498 + }, + { + "name" : "minecraft:zombie_horse_spawn_egg", + "id" : 468 + }, + { + "name" : "minecraft:zombie_pigman_spawn_egg", + "id" : 448 + }, + { + "name" : "minecraft:zombie_spawn_egg", + "id" : 447 + }, + { + "name" : "minecraft:zombie_villager_spawn_egg", + "id" : 477 + } +] \ No newline at end of file From 656c8b1a5ebeb990025f9f1bc207b519b85ced19 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 3 Dec 2022 23:59:27 -0500 Subject: [PATCH 332/358] Update mappings --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 5bd26dd735b..810a4e8174f 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 5bd26dd735bd89dd50e5c55a0d022f7c70916300 +Subproject commit 810a4e8174fd9d5b81c5e7d2f3c2f6164565eb9c From 91a2e79bd1aab3e970c4f53ebb57a275372588e1 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 4 Dec 2022 00:30:16 -0500 Subject: [PATCH 333/358] Actually update mappings --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 810a4e8174f..e8703ccb187 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 810a4e8174fd9d5b81c5e7d2f3c2f6164565eb9c +Subproject commit e8703ccb187f98cd845357395d7b4ecfafbcd864 From f9a52ffc96b6ab1cd10425836bd5c266a0e62b3d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 4 Dec 2022 00:56:43 -0500 Subject: [PATCH 334/358] Add support for SetContentPacket containerId 0 --- .../JavaContainerSetContentTranslator.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java index 61982533813..cfe1c404e26 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java @@ -31,6 +31,7 @@ import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; +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.util.InventoryUtils; @@ -46,7 +47,7 @@ public void translate(GeyserSession session, ClientboundContainerSetContentPacke int inventorySize = inventory.getSize(); for (int i = 0; i < packet.getItems().length; i++) { - if (i > inventorySize) { + if (i >= inventorySize) { GeyserImpl geyser = session.getGeyser(); geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.bedrockUsername() + " that exceeds inventory size!"); @@ -54,10 +55,7 @@ public void translate(GeyserSession session, ClientboundContainerSetContentPacke 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); + } + } } From 3d66d2790f3a03ca8bafa7ce1cc6429ad9ab7a18 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 4 Dec 2022 13:34:51 -0500 Subject: [PATCH 335/358] Fix rare dimension switch inconsistencies Fixes #3161 --- .../java/org/geysermc/geyser/session/GeyserSession.java | 1 - .../translator/protocol/java/JavaRespawnTranslator.java | 7 ++----- .../main/java/org/geysermc/geyser/util/DimensionUtils.java | 4 +++- 3 files changed, 5 insertions(+), 7 deletions(-) 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 17a609fb710..056fa52eefe 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -155,7 +155,6 @@ import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; 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 0f02256d0ea..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,11 +83,8 @@ 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); } 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 887f42a8e82..5963da703a5 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -208,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() { From 59974c3f3aa31c641b81053ad9d6d3a5d4a05ea7 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 4 Dec 2022 18:12:07 -0500 Subject: [PATCH 336/358] A start on camels and hanging signs --- .../geyser/entity/EntityDefinitions.java | 8 +++ .../type/living/animal/horse/CamelEntity.java | 70 +++++++++++++++++++ .../entity/SignBlockEntityTranslator.java | 3 +- .../BedrockBlockEntityDataTranslator.java | 4 ++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java 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..cad69a11775 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; @@ -894,6 +895,13 @@ public final class EntityDefinitions { .type(EntityType.TRADER_LLAMA) .identifier("minecraft:llama") .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(); } EntityDefinition tameableEntityBase = EntityDefinition.inherited(TameableEntity::new, ageableEntityBase) 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/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/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index 67f0d0d59f2..d70759ffbf7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -57,6 +57,10 @@ public void translate(GeyserSession session, BlockEntityDataPacket packet) { // This converts the message into the array'd message Java wants for (char character : text.toCharArray()) { widthCount += SignUtils.getCharacterWidth(character); + + // todo 1.20: update for hanging signs (smaller width). Currently bedrock thinks hanging signs are normal, + // so it thinks hanging signs have more width than they actually do. Seems like JE just truncates it. + // If we get a return in Bedrock, or go over the character width max, that signals to use the next line. if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) { // We need to apply some more logic if we went over the character width max From bd5428a2e63cc877473634280876da63b12b1734 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 5 Dec 2022 14:20:34 -0500 Subject: [PATCH 337/358] Alphabetize the camel --- .../geysermc/geyser/entity/EntityDefinitions.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 cad69a11775..b97e2384762 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -860,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) @@ -895,13 +902,6 @@ public final class EntityDefinitions { .type(EntityType.TRADER_LLAMA) .identifier("minecraft:llama") .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(); } EntityDefinition tameableEntityBase = EntityDefinition.inherited(TameableEntity::new, ageableEntityBase) From f76aa71b5b01d2d0e8f74abf80464dc4a954d98e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:06:40 -0500 Subject: [PATCH 338/358] Point to stable MCProtocolLib version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 62da6ca893b..0a0bccf88f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-SNAPSHOT" +mcprotocollib = "1.19.3-20221206.010348-1" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 0c79732d025ec62738f42b7d67b5ee80f9e11637 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:06:10 -0500 Subject: [PATCH 339/358] Add changes from 1.19.3-rc3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a0bccf88f8..b18c2db633c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20221206.010348-1" +mcprotocollib = "1.19.3-20221206.215111-2" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 32829622e719049fd07eff66379ec70c078dbd12 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 7 Dec 2022 11:07:40 -0500 Subject: [PATCH 340/358] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5051ed04d6..60cbd94f7f2 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.20 - 1.19.50 and Minecraft Java 1.19.1/1.19.2. +### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.50 and Minecraft Java 1.19.3. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. From 95ebdb8ebf58ac2a484304b124804373cb9632c1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 7 Dec 2022 11:59:20 -0500 Subject: [PATCH 341/358] Update BungeeCord version check --- .../geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1c460f4dea5..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 @@ -76,7 +76,7 @@ public void onLoad() { // 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(" / \\"); From e7544c0bb44eee07b9da628a99756abeeed5d6db Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 7 Dec 2022 20:09:48 -0500 Subject: [PATCH 342/358] Fix some chat not appearing for Bedrock users --- .../geyser/session/GeyserSession.java | 72 ++----------------- .../java/JavaDisguisedChatTranslator.java | 41 +++++++++++ .../java/JavaPlayerChatTranslator.java | 41 +---------- .../translator/text/MessageTranslator.java | 45 +++++++++++- 4 files changed, 91 insertions(+), 108 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java 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 7a407f6f97c..2e3ee4dde6b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -58,58 +58,21 @@ import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Session; -import com.github.steveice10.packetlib.event.session.ConnectedEvent; -import com.github.steveice10.packetlib.event.session.DisconnectedEvent; -import com.github.steveice10.packetlib.event.session.PacketErrorEvent; -import com.github.steveice10.packetlib.event.session.PacketSendingEvent; -import com.github.steveice10.packetlib.event.session.SessionAdapter; +import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpClientSession; import com.github.steveice10.packetlib.tcp.TcpSession; import com.nukkitx.math.GenericMath; -import com.nukkitx.math.vector.Vector2f; -import com.nukkitx.math.vector.Vector2i; -import com.nukkitx.math.vector.Vector3d; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.math.vector.*; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; -import com.nukkitx.protocol.bedrock.data.Ability; -import com.nukkitx.protocol.bedrock.data.AbilityLayer; -import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.data.AuthoritativeMovementMode; -import com.nukkitx.protocol.bedrock.data.ChatRestrictionLevel; -import com.nukkitx.protocol.bedrock.data.GamePublishSetting; -import com.nukkitx.protocol.bedrock.data.GameRuleData; -import com.nukkitx.protocol.bedrock.data.GameType; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.data.SyncedPlayerMovementSettings; +import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; -import com.nukkitx.protocol.bedrock.packet.BiomeDefinitionListPacket; -import com.nukkitx.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; -import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; -import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; -import com.nukkitx.protocol.bedrock.packet.CreativeContentPacket; -import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; -import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; -import com.nukkitx.protocol.bedrock.packet.ItemComponentPacket; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; -import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket; -import com.nukkitx.protocol.bedrock.packet.SetTimePacket; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import com.nukkitx.protocol.bedrock.packet.TextPacket; -import com.nukkitx.protocol.bedrock.packet.TransferPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAbilitiesPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAdventureSettingsPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +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; @@ -162,20 +125,7 @@ import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; -import org.geysermc.geyser.session.cache.AdvancementsCache; -import org.geysermc.geyser.session.cache.BookEditCache; -import org.geysermc.geyser.session.cache.ChunkCache; -import org.geysermc.geyser.session.cache.EntityCache; -import org.geysermc.geyser.session.cache.EntityEffectCache; -import org.geysermc.geyser.session.cache.FormCache; -import org.geysermc.geyser.session.cache.LodestoneCache; -import org.geysermc.geyser.session.cache.PistonCache; -import org.geysermc.geyser.session.cache.PreferencesCache; -import org.geysermc.geyser.session.cache.SkullCache; -import org.geysermc.geyser.session.cache.TagCache; -import org.geysermc.geyser.session.cache.TeleportCache; -import org.geysermc.geyser.session.cache.WorldBorder; -import org.geysermc.geyser.session.cache.WorldCache; +import org.geysermc.geyser.session.cache.*; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; @@ -191,15 +141,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -1416,8 +1358,6 @@ public String locale() { return clientData.getLanguageCode(); } - // TODO: 1.19.3 int offest and ack'd messages BitSet??? - /** * Sends a chat message to the Java server. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java new file mode 100644 index 00000000000..2ad45fe521f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.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.translator.protocol.java; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDisguisedChatPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; + +@Translator(packet = ClientboundDisguisedChatPacket.class) +public class JavaDisguisedChatTranslator extends PacketTranslator { + + @Override + 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/JavaPlayerChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java index 4c2d51cb885..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 ? Component.text(packet.getContent()) : 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.locale())); - } else { - textPacket.setMessage(MessageTranslator.convertMessage(message, session.locale())); - } - - session.sendUpstreamPacket(textPacket); + MessageTranslator.handleChatPacket(session, message, packet.getChatType(), packet.getTargetName(), packet.getName()); } } 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 1b267823a52..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 @@ -250,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 * From 57e34372d8e408835a34d3c0ba7d2ab18d693400 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 7 Dec 2022 22:54:43 -0500 Subject: [PATCH 343/358] Update MCProtocolLib to potentially fix respawn issues --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b18c2db633c..d44f8e24a12 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20221206.215111-2" +mcprotocollib = "1.19.3-20221208.034912-3" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 247edc6665ce954220288fbcfcddfa9b5a6b88b6 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 8 Dec 2022 21:31:45 -0500 Subject: [PATCH 344/358] Don't say that 1.19.2 is supported (#3443) --- .../src/main/java/org/geysermc/geyser/network/GameProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index e85dc689d77..2e080c4ddb9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -110,7 +110,7 @@ public static PacketCodec getJavaCodec() { * @return the supported Minecraft: Java Edition version names */ public static List getJavaVersions() { - return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion(), "1.19.2"); + return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion()); } /** From 6876a90c3b7337f9d340a2c588e1f04be4d53925 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 9 Dec 2022 13:39:24 -0500 Subject: [PATCH 345/358] Lower size of BiomeDefinitionsPacket --- .../main/java/org/geysermc/geyser/registry/Registries.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 2c1c51baf0a..866cbd29180 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -181,12 +181,15 @@ public static void init() { POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new); ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new); - // TEMPORARY FIX TO MAKE OLD BIOMES NBT WORK WITH 1.19.30 + // 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.put("name_hash", key); + 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()); From 2d63f09e1674f78868118f1b1c16451710448082 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 11 Dec 2022 00:01:36 -0500 Subject: [PATCH 346/358] Check if spawner contains entity type (#3450) --- .../entity/SpawnerBlockEntityTranslator.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) 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); + } } } From b27b1c86bdbd6da510ca714a6b85d88c2f7c1704 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 11 Dec 2022 10:15:49 -0800 Subject: [PATCH 347/358] Makes bows, crossbows, tridents, projectiles, and lighters registered as custom items function properly (#3420) Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../CustomItemRegistryPopulator.java | 56 +++++++++++++++++++ .../registry/type/GeyserMappingItem.java | 2 + core/src/main/resources/mappings | 2 +- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 0e40f9c4384..e32030db6a0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -128,6 +128,29 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, G 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()); @@ -273,6 +296,39 @@ private static void computeBlockItemProperties(String blockItem, NbtMapBuilder c componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); } + private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + // setting high use_duration prevents the consume animation from playing + itemProperties.putInt("use_duration", Integer.MAX_VALUE); + // display item as tool (mainly for crossbow and bow) + itemProperties.putBoolean("hand_equipped", true); + // ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly) + componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build()); + } + + private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) { + // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks + itemProperties.putInt("use_duration", 32); + // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds + // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively + itemProperties.putInt("use_animation", useAnimation); + // this component is required to allow the eat animation to play + componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + } + + private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { + // all items registered that place entities should be given this component to prevent double placement + // it is okay that the entity here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); + } + + private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { + // allows item to be thrown when holding down right click (individual presses are required w/o this component) + componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); + // this must be set to something for the swing animation to play + // it is okay that the projectile here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); + } + private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { if (isHat) { componentBuilder.remove("minecraft:render_offsets"); diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java index 6c65f1c3476..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 @@ -48,4 +48,6 @@ public class GeyserMappingItem { @JsonProperty("repair_materials") List repairMaterials; @JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false; @JsonProperty("dye_color") int dyeColor = -1; + @JsonProperty("is_edible") boolean edible = false; + @JsonProperty("is_entity_placer") boolean entityPlacer = false; } diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index e8703ccb187..f9d62b3f73d 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit e8703ccb187f98cd845357395d7b4ecfafbcd864 +Subproject commit f9d62b3f73db270bd4e0c833b7728b30d29e1369 From 7c26036906f870d739846b8d49f3990c435adc67 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 13 Dec 2022 13:53:28 -0500 Subject: [PATCH 348/358] Update adapters to support 1.19.3 and add biome command completions --- .../GeyserSpigotNativeWorldManager.java | 9 ++ .../geysermc/geyser/level/WorldManager.java | 9 ++ .../geyser/session/GeyserSession.java | 5 + .../protocol/java/JavaCommandsTranslator.java | 94 ++++++++++++++----- .../protocol/java/JavaLoginTranslator.java | 1 + gradle/libs.versions.toml | 2 +- 6 files changed, 98 insertions(+), 22 deletions(-) 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 bf9085979ae..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 @@ -32,6 +32,7 @@ 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/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index b3a727d2636..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,7 @@ import com.nukkitx.nbt.NbtMap; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nullable; import java.util.Locale; /** @@ -157,4 +158,12 @@ public void setDifficulty(GeyserSession session, Difficulty difficulty) { * @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/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 2e3ee4dde6b..8f9bc394a4a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -296,6 +296,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ @Setter private String worldName = null; + /** + * As of Java 1.19.3, the client only uses these for commands. + */ + @Setter + private String[] levels; private boolean sneaking; 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 14ff1a51afc..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,6 +44,7 @@ import lombok.Getter; import lombok.ToString; import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent; import org.geysermc.geyser.command.GeyserCommandManager; @@ -198,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(); @@ -211,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; @@ -232,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 BLOCK_STATE -> context.getBlockStates(); + case ITEM_STACK -> context.session.getItemMappings().getItemNames(); case COLOR -> VALID_COLORS; case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS; - case RESOURCE, RESOURCE_OR_TAG -> { - String resource = ((ResourceProperties) node.getProperties()).getRegistryKey(); - yield switch (resource) { - // minecraft:worldgen/biome is also valid but we currently don't cache biome IDs - case "minecraft:attribute" -> ATTRIBUTES; - case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; - case "minecraft:entity_type" -> Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]); - case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS; - default -> CommandParam.STRING; - }; - } + case RESOURCE -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false); + case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true); + case DIMENSION -> context.session.getLevels(); + default -> CommandParam.STRING; + }; + } + + private static Object handleResource(CommandBuilderContext context, String resource, boolean tags) { + return switch (resource) { + case "minecraft:attribute" -> ATTRIBUTES; + case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; + case "minecraft:entity_type" -> context.getEntityTypes(); + case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS; + case "minecraft:worldgen/biome" -> tags ? context.getBiomesWithTags() : context.getBiomes(); default -> CommandParam.STRING; }; } @@ -254,7 +258,55 @@ yield switch (resource) { /** * Stores the command description and parameter data for best optimizing the Bedrock commands packet. */ - private static record BedrockCommandInfo(String name, String description, CommandParamData[][] paramData) implements ServerDefineCommandsEvent.CommandInfo { + 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 @@ -279,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]; @@ -320,7 +372,7 @@ 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(); @@ -343,7 +395,7 @@ public void buildChildren(GeyserSession session, CommandNode[] allNodes) { // Recursively build all child options for (ParamInfo child : children) { - child.buildChildren(session, allNodes); + child.buildChildren(context, allNodes); } } 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 a0d418324ed..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 @@ -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(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d44f8e24a12..2864302206e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ jline = "3.21.0" terminalconsoleappender = "1.2.0" paper = "1.19-R0.1-SNAPSHOT" viaversion = "4.0.0" -adapters = "1.5-SNAPSHOT" +adapters = "1.6-SNAPSHOT" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.0.0" From 193d2803a3b3af782983e2ab45bf0762d5c5de7a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 13 Dec 2022 13:54:40 -0500 Subject: [PATCH 349/358] Indicate 1.19.51 support --- README.md | 2 +- .../java/org/geysermc/geyser/network/GameProtocol.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 60cbd94f7f2..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.20 - 1.19.50 and Minecraft Java 1.19.3. +### 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. diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 2e080c4ddb9..6b46f805641 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -67,8 +67,12 @@ public final class GameProtocol { 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); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.V557_CODEC.toBuilder() + .minecraftVersion("1.19.40/1.19.41") + .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.19.50/1.19.51") + .build()); } /** From f7375ed7dc9596a66b4413fc765c249e197ae889 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 13 Dec 2022 14:04:17 -0500 Subject: [PATCH 350/358] Fix chests not animating on open Fixes #3454 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2864302206e..55a25a99fcb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20221208.034912-3" +mcprotocollib = "1.19.3-20221213.190132-4" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 97bedd39e223ccc155884b9d71b78fab7b240f17 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 16 Dec 2022 14:17:12 -0500 Subject: [PATCH 351/358] Update MCProtocolLib to fix some commands packet decoding --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 55a25a99fcb..c463130b1bd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20221213.190132-4" +mcprotocollib = "1.19.3-20221215.055419-5" packetlib = "3.0" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 486e2fca1e7afa4123c3fc51bfe455c17210de81 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 17 Dec 2022 12:38:49 -0500 Subject: [PATCH 352/358] Should clean up some crafting transactions a bit --- .../geyser/inventory/click/ClickPlan.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 da72f9f99c7..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; From e4dcc07dde581583008c2d7b7a7b6615928a08ea Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 18 Dec 2022 15:49:52 +0100 Subject: [PATCH 353/358] Bump MCProtocolLib and PacketLib --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c463130b1bd..9b595671e46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +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-20221215.055419-5" -packetlib = "3.0" +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" From 3b5984117d7cbd897ed9be8a7d3d9cb3c3f2686a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 18 Dec 2022 22:34:16 -0500 Subject: [PATCH 354/358] Update aux value for polished granite Fixes #3462 --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index f9d62b3f73d..677c5b0872d 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit f9d62b3f73db270bd4e0c833b7728b30d29e1369 +Subproject commit 677c5b0872d2f0c99ad834c0ca49a0ae3b45fde3 From 98069cff83c923d808b5fe8b54f0a2458a9a37e7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 00:35:03 -0500 Subject: [PATCH 355/358] Fix certain sounds not correctly playing Fixes #3463 --- .../org/geysermc/geyser/util/SoundUtils.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) 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 b80a9afe79c..ccfd658cece 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -25,6 +25,7 @@ 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.Sound; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -65,10 +66,7 @@ private static SoundEvent toSoundEvent(String sound) { * @return a Bedrock sound */ public static String translatePlaySound(String javaIdentifier) { - // Drop the Minecraft namespace if applicable - if (javaIdentifier.startsWith("minecraft:")) { - javaIdentifier = javaIdentifier.substring("minecraft:".length()); - } + javaIdentifier = trim(javaIdentifier); SoundMapping soundMapping = Registries.SOUNDS.get(javaIdentifier); if (soundMapping == null || soundMapping.getPlaysound() == null) { @@ -79,6 +77,23 @@ public static String translatePlaySound(String 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 * @@ -88,22 +103,24 @@ public static String translatePlaySound(String javaIdentifier) { * @param pitch the pitch */ public static void playSound(GeyserSession session, Sound javaSound, Vector3f position, float volume, float pitch) { - String packetSound = javaSound.getName(); + String packetSound; + if (!(javaSound instanceof BuiltinSound)) { + // Identifier needs trimmed probably. + packetSound = translatePlaySound(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; } From fcd5fe1341ee1346d1576deac929e4483886c990 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 00:37:48 -0500 Subject: [PATCH 356/358] Wrong method call on previous commit --- core/src/main/java/org/geysermc/geyser/util/SoundUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ccfd658cece..fd694f53ee1 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -106,7 +106,7 @@ public static void playSound(GeyserSession session, Sound javaSound, Vector3f po String packetSound; if (!(javaSound instanceof BuiltinSound)) { // Identifier needs trimmed probably. - packetSound = translatePlaySound(javaSound.getName()); + packetSound = trim(javaSound.getName()); } else { packetSound = javaSound.getName(); } From 03390b99e8240dc96a12414a016812b5e34de4dd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:18:49 -0500 Subject: [PATCH 357/358] Fix black cats not appearing correctly --- .../entity/type/living/animal/tameable/CatEntity.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 f700f69516e..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 @@ -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); From 6485200c1ffd47a69bd041290cb75668f4269394 Mon Sep 17 00:00:00 2001 From: David Choo <4722249+davchoo@users.noreply.github.com> Date: Fri, 23 Dec 2022 19:26:37 -0500 Subject: [PATCH 358/358] Fix visual glitch with blocks attached to extending pistons in 1.19.50 (#3475) --- .../level/block/entity/PistonBlockEntity.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 28e30d6bed8..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 @@ -42,6 +42,7 @@ 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; @@ -621,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); @@ -739,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()); @@ -762,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()); @@ -783,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())