diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f4a0e21ff..036d838ef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,9 +55,9 @@ body: required: true - type: input attributes: - label: "Minecraft: Bedrock Edition Version" - description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." - placeholder: "For example: 1.16.201" + label: "Minecraft: Bedrock Edition Device/Version" + description: "What version of Minecraft: Bedrock Edition are you using, and what device(s) does the bug occur on? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." + placeholder: "For example: 1.16.201, Nintendo Switch" - type: textarea attributes: label: Additional Context diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 000000000..598cab46a --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,36 @@ +name: SonarCloud +on: + push: + branches: + - master +jobs: + build: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + submodules: true + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=GeyserMC_Geyser \ No newline at end of file diff --git a/.gitignore b/.gitignore index 85f8a6e9e..f1baa3abb 100644 --- a/.gitignore +++ b/.gitignore @@ -239,8 +239,10 @@ nbdist/ run/ config.yml logs/ -public-key.pem +key.pem locales/ /cache/ /packs/ -/dump.json \ No newline at end of file +/dump.json +/saved-refresh-tokens.json +/languages/ \ No newline at end of file diff --git a/README.md b/README.md index d4b375a5c..3e247f4b5 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,16 @@ 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.30 - 1.17.41 + 1.18.0 - 1.18.2 and Minecraft Java 1.18/1.18.1. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.31 and Minecraft Java 1.18.2. ## Setting Up -Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. +Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. [![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4) ## Links: - Website: https://geysermc.org -- Docs: https://github.com/GeyserMC/Geyser/wiki +- Docs: https://wiki.geysermc.org/geyser/ - Download: https://ci.geysermc.org - Discord: https://discord.gg/geysermc - Donate: https://opencollective.com/geysermc @@ -39,7 +39,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set - Structure block UI ## What can't be fixed -There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page. +There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page. ## Compiling 1. Clone the repo to your computer diff --git a/ap/pom.xml b/ap/pom.xml index e14829ab1..0644044a1 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT ap - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index c584885ce..1d051eaa3 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 diff --git a/api/base/src/main/java/org/geysermc/api/session/Connection.java b/api/base/src/main/java/org/geysermc/api/session/Connection.java index ccf3f7122..3e997912b 100644 --- a/api/base/src/main/java/org/geysermc/api/session/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/session/Connection.java @@ -26,6 +26,7 @@ package org.geysermc.api.session; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; import java.util.UUID; @@ -55,5 +56,13 @@ public interface Connection { */ String xuid(); - + /** + * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are + * currently playing on. + * + * @param address The address of the server + * @param port The port of the server + * @return true if the transfer was a success + */ + boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); } diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 7cee80208..2a933a2e0 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index d79922690..5d078fba5 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 73cea9ec4..b2c661447 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index e4710e085..7d6ac8f98 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 7aca462c9..e9e7687f4 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-spigot @@ -19,13 +19,18 @@ viaversion-repo https://repo.viaversion.com + + + minecraft-repo + https://libraries.minecraft.net/ + org.geysermc core - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -34,6 +39,12 @@ 1.18.1-R0.1-SNAPSHOT provided + + io.papermc.paper + paper-mojangapi + 1.18.1-R0.1-SNAPSHOT + provided + com.viaversion viaversion @@ -43,7 +54,13 @@ org.geysermc.geyser.adapters spigot-all - 1.3-SNAPSHOT + 1.4-SNAPSHOT + + + me.lucko + commodore + 1.13 + compile @@ -70,7 +87,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package @@ -95,6 +112,10 @@ org.objectweb.asm org.geysermc.geyser.platform.spigot.shaded.asm + + me.lucko.commodore + org.geysermc.geyser.platform.spigot.shaded.commodore + @@ -118,6 +139,7 @@ io.netty:netty-codec-dns:* io.netty:netty-resolver-dns:* io.netty:netty-resolver-dns-native-macos:* + com.mojang:* 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 bdf28a203..4ece501c4 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 @@ -29,40 +29,51 @@ import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.command.CommandManager; -import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; -import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.Constants; -import org.geysermc.geyser.util.FileUtils; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; -import org.geysermc.geyser.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.*; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; import java.io.File; import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { + /** + * Determines if the plugin has been ran once before, including before /geyser reload. + */ + private static boolean INITIALIZED = false; + private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotInjector geyserInjector; @@ -230,20 +241,42 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + PluginCommand pluginCommand = this.getCommand("geyser"); + pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); - if (isPre1_12) { - // Register events needed to send all recipes to the client - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(geyser), this); + if (!INITIALIZED) { + // Register permissions so they appear in, for example, LuckPerms' UI + // Re-registering permissions throws an error + for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { + GeyserCommand command = entry.getValue(); + if (command.getAliases().contains(entry.getKey())) { + // Don't register aliases + continue; + } + + Bukkit.getPluginManager().addPermission(new Permission(command.getPermission(), + GeyserLocale.getLocaleStringLog(command.getDescription()), + command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); + } + + // 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); } - this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser)); + boolean brigadierSupported = CommodoreProvider.isSupported(); + geyserLogger.debug("Brigadier supported? " + brigadierSupported); + if (brigadierSupported) { + GeyserBrigadierSupport.loadBrigadier(this, pluginCommand); + } // Check to ensure the current setup can support the protocol version Geyser uses GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); + + INITIALIZED = true; } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java new file mode 100644 index 000000000..61900174c --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.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.platform.spigot.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.lucko.commodore.Commodore; +import me.lucko.commodore.CommodoreProvider; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; + +/** + * Needs to be a separate class so pre-1.13 loads correctly. + */ +public final class GeyserBrigadierSupport { + + public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) { + // Enable command completions if supported + // This is beneficial because this is sent over the network and Bedrock can see it + Commodore commodore = CommodoreProvider.getCommodore(plugin); + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); + for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) { + builder.then(LiteralArgumentBuilder.literal(command)); + } + commodore.register(pluginCommand, builder); + + try { + Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin); + plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); + } catch (ClassNotFoundException e) { + plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener."); + } + } + + private GeyserBrigadierSupport() { + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java new file mode 100644 index 000000000..00c1ba58d --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.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.platform.spigot.command; + +import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; +import com.mojang.brigadier.tree.CommandNode; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommand; + +import java.net.InetSocketAddress; +import java.util.Iterator; +import java.util.Map; + +public final class GeyserPaperCommandListener implements Listener { + + @EventHandler + @SuppressWarnings("deprecation") // Used to indicate an unstable event + public void onCommandSend(AsyncPlayerSendCommandsEvent event) { + // Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2 + // event.hasFiredAsync is never true + if (event.isAsynchronous()) { + CommandNode geyserBrigadier = event.getCommandNode().getChild("geyser"); + if (geyserBrigadier != null) { + Player player = event.getPlayer(); + boolean isJavaPlayer = isProbablyJavaPlayer(player); + Map commands = GeyserImpl.getInstance().getCommandManager().getCommands(); + Iterator> it = geyserBrigadier.getChildren().iterator(); + + while (it.hasNext()) { + CommandNode subnode = it.next(); + GeyserCommand command = commands.get(subnode.getName()); + if (command != null) { + if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.getPermission())) { + // Remove this from the node as we don't have permission to use it + it.remove(); + } + } + } + } + } + } + + /** + * This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that + * chance, though. + */ + private boolean isProbablyJavaPlayer(Player player) { + if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) { + // For sure this is a Bedrock player + return false; + } + + if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) { + InetSocketAddress address = player.getAddress(); + if (address != null) { + return address.getPort() != 0; + } + } + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java index 103390ab8..6107d5b47 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 @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.spigot.command; import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.geysermc.geyser.GeyserImpl; @@ -35,16 +36,24 @@ import java.lang.reflect.Field; public class GeyserSpigotCommandManager extends CommandManager { - private static CommandMap COMMAND_MAP; + private static final CommandMap COMMAND_MAP; static { + CommandMap commandMap = null; try { - Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - cmdMapField.setAccessible(true); - COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); + // Paper-only + Server.class.getMethod("getCommandMap"); + commandMap = Bukkit.getServer().getCommandMap(); + } catch (NoSuchMethodException e) { + try { + Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + cmdMapField.setAccessible(true); + commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } } + COMMAND_MAP = commandMap; } public GeyserSpigotCommandManager(GeyserImpl geyser) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java deleted file mode 100644 index 78a64e47b..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ /dev/null @@ -1,203 +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; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; -import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.api.data.MappingData; -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.util.Pair; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.ShapedRecipe; -import org.bukkit.inventory.ShapelessRecipe; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.util.InventoryUtils; - -import java.util.*; - -/** - * Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server. - * Requires ViaVersion. - */ -public class GeyserSpigot1_11CraftingListener implements Listener { - - private final GeyserImpl geyser; - /** - * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13. - */ - 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_11CraftingListener(GeyserImpl geyser) { - this.geyser = geyser; - this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), - ProtocolVersion.v1_13.getVersion()); - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - GeyserSession session = null; - for (GeyserSession otherSession : geyser.getSessionManager().getSessions().values()) { - if (otherSession.name().equals(event.getPlayer().getName())) { - session = otherSession; - break; - } - } - if (session == null) { - return; - } - - sendServerRecipes(session); - } - - public void sendServerRecipes(GeyserSession session) { - int netId = InventoryUtils.LAST_RECIPE_NET_ID; - - CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); - craftingDataPacket.setCleanRecipes(true); - - Iterator recipeIterator = Bukkit.getServer().recipeIterator(); - while (recipeIterator.hasNext()) { - Recipe recipe = recipeIterator.next(); - - Pair outputs = translateToBedrock(session, recipe.getResult()); - ItemStack javaOutput = outputs.getKey(); - ItemData output = outputs.getValue(); - if (output == null || output.getId() == 0) continue; // If items make air we don't want that - - boolean isNotAllAir = false; // Check for all-air recipes - if (recipe instanceof ShapedRecipe shapedRecipe) { - int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length(); - Ingredient[] ingredients = new Ingredient[size]; - ItemData[] input = new ItemData[size]; - for (int i = 0; i < input.length; i++) { - // Index is converting char to integer, adding i then converting back to char based on ASCII code - Pair result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i))); - ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); - input[i] = result.getValue(); - isNotAllAir |= input[i].getId() != 0; - } - - if (!isNotAllAir) continue; - UUID uuid = UUID.randomUUID(); - // Add recipe to our internal cache - ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, - "", ingredients, javaOutput); - session.getCraftingRecipes().put(netId, - new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); - - // Add recipe for Bedrock - craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), - shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input), - Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); - } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { - Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()]; - ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()]; - - for (int i = 0; i < input.length; i++) { - Pair result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i)); - ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); - input[i] = result.getValue(); - isNotAllAir |= input[i].getId() != 0; - } - - if (!isNotAllAir) continue; - UUID uuid = UUID.randomUUID(); - // Add recipe to our internal cache - ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput); - session.getCraftingRecipes().put(netId, - new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data)); - - // Add recipe for Bedrock - craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), - Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); - } - } - - session.sendUpstreamPacket(craftingDataPacket); - } - - @SuppressWarnings("deprecation") - private Pair translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) { - if (itemStack != null && itemStack.getData() != null) { - if (itemStack.getType().getId() == 0) { - return new Pair<>(null, ItemData.AIR); - } - - int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF); - - if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12 - legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF); - } - - // old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on - int itemId; - if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) { - itemId = mappingData1_12to1_13.getNewItemId(legacyId); - } else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) { - itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0)); - } else { - // No ID found, just send back air - return new Pair<>(null, ItemData.AIR); - } - - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); - if (mappingData != null) { - itemId = mappingData.getNewItemId(itemId); - } - } - - ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount()); - ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack); - return new Pair<>(mcItemStack, finalData); - } - - // Empty slot, most likely - return new Pair<>(null, ItemData.AIR); - } - -} diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 18773402e..aa2747979 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -9,34 +9,3 @@ commands: geyser: description: The main command for Geyser. usage: /geyser -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 diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 0587e811f..8eba4d73d 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index cb6b01b6a..8ee94b793 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -47,17 +47,17 @@ org.jline jline-terminal - 3.20.0 + 3.21.0 org.jline jline-terminal-jna - 3.20.0 + 3.21.0 org.jline jline-reader - 3.20.0 + 3.21.0 org.apache.logging.log4j @@ -93,7 +93,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 com.github.edwgiz @@ -132,7 +132,6 @@ implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer"> - ${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 43ab4b3fe..7c49585d5 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 @@ -275,6 +275,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { return Paths.get(System.getProperty("user.dir")); } + @Override + public Path getSavedUserLoginsFolder() { + // Return the location of the config + return new File(configFilename).getAbsoluteFile().getParentFile().toPath(); + } + @Override public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 87fa95637..36882a19e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/common/pom.xml b/common/pom.xml index 6c204aa9b..0786f3f4d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT common @@ -20,12 +20,12 @@ org.geysermc.cumulus cumulus - 1.1-SNAPSHOT + 1.0-SNAPSHOT com.google.code.gson gson - 2.8.6 + 2.8.9 \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java new file mode 100644 index 000000000..37c5d4015 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.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.floodgate.pluginmessage; + +import com.google.common.base.Charsets; + +public final class PluginMessageChannels { + public static final String SKIN = "floodgate:skin"; + public static final String FORM = "floodgate:form"; + public static final String TRANSFER = "floodgate:transfer"; + + private static final byte[] FLOODGATE_REGISTER_DATA = String.join("\0", SKIN, FORM, TRANSFER).getBytes(Charsets.UTF_8); + + /** + * Get the prebuilt register data as a byte array + * + * @return the register data of the Floodgate channels + */ + public static byte[] getFloodgateRegisterData() { + return FLOODGATE_REGISTER_DATA; + } +} diff --git a/core/pom.xml b/core/pom.xml index 121b58548..8af2aa907 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,14 +6,14 @@ org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT core 4.9.3 8.5.2 - 2.12.4 + 2.13.2 4.1.66.Final @@ -21,19 +21,19 @@ org.geysermc ap - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT provided org.geysermc geyser-api - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile org.geysermc common - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT compile @@ -52,7 +52,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson.version}.1 compile @@ -120,8 +120,8 @@ com.github.CloudburstMC.Protocol - bedrock-v475 - c22aa595 + bedrock-v503 + 297567d compile @@ -147,23 +147,23 @@ - com.github.RednedEpic + com.github.GeyserMC MCAuthLib - 6c99331 + d9d773e compile com.github.GeyserMC MCProtocolLib - 6a23a780 + 0771504 compile - com.github.steveice10 + com.github.GeyserMC packetlib - com.github.steveice10 + com.github.GeyserMC mcauthlib 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 85bfd583d..890290a01 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 @@ -100,7 +100,7 @@ public class GeyserSession { } public void login() { - this.handle.login(); + throw new UnsupportedOperationException(); } public void authenticate(String username) { @@ -120,7 +120,7 @@ public class GeyserSession { } public void close() { - this.handle.close(); + throw new UnsupportedOperationException(); } public void executeInEventLoop(Runnable runnable) { diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 49f8fa676..23fb76d16 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -37,6 +37,8 @@ public final class Constants { public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"; + static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json"; + static { URI wsUri = null; try { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index 54ca36787..d40060310 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -97,6 +97,13 @@ public interface GeyserBootstrap { */ Path getConfigFolder(); + /** + * @return the folder where user tokens are saved. This should always point to the location of the config. + */ + default Path getSavedUserLoginsFolder() { + return getConfigFolder(); + } + /** * Information used for the bootstrap section of the debug dump * diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index f63e222cc..f3ebfa4a3 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -26,6 +26,7 @@ package org.geysermc.geyser; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.steveice10.packetlib.tcp.TcpSession; @@ -37,6 +38,7 @@ import io.netty.channel.kqueue.KQueue; import io.netty.util.NettyRuntime; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.internal.SystemPropertyUtil; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; @@ -59,9 +61,11 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; @@ -70,6 +74,9 @@ import org.geysermc.geyser.util.*; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -77,6 +84,7 @@ import java.net.UnknownHostException; import java.security.Key; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; @@ -124,6 +132,10 @@ public class GeyserImpl implements GeyserApi { private Metrics metrics; + private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; + @Getter(AccessLevel.NONE) + private Map savedRefreshTokens; + private static GeyserImpl instance; private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { @@ -195,6 +207,8 @@ public class GeyserImpl implements GeyserApi { ScoreboardUpdater.init(); + SkinProvider.registerCacheImageTask(this); + ResourcePack.loadPacks(); if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { @@ -265,6 +279,8 @@ public class GeyserImpl implements GeyserApi { logger.debug("Not getting git properties for the news handler as we are in a development environment."); } + pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); + this.newsHandler = new NewsHandler(branch, buildNumber); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); @@ -317,7 +333,7 @@ public class GeyserImpl implements GeyserApi { 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())); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().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)); @@ -401,6 +417,47 @@ public class GeyserImpl implements GeyserApi { metrics = null; } + if (config.getRemote().getAuthType() == 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: " + + "https://wiki.geysermc.org/geyser/understanding-the-config/"); + } + + // May be written/read to on multiple threads from each GeyserSession as well as writing the config + savedRefreshTokens = new ConcurrentHashMap<>(); + + File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + if (tokensFile.exists()) { + TypeReference> type = new TypeReference<>() { }; + + Map refreshTokenFile = null; + try { + refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type); + } catch (IOException e) { + logger.error("Cannot load saved user tokens!", e); + } + if (refreshTokenFile != null) { + List validUsers = config.getSavedUserLogins(); + boolean doWrite = false; + for (Map.Entry entry : refreshTokenFile.entrySet()) { + String user = entry.getKey(); + if (!validUsers.contains(user)) { + // Perform a write to this file to purge the now-unused name + doWrite = true; + continue; + } + savedRefreshTokens.put(user, entry.getValue()); + } + if (doWrite) { + scheduleRefreshTokensWrite(); + } + } + } + } else { + savedRefreshTokens = null; + } + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); } @@ -508,6 +565,39 @@ public class GeyserImpl implements GeyserApi { return bootstrap.getWorldManager(); } + @Nullable + public String refreshTokenFor(@NonNull String bedrockName) { + return savedRefreshTokens.get(bedrockName); + } + + public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) { + if (!getConfig().getSavedUserLogins().contains(bedrockName)) { + // Do not save this login + return; + } + + // We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken + // refreshes the token for us + if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) { + scheduleRefreshTokensWrite(); + } + } + + private void scheduleRefreshTokensWrite() { + scheduledThread.execute(() -> { + // Ensure all writes are handled on the same thread + File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + TypeReference> type = new TypeReference<>() { }; + try (FileWriter writer = new FileWriter(savedTokens)) { + JSON_MAPPER.writerFor(type) + .withDefaultPrettyPrinter() + .writeValue(writer, savedRefreshTokens); + } catch (IOException e) { + getLogger().error("Unable to write saved refresh tokens!", e); + } + }); + } + public static GeyserImpl getInstance() { return instance; } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index a61c5db25..b47801cb5 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -25,6 +25,8 @@ package org.geysermc.geyser; +import javax.annotation.Nullable; + public interface GeyserLogger { /** @@ -78,6 +80,15 @@ public interface GeyserLogger { */ void debug(String message); + /** + * Logs an object to console if debug mode is enabled + * + * @param object the object to log + */ + default void debug(@Nullable Object object) { + debug(String.valueOf(object)); + } + /** * Sets if the logger should print debug messages * 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 20451b5e8..a22c69c04 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -86,4 +86,13 @@ public abstract class GeyserCommand { public boolean isBedrockOnly() { return false; } + + /** + * Used for permission defaults on server implementations. + * + * @return if this command is designated to be used only by server operators. + */ + public boolean isSuggestedOpOnly() { + return false; + } } \ No newline at end of file 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 bd98d2b31..0bac381ba 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 @@ -145,4 +145,9 @@ public class DumpCommand extends GeyserCommand { public List getSubCommands() { return Arrays.asList("offline", "full", "logs"); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } 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 f1004c3fb..0a4cfa023 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 @@ -51,4 +51,9 @@ public class ListCommand extends GeyserCommand { sender.sendMessage(message); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } 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 b6a728382..e970e5d3d 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 @@ -54,4 +54,9 @@ public class ReloadCommand extends GeyserCommand { geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); geyser.reload(); } + + @Override + public boolean isSuggestedOpOnly() { + return 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 903e3bf4b..9c7bd8140 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 @@ -54,4 +54,9 @@ public class StopCommand extends GeyserCommand { geyser.getBootstrap().onDisable(); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 6ec816b12..f4f62892a 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 @@ -100,4 +100,9 @@ public class VersionCommand extends GeyserCommand { } } } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } 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 06d6bdbc5..1f188cf40 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -44,6 +44,9 @@ public interface GeyserConfiguration { IRemoteConfiguration getRemote(); + List getSavedUserLogins(); + + @Deprecated Map getUserAuths(); boolean isCommandSuggestions(); @@ -78,6 +81,8 @@ public interface GeyserConfiguration { boolean isDisableBedrockScaffolding(); + boolean isAlwaysQuickChangeArmor(); + EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); String getDefaultLocale(); @@ -96,8 +101,14 @@ public interface GeyserConfiguration { boolean isAllowCustomSkulls(); + int getMaxVisibleCustomSkulls(); + + int getCustomSkullRenderDistance(); + IMetricsInfo getMetrics(); + int getPendingAuthenticationTimeout(); + interface IBedrockConfiguration { String getAddress(); 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 825edf43e..30a947e53 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -35,9 +35,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.AsteriskSerializer; -import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; @@ -62,6 +62,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private BedrockConfiguration bedrock = new BedrockConfiguration(); private RemoteConfiguration remote = new RemoteConfiguration(); + @JsonProperty("saved-user-logins") + private List savedUserLogins = Collections.emptyList(); + @JsonProperty("floodgate-key-file") private String floodgateKeyFile = "key.pem"; @@ -108,6 +111,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("disable-bedrock-scaffolding") private boolean disableBedrockScaffolding = false; + @JsonProperty("always-quick-change-armor") + private boolean alwaysQuickChangeArmor = false; + @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) @JsonProperty("emote-offhand-workaround") private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; @@ -124,6 +130,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("allow-custom-skulls") private boolean allowCustomSkulls = true; + @JsonProperty("max-visible-custom-skulls") + private int maxVisibleCustomSkulls = 128; + + @JsonProperty("custom-skull-render-distance") + private int customSkullRenderDistance = 32; + @JsonProperty("add-non-bedrock-items") private boolean addNonBedrockItems = true; @@ -138,6 +150,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private MetricsInfo metrics = new MetricsInfo(); + @JsonProperty("pending-authentication-timeout") + private int pendingAuthenticationTimeout = 120; + @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @@ -231,8 +246,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; + @JsonDeserialize(using = MetricsIdDeserializer.class) @JsonProperty("uuid") private String uniqueId = UUID.randomUUID().toString(); + + private static class MetricsIdDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String uuid = p.getValueAsString(); + if ("generateduuid".equals(uuid)) { + // Compensate for configs not copied from the jar + return UUID.randomUUID().toString(); + } + return uuid; + } + } } @JsonProperty("scoreboard-packet-threshold") 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 2734c7443..ca902ea5c 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -54,10 +54,7 @@ import java.net.InetSocketAddress; 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 @@ -67,6 +64,8 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private final int cpuCount; + private final Locale systemLocale; + private final String systemEncoding; private Properties gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; @@ -81,6 +80,8 @@ public class DumpInfo { this.versionInfo = new VersionInfo(); this.cpuCount = Runtime.getRuntime().availableProcessors(); + this.systemLocale = Locale.getDefault(); + this.systemEncoding = System.getProperty("file.encoding"); try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { this.gitInfo = new Properties(); 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 9e4124cdc..1de571c94 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -65,9 +65,9 @@ public final class EntityDefinitions { public static final EntityDefinition CHICKEN; public static final EntityDefinition COD; public static final EntityDefinition COMMAND_BLOCK_MINECART; - public static final EntityDefinition COW; + public static final EntityDefinition COW; public static final EntityDefinition CREEPER; - public static final EntityDefinition DOLPHIN; + public static final EntityDefinition DOLPHIN; public static final EntityDefinition DONKEY; public static final EntityDefinition DRAGON_FIREBALL; public static final EntityDefinition DROWNED; @@ -132,7 +132,7 @@ public final class EntityDefinitions { public static final EntityDefinition SHULKER_BULLET; public static final EntityDefinition SILVERFISH; public static final EntityDefinition SKELETON; - public static final EntityDefinition SKELETON_HORSE; + public static final EntityDefinition SKELETON_HORSE; public static final EntityDefinition SLIME; public static final EntityDefinition SMALL_FIREBALL; public static final EntityDefinition SNOWBALL; @@ -160,7 +160,7 @@ public final class EntityDefinitions { public static final EntityDefinition WOLF; public static final EntityDefinition ZOGLIN; public static final EntityDefinition ZOMBIE; - public static final EntityDefinition ZOMBIE_HORSE; + public static final EntityDefinition ZOMBIE_HORSE; public static final EntityDefinition ZOMBIE_VILLAGER; public static final EntityDefinition ZOMBIFIED_PIGLIN; @@ -459,7 +459,7 @@ public final class EntityDefinitions { .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) .addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited) .build(); - DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase) + DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase) .type(EntityType.DOLPHIN) .height(0.6f).width(0.9f) //TODO check @@ -723,7 +723,7 @@ public final class EntityDefinitions { .type(EntityType.CHICKEN) .height(0.7f).width(0.4f) .build(); - COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase) + COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase) .type(EntityType.COW) .height(1.4f).width(0.9f) .build(); @@ -745,14 +745,14 @@ public final class EntityDefinitions { .height(1.3f).width(0.9f) .addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer) .build(); - MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class + MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) .type(EntityType.MOOSHROOM) .height(1.4f).width(0.9f) - .addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0)) + .addTranslator(MetadataType.STRING, MooshroomEntity::setVariant) .build(); OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase) .type(EntityType.OCELOT) - .height(0.35f).width(0.3f) + .height(0.7f).width(0.6f) .addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) .build(); PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase) @@ -783,7 +783,7 @@ public final class EntityDefinitions { .build(); SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase) .type(EntityType.SHEEP) - .heightAndWidth(0.9f) + .height(1.3f).width(0.9f) .addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags) .build(); STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase) @@ -832,11 +832,11 @@ public final class EntityDefinitions { .height(1.6f).width(1.3965f) .addTranslator(MetadataType.INT, HorseEntity::setHorseVariant) .build(); - SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase) .type(EntityType.SKELETON_HORSE) .height(1.6f).width(1.3965f) .build(); - ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + ZOMBIE_HORSE = EntityDefinition.inherited(ZombieHorseEntity::new, abstractHorseEntityBase) .type(EntityType.ZOMBIE_HORSE) .height(1.6f).width(1.3965f) .build(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java index caa373549..f0095d26a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java @@ -34,7 +34,7 @@ import java.util.Map; /** * A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock. */ -public class GeyserDirtyMetadata { +public final class GeyserDirtyMetadata { private final Map metadata = new Object2ObjectLinkedOpenHashMap<>(); public void put(EntityData entityData, Object value) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java b/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java deleted file mode 100644 index 0bc91cfcd..000000000 --- a/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java +++ /dev/null @@ -1,293 +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.github.steveice10.mc.protocol.data.game.entity.type.EntityType; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import lombok.Getter; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.MobEntity; -import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; -import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.registry.type.ItemMapping; - -import java.util.EnumSet; -import java.util.Set; - -public class InteractiveTagManager { - /** - * All entity types that can be leashed on Java Edition - */ - private static final Set LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, - EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN, - EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, - EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, - EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER, - EntityType.WOLF, EntityType.ZOGLIN); - - private static final Set SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, - EntityType.ZOMBIE_HORSE, EntityType.MULE); - - /** - * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") - * - * @param session the Bedrock client session - * @param interactEntity the entity that the client is currently facing. - */ - public static void updateTag(GeyserSession session, Entity interactEntity) { - ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session); - String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", ""); - EntityType entityType = interactEntity.getDefinition().entityType(); - if (entityType == null) { - // Likely a technical entity; we don't need to worry about this - return; - } - - InteractiveTag interactiveTag = InteractiveTag.NONE; - - if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Unleash the entity - interactiveTag = InteractiveTag.REMOVE_LEASH; - } else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) && - ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) || - entityType == EntityType.PIG || entityType == EntityType.STRIDER)) { - // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) - interactiveTag = InteractiveTag.SADDLE; - } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null && - session.getPlayerInventory().getItemInHand().getNbt().contains("display")) { - // Holding a named name tag - interactiveTag = InteractiveTag.NAME; - } else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead") - && LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) { - // Holding a leash and the mob is leashable for sure - // (Plugins can change this behavior so that's something to look into in the far far future) - interactiveTag = InteractiveTag.LEASH; - } else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) { - // This animal can be fed - interactiveTag = InteractiveTag.FEED; - } else { - switch (entityType) { - case BOAT: - if (interactEntity.getPassengers().size() < 2) { - interactiveTag = InteractiveTag.BOARD_BOAT; - } - break; - case CAT: - if (interactEntity.getFlag(EntityFlag.TAMED) && - ((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - break; - } - break; - case MOOSHROOM: - // Shear the mooshroom - if (javaIdentifierStripped.equals("shears")) { - interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; - break; - } - // Bowls are acceptable here - else if (javaIdentifierStripped.equals("bowl")) { - interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; - break; - } - // Fall down to COW as this works on mooshrooms - case COW: - if (javaIdentifierStripped.equals("bucket")) { - // Milk the cow - interactiveTag = InteractiveTag.MILK; - } - break; - case CREEPER: - if (javaIdentifierStripped.equals("flint_and_steel")) { - // Today I learned that you can ignite a creeper with flint and steel! Huh. - interactiveTag = InteractiveTag.IGNITE_CREEPER; - } - break; - case DONKEY: - case LLAMA: - case MULE: - if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED) - && javaIdentifierStripped.equals("chest")) { - // Can attach a chest - interactiveTag = InteractiveTag.ATTACH_CHEST; - break; - } - // Intentional fall-through - case HORSE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - boolean tamed = interactEntity.getFlag(EntityFlag.TAMED); - if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) { - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - } - if (!interactEntity.getFlag(EntityFlag.BABY)) { - // Can't ride a baby - if (tamed) { - interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (mapping.getJavaId() == 0) { - // Can't hide an untamed entity without having your hand empty - interactiveTag = InteractiveTag.MOUNT; - } - } - break; - case MINECART: - if (interactEntity.getPassengers().isEmpty()) { - interactiveTag = InteractiveTag.RIDE_MINECART; - } - break; - case CHEST_MINECART: - case COMMAND_BLOCK_MINECART: - case HOPPER_MINECART: - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - case PIG: - if (interactEntity.getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.MOUNT; - } - break; - case PIGLIN: - if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { - interactiveTag = InteractiveTag.BARTER; - } - break; - case SHEEP: - if (!interactEntity.getFlag(EntityFlag.SHEARED)) { - if (javaIdentifierStripped.equals("shears")) { - // Shear the sheep - interactiveTag = InteractiveTag.SHEAR; - } else if (javaIdentifierStripped.contains("_dye")) { - // Dye the sheep - interactiveTag = InteractiveTag.DYE; - } - } - break; - case STRIDER: - if (interactEntity.getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.RIDE_STRIDER; - } - break; - case VILLAGER: - VillagerEntity villager = (VillagerEntity) interactEntity; - if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby - interactiveTag = InteractiveTag.TRADE; - } - break; - case WANDERING_TRADER: - interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. - break; - case WOLF: - if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) { - // Bone and untamed - can tame - interactiveTag = InteractiveTag.TAME; - } else if (interactEntity.getFlag(EntityFlag.TAMED) && - ((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - } - break; - case ZOMBIE_VILLAGER: - // We can't guarantee the existence of the weakness effect so we just always show it. - if (javaIdentifierStripped.equals("golden_apple")) { - interactiveTag = InteractiveTag.CURE; - } - break; - default: - break; - } - } - session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); - session.getPlayerEntity().updateBedrockMetadata(); - } - - /** - * All interactive tags in enum form. For potential API usage. - */ - public enum InteractiveTag { - NONE((Void) null), - IGNITE_CREEPER("creeper"), - EDIT, - LEAVE_BOAT("exit.boat"), - FEED, - FISH("fishing"), - MILK, - MOOSHROOM_SHEAR("mooshear"), - MOOSHROOM_MILK_STEW("moostew"), - BOARD_BOAT("ride.boat"), - RIDE_MINECART("ride.minecart"), - RIDE_HORSE("ride.horse"), - RIDE_STRIDER("ride.strider"), - SHEAR, - SIT, - STAND, - TALK, - TAME, - DYE, - CURE, - OPEN_CONTAINER("opencontainer"), - CREATE_MAP("createMap"), - TAKE_PICTURE("takepicture"), - SADDLE, - MOUNT, - BOOST, - WRITE, - LEASH, - REMOVE_LEASH("unleash"), - NAME, - ATTACH_CHEST("attachchest"), - TRADE, - POSE_ARMOR_STAND("armorstand.pose"), - EQUIP_ARMOR_STAND("armorstand.equip"), - READ, - WAKE_VILLAGER("wakevillager"), - BARTER; - - /** - * The full string that should be passed on to the client. - */ - @Getter - private final String value; - - InteractiveTag(Void isNone) { - this.value = ""; - } - - InteractiveTag(String value) { - this.value = "action.interact." + value; - } - - InteractiveTag() { - this.value = "action.interact." + name().toLowerCase(); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java index db0cfc738..963e0b70a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java @@ -70,8 +70,8 @@ public class AbstractArrowEntity extends Entity { super.setMotion(motion); double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); - this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())); - this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)); - this.headYaw = yaw; + setYaw((float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()))); + setPitch((float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed))); + setHeadYaw(getYaw()); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index ddff746d6..9fd96f46b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type; 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.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.AnimatePacket; @@ -35,6 +36,8 @@ import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -78,8 +81,8 @@ public class BoatEntity extends Entity { public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { // We don't include the rotation (y) as it causes the boat to appear sideways setPosition(position.add(0d, this.definition.offset(), 0d)); - this.yaw = yaw + 90; - this.headYaw = yaw + 90; + setYaw(yaw + 90); + setHeadYaw(yaw + 90); setOnGround(isOnGround); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); @@ -158,6 +161,27 @@ public class BoatEntity extends Entity { } } + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (session.isSneaking()) { + return InteractiveTag.NONE; + } else if (passengers.size() < 2) { + return InteractiveTag.BOARD_BOAT; + } else { + return InteractiveTag.NONE; + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (session.isSneaking()) { + return InteractionResult.PASS; + } else { + // TODO: the client also checks for "out of control" ticks + return InteractionResult.SUCCESS; + } + } + private void updateLeftPaddle(GeyserSession session, Entity rower) { if (isPaddlingLeft) { paddleTimeLeft += ROWING_SPEED; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java index 36c050d1b..251eb98a0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java @@ -25,10 +25,16 @@ package org.geysermc.geyser.entity.type; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; @@ -55,4 +61,30 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId()); dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } + + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (session.canUseCommandBlocks()) { + return InteractiveTag.OPEN_CONTAINER; + } else { + return InteractiveTag.NONE; + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (session.canUseCommandBlocks()) { + // Client-side GUI required + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.ZERO); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(geyserId); + session.sendUpstreamPacket(openPacket); + + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } } 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 adeccdd01..4dc3a437a 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 @@ -30,15 +30,14 @@ 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.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.*; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -48,6 +47,8 @@ import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.MathUtils; import java.util.Collections; @@ -203,7 +204,7 @@ public class Entity { } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround); + moveRelative(relX, relY, relZ, yaw, pitch, getHeadYaw(), isOnGround); } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { @@ -224,7 +225,7 @@ public class Entity { } public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { - moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported); + moveAbsolute(position, yaw, pitch, getHeadYaw(), isOnGround, teleported); } public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { @@ -253,7 +254,8 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(position, yaw, pitch, isOnGround, false); + // teleport will always set the headYaw to yaw + moveAbsolute(position, yaw, pitch, yaw, isOnGround, false); } /** @@ -261,7 +263,7 @@ public class Entity { * @param headYaw The new head rotation of the entity. */ public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); } /** @@ -274,7 +276,7 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround); + moveRelative(moveX, moveY, moveZ, yaw, pitch, getHeadYaw(), isOnGround); } /** @@ -435,12 +437,12 @@ public class Entity { } /** - * x = Pitch, y = HeadYaw, z = Yaw + * x = Pitch, y = Yaw, z = HeadYaw * * @return the bedrock rotation */ public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, headYaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getHeadYaw()); } /** @@ -467,12 +469,68 @@ public class Entity { } } + public boolean isAlive() { + return this.valid; + } + + /** + * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") + */ + public final void updateInteractiveTag() { + InteractiveTag tag = InteractiveTag.NONE; + for (Hand hand: EntityUtils.HANDS) { + tag = testInteraction(hand); + if (tag != InteractiveTag.NONE) { + break; + } + } + session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, tag.getValue()); + session.getPlayerEntity().updateBedrockMetadata(); + } + + /** + * Test interacting with the given hand to see if we should send a tag to the Bedrock client. + * Should usually mirror {@link #interact(Hand)} without any side effects. + */ + protected InteractiveTag testInteraction(Hand hand) { + return InteractiveTag.NONE; + } + + /** + * Simulates interacting with an entity. The code here should mirror Java Edition code to the best of its ability, + * to ensure packet parity as well as functionality parity (such as sound effect responses). + */ + public InteractionResult interact(Hand hand) { + return InteractionResult.PASS; + } + + /** + * Simulates interacting with this entity at a specific click point. As of Java Edition 1.18.1, this is only used for armor stands. + */ + public InteractionResult interactAt(Hand hand) { + return InteractionResult.PASS; + } + + /** + * Send an entity event of the specified type to the Bedrock player from this entity. + */ + public final void playEntityEvent(EntityEventType type) { + playEntityEvent(type, 0); + } + + /** + * Send an entity event of the specified type with the specified data to the Bedrock player from this entity. + */ + public final void playEntityEvent(EntityEventType type, int data) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(type); + packet.setData(data); + session.sendUpstreamPacket(packet); + } + @SuppressWarnings("unchecked") public I as(Class entityClass) { return entityClass.isInstance(this) ? (I) this : null; } - - public boolean is(Class entityClass) { - return entityClass.isInstance(this); - } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java index 744ddf4a6..135f58906 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -72,6 +72,6 @@ public class FireballEntity extends ThrowableEntity { @Override public void tick() { - moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false); + moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java index 2f5590c37..52ad82370 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -152,7 +152,7 @@ public class FishingHookEntity extends ThrowableEntity { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag); @@ -160,7 +160,7 @@ public class FishingHookEntity extends ThrowableEntity { @Override protected float getGravity() { - if (!isInWater() && !onGround) { + if (!isInWater() && !isOnGround()) { return 0.03f; } return 0; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java index 9b7c79de4..dbd9bf91f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java @@ -26,11 +26,13 @@ package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.util.InteractionResult; import java.util.UUID; @@ -42,6 +44,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { } public void setHasFuel(BooleanEntityMetadata entityMetadata) { + // Note: Java ticks this entity and gives it particles if it has fuel hasFuel = entityMetadata.getPrimitiveValue(); updateDefaultBlockMetadata(); } @@ -51,4 +54,10 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID)); dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } + + @Override + public InteractionResult interact(Hand hand) { + // Always works since you can "push" it this way + return InteractionResult.SUCCESS; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index 79ffe68ef..f36a7c732 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -76,10 +76,10 @@ public class ItemEntity extends ThrowableEntity { if (isInWater()) { return; } - if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { + if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag, 0.98f, drag); } @@ -124,7 +124,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getGravity() { - if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) { + if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) { // Gravity can change if the item is in water/lava, but // the server calculates the motion & position for us return 0.04f; @@ -134,7 +134,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getDrag() { - if (onGround) { + if (isOnGround()) { Vector3i groundBlockPos = position.toInt().down(1); int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos); return BlockStateValues.getSlipperiness(blockState) * 0.98f; 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 9139e0a99..9cfa22a1f 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 @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -37,12 +38,13 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; 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.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InventoryUtils; import java.util.UUID; @@ -85,10 +87,8 @@ public class ItemFrameEntity extends Entity { .putInt("version", session.getBlockMappings().getBlockStateVersion()); NbtMapBuilder statesBuilder = NbtMap.builder() .putInt("facing_direction", direction.ordinal()) - .putByte("item_frame_map_bit", (byte) 0); - if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) { - statesBuilder.putByte("item_frame_photo_bit", (byte) 0); - } + .putByte("item_frame_map_bit", (byte) 0) + .putByte("item_frame_photo_bit", (byte) 0); blockBuilder.put("states", statesBuilder.build()); bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build()); @@ -208,6 +208,11 @@ public class ItemFrameEntity extends Entity { changed = false; } + @Override + public InteractionResult interact(Hand hand) { + return InventoryUtils.isEmpty(heldItem) && session.getPlayerInventory().getItemInHand(hand).isEmpty() ? InteractionResult.PASS : InteractionResult.SUCCESS; + } + /** * Finds the Java entity ID of an item frame from its Bedrock position. * @param position position of item frame in Bedrock. diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java index 28fe7d5bc..4ff1dfe7c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.entity.type; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; import java.util.UUID; @@ -38,4 +40,9 @@ public class LeashKnotEntity extends Entity { super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw); } + @Override + public InteractionResult interact(Hand hand) { + // Un-leashing the knot + return InteractionResult.SUCCESS; + } } 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 bc553f56c..87b709309 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 @@ -33,6 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; @@ -48,10 +51,12 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.InteractionResult; import java.util.ArrayList; import java.util.Collections; @@ -94,13 +99,15 @@ public class LivingEntity extends Entity { public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); - // Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like - // you're "mining" with ex. a shield. + boolean isUsingItem = (xd & 0x01) == 0x01; + boolean isUsingOffhand = (xd & 0x02) == 0x02; + ItemMapping shield = session.getItemMappings().getStoredItems().shield(); - boolean isUsingShield = (getHand().getId() == shield.getBedrockId() || - getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId()); - setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield); - setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); + boolean isUsingShield = hasShield(isUsingOffhand, shield); + + setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield); + // Override the blocking + setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); // Riptide spin attack setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); @@ -137,6 +144,14 @@ public class LivingEntity extends Entity { } } + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + if (offhand) { + return offHand.getId() == shieldMapping.getBedrockId(); + } else { + return hand.getId() == shieldMapping.getBedrockId(); + } + } + @Override protected boolean isShaking() { return isMaxFrozenState; @@ -169,6 +184,36 @@ public class LivingEntity extends Entity { return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth); } + @Override + public boolean isAlive() { + return this.valid && health > 0f; + } + + @Override + public InteractionResult interact(Hand hand) { + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand); + if (itemStack.getJavaId() == session.getItemMappings().getStoredItems().nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemStack); + if (result.consumesAction()) { + return result; + } + } + + return super.interact(hand); + } + + /** + * Checks to see if a nametag interaction would go through. + */ + protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) { + CompoundTag nbt = itemStack.getNbt(); + if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) { + // The mob shall be named + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + public void updateArmor(GeyserSession session) { if (!valid) return; @@ -255,7 +300,9 @@ public class LivingEntity extends Entity { if (javaAttribute.getType() instanceof AttributeType.Builtin type) { switch (type) { case GENERIC_MAX_HEALTH -> { - this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute); + // Since 1.18.0, setting the max health to 0 or below causes the entity to die on Bedrock but not on Java + // See https://github.com/GeyserMC/Geyser/issues/2971 + this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f); newAttributes.add(createHealthAttribute()); } case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index 80fc2a62e..6f722864b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -27,10 +27,14 @@ package org.geysermc.geyser.entity.type; 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.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; @@ -62,6 +66,41 @@ public class MinecartEntity extends Entity { @Override public Vector3f getBedrockRotation() { // Note: minecart rotation on rails does not care about the actual rotation value - return Vector3f.from(0, yaw, 0); + return Vector3f.from(0, getYaw(), 0); + } + + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) { + return InteractiveTag.OPEN_CONTAINER; + } else { + if (session.isSneaking()) { + return InteractiveTag.NONE; + } else if (!passengers.isEmpty()) { + // Can't enter if someone is inside + return InteractiveTag.NONE; + } else { + // Attempt to enter + return InteractiveTag.RIDE_MINECART; + } + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) { + // Opening the UI of this minecart + return InteractionResult.SUCCESS; + } else { + if (session.isSneaking()) { + return InteractionResult.PASS; + } else if (!passengers.isEmpty()) { + // Can't enter if someone is inside + return InteractionResult.PASS; + } else { + // Attempt to enter + return InteractionResult.SUCCESS; + } + } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index 87e3be405..ad8b60bdb 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -56,7 +56,7 @@ public class ThrowableEntity extends Entity implements Tickable { */ @Override public void tick() { - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); float gravity = getGravity(); motion = motion.mul(drag).down(gravity); @@ -89,20 +89,20 @@ public class ThrowableEntity extends Entity implements Tickable { } setPosition(position); - if (this.yaw != yaw) { + if (getYaw() != yaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); moveEntityDeltaPacket.setYaw(yaw); - this.yaw = yaw; + setYaw(yaw); } - if (this.pitch != pitch) { + if (getPitch() != pitch) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); moveEntityDeltaPacket.setPitch(pitch); - this.pitch = pitch; + setPitch(pitch); } - if (this.headYaw != headYaw) { + if (getHeadYaw() != headYaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); moveEntityDeltaPacket.setHeadYaw(headYaw); - this.headYaw = headYaw; + setHeadYaw(headYaw); } if (!moveEntityDeltaPacket.getFlags().isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java index dae1c76e6..e6cd13f61 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java @@ -28,8 +28,12 @@ package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class AbstractFishEntity extends WaterEntity { @@ -42,4 +46,14 @@ public class AbstractFishEntity extends WaterEntity { setFlag(EntityFlag.CAN_CLIMB, false); setFlag(EntityFlag.HAS_GRAVITY, false); } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (EntityUtils.attemptToBucket(session, itemInHand)) { + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java index 9dc5dca07..d4c627a8e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java @@ -36,4 +36,9 @@ public class AmbientEntity extends MobEntity { public AmbientEntity(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 boolean canBeLeashed() { + return false; + } } 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 9980cd2c1..18076763e 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 @@ -28,6 +28,8 @@ package org.geysermc.geyser.entity.type.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -39,6 +41,8 @@ import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.MathUtils; import java.util.Optional; import java.util.UUID; @@ -84,8 +88,6 @@ public class ArmorStandEntity extends LivingEntity { @Override public void spawnEntity() { - this.pitch = yaw; - this.headYaw = yaw; super.spawnEntity(); } @@ -136,7 +138,7 @@ public class ArmorStandEntity extends LivingEntity { } isSmall = newIsSmall; - if (!isMarker) { + if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780 toggleSmallStatus(); } } @@ -202,9 +204,9 @@ public class ArmorStandEntity extends LivingEntity { // Indicate that rotation should be checked setFlag(EntityFlag.BRIBED, true); - int rotationX = getRotation(rotation.getPitch()); - int rotationY = getRotation(rotation.getYaw()); - int rotationZ = getRotation(rotation.getRoll()); + int rotationX = MathUtils.wrapDegreesToInt(rotation.getPitch()); + int rotationY = MathUtils.wrapDegreesToInt(rotation.getYaw()); + int rotationZ = MathUtils.wrapDegreesToInt(rotation.getRoll()); // The top bit acts like binary and determines if each rotation goes above 100 // We don't do this for the negative values out of concerns of the number being too big int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); @@ -237,6 +239,16 @@ public class ArmorStandEntity extends LivingEntity { } } + @Override + public InteractionResult interactAt(Hand hand) { + if (!isMarker && session.getPlayerInventory().getItemInHand(hand).getJavaId() != session.getItemMappings().getStoredItems().nameTag()) { + // Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client + return InteractionResult.CONSUME; + } else { + return InteractionResult.PASS; + } + } + @Override public void setHelmet(ItemData helmet) { super.setHelmet(helmet); @@ -306,7 +318,7 @@ public class ArmorStandEntity extends LivingEntity { // Create the second entity. It doesn't need to worry about the items, but it does need to worry about // the metadata as it will hold the name tag. secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, - EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw); + EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw()); secondEntity.primaryEntity = false; if (!this.positionRequiresOffset) { // Ensure the offset is applied for the 0 scale @@ -362,17 +374,6 @@ public class ArmorStandEntity extends LivingEntity { } } - private int getRotation(float rotation) { - rotation = rotation % 360f; - if (rotation < -180f) { - rotation += 360f; - } else if (rotation >= 180f) { - // 181 -> -179 - rotation = -(180 - (rotation - 180)); - } - return (int) rotation; - } - /** * If this armor stand is not a marker, set its bounding box size and scale. */ @@ -426,9 +427,14 @@ public class ArmorStandEntity extends LivingEntity { MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw)); - moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround()); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); } + + @Override + public Vector3f getBedrockRotation() { + return Vector3f.from(getYaw(), getYaw(), getYaw()); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java new file mode 100644 index 000000000..7085547f8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.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.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class DolphinEntity extends WaterEntity { + public DolphinEntity(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 boolean canBeLeashed() { + return true; + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) { + return InteractiveTag.FEED; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) { + // Feed + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java index 0acdb960f..4ab36b00e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java @@ -29,8 +29,11 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class IronGolemEntity extends GolemEntity { @@ -42,4 +45,18 @@ public class IronGolemEntity extends GolemEntity { // Required, or else the overlay is black dirtyMetadata.put(EntityData.COLOR_2, (byte) 0); } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().ironIngot()) { + if (health < maxHealth) { + // Healing the iron golem + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java index 54d652f32..8734f8bd1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java @@ -26,14 +26,21 @@ package org.geysermc.geyser.entity.type.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.item.StoredItemMappings; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class MobEntity extends LivingEntity { @@ -62,4 +69,95 @@ public class MobEntity extends LivingEntity { this.leashHolderBedrockId = bedrockId; dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId); } + + @Override + protected final InteractiveTag testInteraction(Hand hand) { + if (!isAlive()) { + // dead lol + return InteractiveTag.NONE; + } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) { + return InteractiveTag.REMOVE_LEASH; + } else { + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand); + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (itemStack.getJavaId() == storedItems.lead() && canBeLeashed()) { + // We shall leash + return InteractiveTag.LEASH; + } else if (itemStack.getJavaId() == storedItems.nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemStack); + if (result.consumesAction()) { + return InteractiveTag.NAME; + } + } + + InteractiveTag tag = testMobInteraction(itemStack); + return tag != InteractiveTag.NONE ? tag : super.testInteraction(hand); + } + } + + @Override + public final InteractionResult interact(Hand hand) { + if (!isAlive()) { + // dead lol + return InteractionResult.PASS; + } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) { + // TODO looks like the client assumes it will go through and removes the attachment itself? + return InteractionResult.SUCCESS; + } else { + GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand); + InteractionResult result = checkPriorityInteractions(itemInHand); + if (result.consumesAction()) { + return result; + } else { + InteractionResult mobResult = mobInteract(itemInHand); + return mobResult.consumesAction() ? mobResult : super.interact(hand); + } + } + } + + private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (itemInHand.getJavaId() == storedItems.lead() && canBeLeashed()) { + // We shall leash + return InteractionResult.SUCCESS; + } else if (itemInHand.getJavaId() == storedItems.nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemInHand); + if (result.consumesAction()) { + return result; + } + } else { + ItemMapping mapping = itemInHand.getMapping(session); + if (mapping.getJavaIdentifier().endsWith("_spawn_egg")) { + // Using the spawn egg on this entity to create a child + return InteractionResult.CONSUME; + } + } + + return InteractionResult.PASS; + } + + @Nonnull + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return InteractiveTag.NONE; + } + + @Nonnull + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return InteractionResult.PASS; + } + + protected boolean canBeLeashed() { + return isNotLeashed() && !isEnemy(); + } + + protected final boolean isNotLeashed() { + return leashHolderBedrockId == -1L; + } + + /** + * Returns if the entity is hostile. Used to determine if it can be leashed. + */ + protected boolean isEnemy() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java index 60e639415..26cf2d627 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java @@ -42,4 +42,9 @@ public class SlimeEntity extends MobEntity { public void setScale(IntEntityMetadata entityMetadata) { dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue()); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java index 10ddb48f4..794f71c04 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java @@ -29,8 +29,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEnti import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class SnowGolemEntity extends GolemEntity { @@ -44,4 +48,24 @@ public class SnowGolemEntity extends GolemEntity { // Handle the visibility of the pumpkin setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) { + // Shearing the snow golem + return InteractiveTag.SHEAR; + } + return InteractiveTag.NONE; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) { + // Shearing the snow golem + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index 0f860ae60..552f6a46c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -117,7 +117,12 @@ public class SquidEntity extends WaterEntity implements Tickable { @Override public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, yaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getYaw()); + } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); } private void checkInWater() { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java index 5adbd50a9..44275a7b1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java @@ -36,4 +36,9 @@ public class WaterEntity extends CreatureEntity { public WaterEntity(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 boolean canBeLeashed() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java index 2d1787932..64f41c5ad 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java @@ -26,11 +26,17 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class AnimalEntity extends AgeableEntity { @@ -39,6 +45,12 @@ public class AnimalEntity extends AgeableEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + public final boolean canEat(GeyserItemStack itemStack) { + ItemMapping mapping = itemStack.getMapping(session); + String handIdentifier = mapping.getJavaIdentifier(); + return canEat(handIdentifier.replace("minecraft:", ""), mapping); + } + /** * @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example, * wheat. @@ -48,4 +60,28 @@ public class AnimalEntity extends AgeableEntity { // This is what it defaults to. OK. return javaIdentifierStripped.equals("wheat"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (canEat(itemInHand)) { + return InteractiveTag.FEED; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (canEat(itemInHand)) { + // FEED + if (getFlag(EntityFlag.BABY)) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + return InteractionResult.SUCCESS; + } else { + return InteractionResult.CONSUME; + } + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java index ec919a5c4..aafa2b782 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -31,9 +31,13 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class AxolotlEntity extends AnimalEntity { @@ -56,11 +60,26 @@ public class AxolotlEntity extends AnimalEntity { @Override public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { - return javaIdentifierStripped.equals("tropical_fish_bucket"); + return session.getTagCache().isAxolotlTemptItem(mapping); } @Override protected int getMaxAir() { return 6000; } + + @Override + protected boolean canBeLeashed() { + return true; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (EntityUtils.attemptToBucket(session, itemInHand)) { + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java new file mode 100644 index 000000000..b5ae48b23 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.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.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class CowEntity extends AnimalEntity { + public CowEntity(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); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + return super.testMobInteraction(itemInHand); + } + + return InteractiveTag.MILK; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + return super.mobInteract(itemInHand); + } + + session.playSoundEvent(SoundEvent.MILK, position); + return InteractionResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java index 7442a5417..817b466fa 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java @@ -28,17 +28,20 @@ package org.geysermc.geyser.entity.type.living.animal; 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 lombok.Getter; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class GoatEntity extends AnimalEntity { private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f; private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f; - @Getter private boolean isScreamer; public GoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { @@ -59,4 +62,15 @@ public class GoatEntity extends AnimalEntity { super.setDimensions(pose); } } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.BABY) && itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + session.playSoundEvent(isScreamer ? SoundEvent.MILK_SCREAMER : SoundEvent.MILK, position); + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java index e96124250..362c25256 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java @@ -56,4 +56,14 @@ public class HoglinEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("crimson_fungus"); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java index e75d20f8d..c249663ac 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java @@ -25,15 +25,62 @@ package org.geysermc.geyser.entity.type.living.animal; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class MooshroomEntity extends AnimalEntity { + private boolean isBrown = false; public MooshroomEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + public void setVariant(ObjectEntityMetadata entityMetadata) { + isBrown = entityMetadata.getValue().equals("brown"); + dirtyMetadata.put(EntityData.VARIANT, isBrown ? 1 : 0); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (!isBaby()) { + if (itemInHand.getJavaId() == storedItems.bowl()) { + // Stew + return InteractiveTag.MOOSHROOM_MILK_STEW; + } else if (isAlive() && itemInHand.getJavaId() == storedItems.shears()) { + // Shear items + return InteractiveTag.MOOSHROOM_SHEAR; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + boolean isBaby = isBaby(); + if (!isBaby && itemInHand.getJavaId() == storedItems.bowl()) { + // Stew + return InteractionResult.SUCCESS; + } else if (!isBaby && isAlive() && itemInHand.getJavaId() == storedItems.shears()) { + // Shear items + return InteractionResult.SUCCESS; + } else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.getMapping(session).isHasSuspiciousStewEffect()) { + // ? + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java index ab7e9a053..4ed2bdce1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java @@ -26,10 +26,15 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class OcelotEntity extends AnimalEntity { @@ -42,4 +47,26 @@ public class OcelotEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) { + // Attempt to feed + return InteractiveTag.FEED; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) { + // Attempt to feed + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java index bfe743bc1..d607f113b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java @@ -33,14 +33,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.UUID; public class PandaEntity extends AnimalEntity { - private int mainGene; - private int hiddenGene; + private Gene mainGene = Gene.NORMAL; + private Gene hiddenGene = Gene.NORMAL; public PandaEntity(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); @@ -61,12 +66,12 @@ public class PandaEntity extends AnimalEntity { } public void setMainGene(ByteEntityMetadata entityMetadata) { - mainGene = entityMetadata.getPrimitiveValue(); + mainGene = Gene.fromId(entityMetadata.getPrimitiveValue()); updateAppearance(); } public void setHiddenGene(ByteEntityMetadata entityMetadata) { - hiddenGene = entityMetadata.getPrimitiveValue(); + hiddenGene = Gene.fromId(entityMetadata.getPrimitiveValue()); updateAppearance(); } @@ -86,23 +91,81 @@ public class PandaEntity extends AnimalEntity { return javaIdentifierStripped.equals("bamboo"); } + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (mainGene == Gene.WORRIED && session.isThunder()) { + return InteractiveTag.NONE; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (mainGene == Gene.WORRIED && session.isThunder()) { + // Huh! + return InteractionResult.PASS; + } else if (getFlag(EntityFlag.LAYING_DOWN)) { + // Stop the panda from laying down + // TODO laying up is client-side? + return InteractionResult.SUCCESS; + } else if (canEat(itemInHand)) { + if (getFlag(EntityFlag.BABY)) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + } + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + @Override + protected boolean canBeLeashed() { + return false; + } + /** * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up * when both main and hidden genes match */ private void updateAppearance() { - if (mainGene == 4 || mainGene == 5) { - // Main gene is a recessive trait + if (mainGene.isRecessive) { if (mainGene == hiddenGene) { // Main and hidden genes match; this is what the panda looks like. - dirtyMetadata.put(EntityData.VARIANT, mainGene); + dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal()); } else { // Genes have no effect on appearance - dirtyMetadata.put(EntityData.VARIANT, 0); + dirtyMetadata.put(EntityData.VARIANT, Gene.NORMAL.ordinal()); } } else { // No need to worry about hidden gene - dirtyMetadata.put(EntityData.VARIANT, mainGene); + dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal()); + } + } + + enum Gene { + NORMAL(false), + LAZY(false), + WORRIED(false), + PLAYFUL(false), + BROWN(true), + WEAK(true), + AGGRESSIVE(false); + + private static final Gene[] VALUES = values(); + + private final boolean isRecessive; + + Gene(boolean isRecessive) { + this.isRecessive = isRecessive; + } + + @Nullable + private static Gene fromId(int id) { + if (id < 0 || id >= VALUES.length) { + return NORMAL; + } + return VALUES[id]; } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java index a97193358..05f628f44 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java @@ -26,10 +26,16 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class PigEntity extends AnimalEntity { @@ -42,4 +48,37 @@ public class PigEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount + return InteractiveTag.MOUNT; + } else { + InteractiveTag superTag = super.testMobInteraction(itemInHand); + if (superTag != InteractiveTag.NONE) { + return superTag; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction() + ? InteractiveTag.SADDLE : InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount + return InteractionResult.SUCCESS; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand); + } + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java index 284b4aea4..9481944a7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java @@ -30,19 +30,69 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.geyser.util.ItemUtils; +import javax.annotation.Nonnull; import java.util.UUID; public class SheepEntity extends AnimalEntity { + private int color; public SheepEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } public void setSheepFlags(ByteEntityMetadata entityMetadata) { - byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue(); + byte xd = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); - dirtyMetadata.put(EntityData.COLOR, xd); + color = xd & 15; + dirtyMetadata.put(EntityData.COLOR, (byte) color); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) { + return InteractiveTag.SHEAR; + } else { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + int color = ItemUtils.dyeColorFor(itemInHand.getJavaId()); + if (canDye(color)) { + return InteractiveTag.DYE; + } + return InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) { + return InteractionResult.CONSUME; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + int color = ItemUtils.dyeColorFor(itemInHand.getJavaId()); + if (canDye(color)) { + // Dyeing the sheep + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + } + } + + private boolean canDye(int color) { + return color != -1 && color != this.color && !getFlag(EntityFlag.SHEARED); } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java index 27438544c..5f42b4b67 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java @@ -30,9 +30,14 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class StriderEntity extends AnimalEntity { @@ -90,4 +95,37 @@ public class StriderEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("warped_fungus"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount Strider + return InteractiveTag.RIDE_STRIDER; + } else { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction() + ? InteractiveTag.SADDLE : InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount Strider + return InteractionResult.SUCCESS; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand); + } + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index f7d987300..79a7b8f50 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -52,4 +52,9 @@ public class TurtleEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("seagrass"); } + + @Override + protected boolean canBeLeashed() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java index ef53f604f..9139495b8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -37,9 +37,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.Set; import java.util.UUID; @@ -122,4 +126,164 @@ public class AbstractHorseEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testHorseInteraction(itemInHand); + } + + @Nonnull + protected final InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) { + boolean isBaby = isBaby(); + if (!isBaby) { + if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { + return InteractiveTag.OPEN_CONTAINER; + } + + if (!passengers.isEmpty()) { + return super.testMobInteraction(itemInHand); + } + } + + if (!itemInHand.isEmpty()) { + if (canEat(itemInHand)) { + return InteractiveTag.FEED; + } + + if (testSaddle(itemInHand)) { + return InteractiveTag.SADDLE; + } + + if (!getFlag(EntityFlag.TAMED)) { + // Horse will become mad + return InteractiveTag.NONE; + } + + if (testForChest(itemInHand)) { + return InteractiveTag.ATTACH_CHEST; + } + + if (additionalTestForInventoryOpen(itemInHand) || !isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) { + // Will open the inventory to be saddled + return InteractiveTag.OPEN_CONTAINER; + } + } + + if (isBaby) { + return super.testMobInteraction(itemInHand); + } else { + return InteractiveTag.MOUNT; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return mobHorseInteract(itemInHand); + } + + @Nonnull + protected final InteractionResult mobHorseInteract(@Nonnull GeyserItemStack itemInHand) { + boolean isBaby = isBaby(); + if (!isBaby) { + if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { + // Will open the inventory + return InteractionResult.SUCCESS; + } + + if (!passengers.isEmpty()) { + return super.mobInteract(itemInHand); + } + } + + if (!itemInHand.isEmpty()) { + if (canEat(itemInHand)) { + if (isBaby) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + } + return InteractionResult.CONSUME; + } + + if (testSaddle(itemInHand)) { + return InteractionResult.SUCCESS; + } + + if (!getFlag(EntityFlag.TAMED)) { + // Horse will become mad + return InteractionResult.SUCCESS; + } + + if (testForChest(itemInHand)) { + // TODO looks like chest is also handled client side + return InteractionResult.SUCCESS; + } + + // Note: yes, this code triggers for llamas too. lol (as of Java Edition 1.18.1) + if (additionalTestForInventoryOpen(itemInHand) || (!isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle())) { + // Will open the inventory to be saddled + return InteractionResult.SUCCESS; + } + } + + if (isBaby) { + return super.mobInteract(itemInHand); + } else { + // Attempt to mount + // TODO client-set flags sitting standing? + return InteractionResult.SUCCESS; + } + } + + protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) { + return isAlive() && !getFlag(EntityFlag.BABY) && getFlag(EntityFlag.TAMED); + } + + protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) { + return false; + } + + protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) { + return itemInHand.getMapping(session).getJavaIdentifier().endsWith("_horse_armor"); + } + + /* Just a place to stuff common code for the undead variants without having duplicate code */ + + protected final InteractiveTag testUndeadHorseInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TAMED)) { + return InteractiveTag.NONE; + } else if (isBaby()) { + return testHorseInteraction(itemInHand); + } else if (session.isSneaking()) { + return InteractiveTag.OPEN_CONTAINER; + } else if (!passengers.isEmpty()) { + return testHorseInteraction(itemInHand); + } else { + if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) { + return InteractiveTag.OPEN_CONTAINER; + } + + if (testSaddle(itemInHand)) { + return InteractiveTag.SADDLE; + } + + return InteractiveTag.RIDE_HORSE; + } + } + + protected final InteractionResult undeadHorseInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TAMED)) { + return InteractionResult.PASS; + } else if (isBaby()) { + return mobHorseInteract(itemInHand); + } else if (session.isSneaking()) { + // Opens inventory + return InteractionResult.SUCCESS; + } else if (!passengers.isEmpty()) { + return mobHorseInteract(itemInHand); + } else { + // The client tests for saddle but it doesn't matter for us at this point. + return InteractionResult.SUCCESS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java index fb907829a..7d59be713 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java @@ -26,9 +26,12 @@ package org.geysermc.geyser.entity.type.living.animal.horse; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nonnull; import java.util.UUID; public class ChestedHorseEntity extends AbstractHorseEntity { @@ -41,4 +44,21 @@ public class ChestedHorseEntity extends AbstractHorseEntity { protected int getContainerBaseSize() { return 16; } + + @Override + protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) { + // Not checked here + return false; + } + + @Override + protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) { + return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().chest() && !getFlag(EntityFlag.CHESTED); + } + + @Override + protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) { + // Armor won't work on these + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java index 41ed74f5a..c2548daaf 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java new file mode 100644 index 000000000..c9f95f507 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION 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.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class SkeletonHorseEntity extends AbstractHorseEntity { + public SkeletonHorseEntity(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); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testUndeadHorseInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return undeadHorseInteract(itemInHand); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java similarity index 54% rename from core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java index b3a04e163..ddde11c5d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java @@ -23,29 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.translator.protocol.java; +package org.geysermc.geyser.entity.type.living.animal.horse; -import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; 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.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; -import java.util.Arrays; +import javax.annotation.Nonnull; +import java.util.UUID; -/** - * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) - */ -@Translator(packet = ClientboundRecipePacket.class) -public class JavaRecipeTranslator extends PacketTranslator { +public class ZombieHorseEntity extends AbstractHorseEntity { + public ZombieHorseEntity(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); + } + @Nonnull @Override - public void translate(GeyserSession session, ClientboundRecipePacket packet) { - if (packet.getAction() == UnlockRecipesAction.REMOVE) { - session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes())); - } else { - session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes())); - } + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testUndeadHorseInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return undeadHorseInteract(itemInHand); } } - 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 c38b15397..c17503606 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 @@ -32,9 +32,13 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class CatEntity extends TameableEntity { @@ -98,4 +102,28 @@ public class CatEntity extends TameableEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + boolean tamed = getFlag(EntityFlag.TAMED); + if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Toggle sitting + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } else { + return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractiveTag.NONE : InteractiveTag.FEED; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + boolean tamed = getFlag(EntityFlag.TAMED); + if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + return InteractionResult.SUCCESS; + } else { + // Attempt to feed + return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java index 23f7696d4..b7aca99e5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -26,10 +26,15 @@ package org.geysermc.geyser.entity.type.living.animal.tameable; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class ParrotEntity extends TameableEntity { @@ -40,6 +45,46 @@ public class ParrotEntity extends TameableEntity { @Override public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { - return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie"); + return false; + } + + private boolean isTameFood(String javaIdentifierStripped) { + return javaIdentifierStripped.contains("seeds"); + } + + private boolean isPoisonousFood(String javaIdentifierStripped) { + return javaIdentifierStripped.equals("cookie"); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", ""); + boolean tame = getFlag(EntityFlag.TAMED); + if (!tame && isTameFood(javaIdentifierStripped)) { + return InteractiveTag.FEED; + } else if (isPoisonousFood(javaIdentifierStripped)) { + return InteractiveTag.FEED; + } else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Sitting/standing + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", ""); + boolean tame = getFlag(EntityFlag.TAMED); + if (!tame && isTameFood(javaIdentifierStripped)) { + return InteractionResult.SUCCESS; + } else if (isPoisonousFood(javaIdentifierStripped)) { + return InteractionResult.SUCCESS; + } else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Sitting/standing + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java index 9bdb57368..33b2144e8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -61,17 +61,30 @@ public class TameableEntity extends AnimalEntity { // Note: Must be set for wolf collar color to work if (entityMetadata.getValue().isPresent()) { // Owner UUID of entity - Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get()); + UUID uuid = entityMetadata.getValue().get(); + Entity entity; + if (uuid.equals(session.getPlayerEntity().getUuid())) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getPlayerEntity(uuid); + } // Used as both a check since the player isn't in the entity cache and a normal fallback if (entity == null) { - entity = session.getPlayerEntity(); + // Set to tame, but indicate that we are not the player that owns this + ownerBedrockId = Long.MAX_VALUE; + } else { + // Translate to entity ID + ownerBedrockId = entity.getGeyserId(); } - // Translate to entity ID - ownerBedrockId = entity.getGeyserId(); } else { // Reset ownerBedrockId = 0L; } dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index 60a4a1993..8b900f071 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -32,9 +32,14 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.geyser.util.ItemUtils; +import javax.annotation.Nonnull; import java.util.Set; import java.util.UUID; @@ -90,4 +95,45 @@ public class WolfEntity extends TameableEntity { // Cannot be a baby to eat these foods return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby(); } + + @Override + protected boolean canBeLeashed() { + return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed(); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.ANGRY)) { + return InteractiveTag.NONE; + } + if (itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + return InteractiveTag.TAME; + } else { + int color = ItemUtils.dyeColorFor(itemInHand.getJavaId()); + if (color != -1) { + // If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand + if (color != this.collarColor) { + return InteractiveTag.DYE; + } + } else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (ownerBedrockId == session.getPlayerEntity().getGeyserId() || getFlag(EntityFlag.TAMED) + || itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.ANGRY)) { + // Sitting toggle or feeding; not angry + return InteractionResult.CONSUME; + } else { + return InteractionResult.PASS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java index 28a523f40..633ba707f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java @@ -26,10 +26,16 @@ package org.geysermc.geyser.entity.type.living.merchant; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class AbstractMerchantEntity extends AgeableEntity { @@ -37,4 +43,37 @@ public class AbstractMerchantEntity extends AgeableEntity { public AbstractMerchantEntity(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 boolean canBeLeashed() { + return false; + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); + if (!javaIdentifier.equals("minecraft:villager_spawn_egg") + && (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING) && ((VillagerEntity) this).isCanTradeWith())) { + // An additional check we know cannot work + if (!isBaby()) { + return InteractiveTag.TRADE; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); + if (!javaIdentifier.equals("minecraft:villager_spawn_egg") + && (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING)) + && (definition != EntityDefinitions.WANDERING_TRADER || !getFlag(EntityFlag.BABY))) { + // Trading time + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java index 0f90e4d38..866ba36fc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java @@ -33,52 +33,49 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; import java.util.Optional; import java.util.UUID; public class VillagerEntity extends AbstractMerchantEntity { - /** * A map of Java profession IDs to Bedrock IDs */ - public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap(); + private static final int[] VILLAGER_PROFESSIONS = new int[15]; /** * A map of all Java region IDs (plains, savanna...) to Bedrock */ - public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + private static final int[] VILLAGER_REGIONS = new int[7]; static { // Java villager profession IDs -> Bedrock - VILLAGER_PROFESSIONS.put(0, 0); - VILLAGER_PROFESSIONS.put(1, 8); - VILLAGER_PROFESSIONS.put(2, 11); - VILLAGER_PROFESSIONS.put(3, 6); - VILLAGER_PROFESSIONS.put(4, 7); - VILLAGER_PROFESSIONS.put(5, 1); - VILLAGER_PROFESSIONS.put(6, 2); - VILLAGER_PROFESSIONS.put(7, 4); - VILLAGER_PROFESSIONS.put(8, 12); - VILLAGER_PROFESSIONS.put(9, 5); - VILLAGER_PROFESSIONS.put(10, 13); - VILLAGER_PROFESSIONS.put(11, 14); - VILLAGER_PROFESSIONS.put(12, 3); - VILLAGER_PROFESSIONS.put(13, 10); - VILLAGER_PROFESSIONS.put(14, 9); + VILLAGER_PROFESSIONS[0] = 0; + VILLAGER_PROFESSIONS[1] = 8; + VILLAGER_PROFESSIONS[2] = 11; + VILLAGER_PROFESSIONS[3] = 6; + VILLAGER_PROFESSIONS[4] = 7; + VILLAGER_PROFESSIONS[5] = 1; + VILLAGER_PROFESSIONS[6] = 2; + VILLAGER_PROFESSIONS[7] = 4; + VILLAGER_PROFESSIONS[8] = 12; + VILLAGER_PROFESSIONS[9] = 5; + VILLAGER_PROFESSIONS[10] = 13; + VILLAGER_PROFESSIONS[11] = 14; + VILLAGER_PROFESSIONS[12] = 3; + VILLAGER_PROFESSIONS[13] = 10; + VILLAGER_PROFESSIONS[14] = 9; - VILLAGER_REGIONS.put(0, 1); - VILLAGER_REGIONS.put(1, 2); - VILLAGER_REGIONS.put(2, 0); - VILLAGER_REGIONS.put(3, 3); - VILLAGER_REGIONS.put(4, 4); - VILLAGER_REGIONS.put(5, 5); - VILLAGER_REGIONS.put(6, 6); + VILLAGER_REGIONS[0] = 1; + VILLAGER_REGIONS[1] = 2; + VILLAGER_REGIONS[2] = 0; + VILLAGER_REGIONS[3] = 3; + VILLAGER_REGIONS[4] = 4; + VILLAGER_REGIONS[5] = 5; + VILLAGER_REGIONS[6] = 6; } private Vector3i bedPosition; @@ -95,12 +92,12 @@ public class VillagerEntity extends AbstractMerchantEntity { public void setVillagerData(EntityMetadata entityMetadata) { VillagerData villagerData = entityMetadata.getValue(); // Profession - int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession()); + int profession = getBedrockProfession(villagerData.getProfession()); canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless dirtyMetadata.put(EntityData.VARIANT, profession); //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? // Region - dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); + dirtyMetadata.put(EntityData.MARK_VARIANT, getBedrockRegion(villagerData.getType())); // Trade tier - different indexing in Bedrock dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); } @@ -158,4 +155,12 @@ public class VillagerEntity extends AbstractMerchantEntity { moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); } + + public static int getBedrockProfession(int javaProfession) { + return javaProfession >= 0 && javaProfession < VILLAGER_PROFESSIONS.length ? VILLAGER_PROFESSIONS[javaProfession] : 0; + } + + public static int getBedrockRegion(int javaRegion) { + return javaRegion >= 0 && javaRegion < VILLAGER_REGIONS.length ? VILLAGER_REGIONS[javaRegion] : 0; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java index 12117d949..cf9393410 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java @@ -28,10 +28,15 @@ package org.geysermc.geyser.entity.type.living.monster; 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.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class CreeperEntity extends MonsterEntity { @@ -55,4 +60,26 @@ public class CreeperEntity extends MonsterEntity { ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) { + return InteractiveTag.IGNITE_CREEPER; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) { + // Ignite creeper + session.playSoundEvent(SoundEvent.IGNITE, position); + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index de1dab463..1d689e806 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -39,6 +39,7 @@ import org.geysermc.geyser.entity.type.living.MobEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.DimensionUtils; +import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -130,7 +131,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { for (int i = 0; i < segmentHistory.length; i++) { segmentHistory[i] = new Segment(); - segmentHistory[i].yaw = headYaw; + segmentHistory[i].yaw = getHeadYaw(); segmentHistory[i].y = position.getY(); } } @@ -150,6 +151,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable { return super.despawnEntity(); } + @Override + protected boolean isEnemy() { + return true; + } + @Override public void tick() { effectTick(); @@ -163,7 +169,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { * Updates the positions of the Ender Dragon's multiple bounding boxes */ private void updateBoundingBoxes() { - Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw); + Vector3f facingDir = Vector3f.createDirectionDeg(0, getHeadYaw()); Segment baseSegment = getSegment(5); // Used to angle the head, neck, and tail when the dragon flies up/down float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); @@ -182,7 +188,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); - Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f); + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - getHeadYaw()).mul(4.5f).up(2f); rightWing.setPosition(wingPos); leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally @@ -191,7 +197,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { float distance = (i + 1) * 2f; // Curls the tail when the dragon turns Segment targetSegment = getSegment(12 + 2 * i); - float angle = headYaw + targetSegment.yaw - baseSegment.yaw; + float angle = getHeadYaw() + targetSegment.yaw - baseSegment.yaw; float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); @@ -257,6 +263,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); + spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty()); session.sendUpstreamPacket(spawnParticleEffectPacket); } } @@ -288,10 +295,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable { session.sendUpstreamPacket(playSoundPacket); } - private boolean isAlive() { - return health > 0; - } - private boolean isHovering() { return phase == 10; } @@ -305,7 +308,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { */ private void pushSegment() { latestSegment = (latestSegment + 1) % segmentHistory.length; - segmentHistory[latestSegment].yaw = headYaw; + segmentHistory[latestSegment].yaw = getHeadYaw(); segmentHistory[latestSegment].y = position.getY(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java index 035d405a0..511c56ff7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java @@ -44,4 +44,9 @@ public class GhastEntity extends FlyingEntity { // If the ghast is attacking dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java index 885961326..92fbeee67 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java @@ -37,4 +37,9 @@ public class MonsterEntity extends CreatureEntity { public MonsterEntity(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 boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java index bdc461518..dff79104b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java @@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity { setBoundingBoxHeight(boundsScale * definition.height()); dirtyMetadata.put(EntityData.SCALE, modelScale); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java index 8d1c54a00..f0577ee20 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java @@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class PiglinEntity extends BasePiglinEntity { @@ -64,4 +68,30 @@ public class PiglinEntity extends BasePiglinEntity { super.updateOffHand(session); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + return canGiveGoldTo(itemInHand) ? InteractiveTag.BARTER : InteractiveTag.NONE; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return canGiveGoldTo(itemInHand) ? InteractionResult.SUCCESS : InteractionResult.PASS; + } + } + + private boolean canGiveGoldTo(@Nonnull GeyserItemStack itemInHand) { + return !getFlag(EntityFlag.BABY) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldIngot() && !getFlag(EntityFlag.ADMIRING); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java index 56719e902..ff1ba9ac3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java @@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity { dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15)); } } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java index f02031044..dd5acbfb1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java @@ -55,4 +55,14 @@ public class ZoglinEntity extends MonsterEntity { float scale = getFlag(EntityFlag.BABY) ? 0.55f : 1f; return scale * definition.height(); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java index 15bcc9c6a..1ec0fc26b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java @@ -33,33 +33,56 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class ZombieVillagerEntity extends ZombieEntity { - private boolean isTransforming; public ZombieVillagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } public void setTransforming(BooleanEntityMetadata entityMetadata) { - isTransforming = entityMetadata.getPrimitiveValue(); - setFlag(EntityFlag.IS_TRANSFORMING, isTransforming); + setFlag(EntityFlag.IS_TRANSFORMING, entityMetadata.getPrimitiveValue()); setFlag(EntityFlag.SHAKING, isShaking()); } public void setZombieVillagerData(EntityMetadata entityMetadata) { VillagerData villagerData = entityMetadata.getValue(); - dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack - dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.getBedrockProfession(villagerData.getProfession())); // Actually works properly with the OptionalPack + dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.getBedrockRegion(villagerData.getType())); // Used with the OptionalPack dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); } @Override protected boolean isShaking() { - return isTransforming || super.isShaking(); + return getFlag(EntityFlag.IS_TRANSFORMING) || super.isShaking(); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) { + return InteractiveTag.CURE; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) { + // The client doesn't know if the entity has weakness as that's not usually sent over the network + return InteractionResult.CONSUME; + } else { + return super.mobInteract(itemInHand); + } } } 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 71bab079d..5c0b18838 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; @@ -38,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; 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; @@ -61,15 +61,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import javax.annotation.Nullable; import java.util.Collections; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter public class PlayerEntity extends LivingEntity { public static final float SNEAKING_POSE_HEIGHT = 1.5f; - private GameProfile profile; private String username; - private boolean playerList = true; // Player is in the player list + private boolean playerList = true; // Player is in the player list + + /** + * The textures property from the GameProfile. + */ + @Nullable + private String texturesProperty; private Vector3i bedPosition; @@ -82,11 +88,12 @@ public class PlayerEntity extends LivingEntity { */ private ParrotEntity rightParrot; - public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { - super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); + public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position, + Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) { + super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); - profile = gameProfile; - username = gameProfile.getName(); + this.username = username; + this.texturesProperty = texturesProperty; } @Override @@ -120,6 +127,7 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); @@ -205,7 +213,7 @@ public class PlayerEntity extends LivingEntity { @Override public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(position); @@ -225,9 +233,11 @@ public class PlayerEntity extends LivingEntity { } } - @Override - public void updateRotation(float yaw, float pitch, boolean isOnGround) { - super.updateRotation(yaw, pitch, isOnGround); + public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) { + // the method below is called by super.updateRotation(yaw, pitch, isOnGround). + // but we have to be able to set the headYaw, so we call the method below directly. + super.moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround); + // Both packets need to be sent or else player head rotation isn't correctly updated MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); @@ -244,6 +254,11 @@ public class PlayerEntity extends LivingEntity { } } + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + updateRotation(yaw, pitch, getHeadYaw(), isOnGround); + } + @Override public void setPosition(Vector3f position) { super.setPosition(position.add(0, definition.offset(), 0)); @@ -292,7 +307,7 @@ public class PlayerEntity extends LivingEntity { } // The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT? ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), - null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw); + null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw()); parrot.spawnEntity(); parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); // Different position whether the parrot is left or right @@ -382,15 +397,26 @@ public class PlayerEntity extends LivingEntity { @Override protected void setDimensions(Pose pose) { float height; + float width; switch (pose) { - case SNEAKING -> height = SNEAKING_POSE_HEIGHT; - case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f; + case SNEAKING -> { + height = SNEAKING_POSE_HEIGHT; + width = definition.width(); + } + case FALL_FLYING, SPIN_ATTACK, SWIMMING -> { + height = 0.6f; + width = definition.width(); + } + case DYING -> { + height = 0.2f; + width = 0.2f; + } default -> { super.setDimensions(pose); return; } } - setBoundingBoxWidth(definition.width()); + setBoundingBoxWidth(width); setBoundingBoxHeight(height); } 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 8dd24bdb8..6edcd60f3 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 @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; @@ -38,6 +37,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.AttributeUtils; @@ -70,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity { private int fakeTradeXp; public SessionPlayerEntity(GeyserSession session) { - super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null); valid = true; } @@ -134,6 +134,10 @@ public class SessionPlayerEntity extends PlayerEntity { return maxHealth; } + public float getHealth() { + return this.health; + } + public void setHealth(float health) { this.health = health; } @@ -167,6 +171,16 @@ public class SessionPlayerEntity extends PlayerEntity { return super.createHealthAttribute(); } + @Override + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + // Must be overridden to point to the player's inventory cache + if (offhand) { + return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId(); + } else { + return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId(); + } + } + @Override public void updateBedrockMetadata() { super.updateBedrockMetadata(); 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 847abf2a9..6c15a4d3e 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 @@ -25,32 +25,29 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; 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; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; -import lombok.Getter; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.skin.SkullSkinManager; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no * custom player skulls in Bedrock. */ public class SkullPlayerEntity extends PlayerEntity { - /** - * Stores the block state that the skull is associated with. Used to determine if the block in the skull's position - * has changed - */ - @Getter - private final int blockState; - public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) { - super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation); - this.blockState = blockState; + public SkullPlayerEntity(GeyserSession session, long geyserId) { + super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null); setPlayerList(false); } @@ -83,6 +80,7 @@ public class SkullPlayerEntity extends PlayerEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); @@ -92,8 +90,57 @@ public class SkullPlayerEntity extends PlayerEntity { session.sendUpstreamPacket(addPlayerPacket); } - public void despawnEntity(Vector3i position) { - this.despawnEntity(); - session.getSkullCache().remove(position, this); + /** + * Hide the player entity so that it can be reused for a different skull. + */ + public void free() { + setFlag(EntityFlag.INVISIBLE, true); + updateBedrockMetadata(); + + // Move skull entity out of the way + moveAbsolute(session.getPlayerEntity().getPosition().up(128), 0, 0, 0, false, true); + } + + public void updateSkull(SkullCache.Skull skull) { + if (!skull.getTexturesProperty().equals(getTexturesProperty())) { + // Make skull invisible as we change skins + setFlag(EntityFlag.INVISIBLE, true); + updateBedrockMetadata(); + + setTexturesProperty(skull.getTexturesProperty()); + + SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> { + // Delay to minimize split-second "player" pop-in + setFlag(EntityFlag.INVISIBLE, false); + updateBedrockMetadata(); + }, 250, TimeUnit.MILLISECONDS))); + } else { + // Just a rotation/position change + setFlag(EntityFlag.INVISIBLE, false); + updateBedrockMetadata(); + } + + float x = skull.getPosition().getX() + .5f; + float y = skull.getPosition().getY() - .01f; + float z = skull.getPosition().getZ() + .5f; + float rotation; + + int blockState = skull.getBlockState(); + byte floorRotation = BlockStateValues.getSkullRotation(blockState); + if (floorRotation == -1) { + // Wall skull + y += 0.25f; + rotation = BlockStateValues.getSkullWallDirections().get(blockState); + switch ((int) rotation) { + case 180 -> z += 0.24f; // North + case 0 -> z -= 0.24f; // South + case 90 -> x += 0.24f; // West + case 270 -> x -= 0.24f; // East + } + } else { + rotation = (180f + (floorRotation * 22.5f)) % 360; + } + + moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true); } } 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 a2b7ff9d6..688151a9e 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -26,8 +26,14 @@ package org.geysermc.geyser.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.ItemUtils; + +import javax.annotation.Nullable; /** * Used to determine if rename packets should be sent and stores @@ -48,6 +54,7 @@ public class AnvilContainer extends Container { /** * The new name of the item as received from Bedrock */ + @Nullable private String newName = null; private GeyserItemStack lastInput = GeyserItemStack.EMPTY; @@ -59,6 +66,36 @@ public class AnvilContainer extends Container { super(title, id, size, containerType, playerInventory); } + /** + * @return the name to use instead for renaming. + */ + public String checkForRename(GeyserSession session, String rename) { + String correctRename; + newName = rename; + + String originalName = ItemUtils.getCustomName(getInput().getNbt()); + + String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale()); + String plainNewName = MessageTranslator.convertToPlainText(rename, session.getLocale()); + if (!plainOriginalName.equals(plainNewName)) { + // Strip out formatting since Java Edition does not allow it + correctRename = plainNewName; + // Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName); + session.sendDownstreamPacket(renameItemPacket); + } else { + // Restore formatting for item since we're not renaming + correctRename = MessageTranslator.convertMessageLenient(originalName); + // Java Edition sends the original custom name when not renaming, + // if there isn't a custom name an empty string is sent + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName); + session.sendDownstreamPacket(renameItemPacket); + } + + useJavaLevelCost = false; + return correctRename; + } + public GeyserItemStack getInput() { return getItem(0); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java index 073887a64..569802a5a 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java @@ -27,11 +27,12 @@ package org.geysermc.geyser.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import lombok.Getter; -import lombok.NonNull; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.jetbrains.annotations.Range; +import javax.annotation.Nonnull; + /** * Combination of {@link Inventory} and {@link PlayerInventory} */ @@ -66,7 +67,7 @@ public class Container extends Inventory { } @Override - public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) { if (slot < this.size) { super.setItem(slot, newItem, session); } else { 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 3b307ba8d..ca7e90a25 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -31,18 +31,19 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3i; import lombok.Getter; -import lombok.NonNull; 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; +import javax.annotation.Nonnull; import java.util.Arrays; @ToString public abstract class Inventory { - @Getter protected final int id; @@ -70,8 +71,7 @@ public abstract class Inventory { protected final ContainerType containerType; @Getter - @Setter - protected String title; + protected final String title; protected final GeyserItemStack[] items; @@ -113,7 +113,7 @@ public abstract class Inventory { public abstract int getOffsetForHotbar(@Range(from = 0, to = 8) int slot); - public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) { if (slot > this.size) { session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this); return; @@ -136,7 +136,9 @@ public abstract class Inventory { protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { if (!newItem.isEmpty()) { - if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + ItemMapping oldMapping = ItemTranslator.getBedrockItemMapping(session, oldItem); + ItemMapping newMapping = ItemTranslator.getBedrockItemMapping(session, newItem); + if (oldMapping.getBedrockId() == newMapping.getBedrockId()) { newItem.setNetId(oldItem.getNetId()); } else { newItem.setNetId(session.getNextItemNetId()); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java index 7c0bcaf4d..315e6cb18 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java @@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; -@Getter -@Setter public class MerchantContainer extends Container { + @Getter @Setter private Entity villager; + @Setter private VillagerTrade[] villagerTrades; + @Getter @Setter private ClientboundMerchantOffersPacket pendingOffersPacket; public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { super(title, id, size, containerType, playerInventory); } + + public void onTradeSelected(GeyserSession session, int slot) { + if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) { + VillagerTrade trade = villagerTrades[slot]; + setItem(2, GeyserItemStack.from(trade.getOutput()), session); + // TODO this logic doesn't add up + session.getPlayerEntity().addFakeTradeExperience(trade.getXp()); + session.getPlayerEntity().updateBedrockMetadata(); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java index 14c796a5f..7b1064c8f 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.inventory; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; @@ -61,6 +62,10 @@ public class PlayerInventory extends Inventory { cursor = newCursor; } + public GeyserItemStack getItemInHand(@Nonnull Hand hand) { + return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand(); + } + public GeyserItemStack getItemInHand() { if (36 + heldItemSlot > this.size) { GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index e973beadc..ec36645da 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 @@ -40,20 +40,22 @@ import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator; -import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; import org.geysermc.geyser.util.InventoryUtils; import org.jetbrains.annotations.Contract; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.ListIterator; -public class ClickPlan { +public final class ClickPlan { private final List plan = new ArrayList<>(); private final Int2ObjectMap simulatedItems; + /** + * Used for 1.17.1+ proper packet translation - any non-cursor item that is changed in a single transaction gets sent here. + */ + private Int2ObjectMap changedItems; private GeyserItemStack simulatedCursor; - private boolean simulating; + private boolean finished; private final GeyserSession session; private final InventoryTranslator translator; @@ -66,16 +68,11 @@ public class ClickPlan { this.inventory = inventory; this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); + this.changedItems = null; this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); - this.simulating = true; + this.finished = false; - if (translator instanceof PlayerInventoryTranslator) { - gridSize = 4; - } else if (translator instanceof CraftingInventoryTranslator) { - gridSize = 9; - } else { - gridSize = -1; - } + gridSize = translator.getGridSize(); } private void resetSimulation() { @@ -88,7 +85,7 @@ public class ClickPlan { } public void add(Click click, int slot, boolean force) { - if (!simulating) + if (finished) throw new UnsupportedOperationException("ClickPlan already executed"); if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) { @@ -97,6 +94,8 @@ public class ClickPlan { ClickAction action = new ClickAction(click, slot, force); plan.add(action); + // RUNNING THE SIMULATION HERE IS IMPORTANT. The contents of the simulation are used in complex, multi-stage tasks + // such as autocrafting. simulateAction(action); } @@ -112,33 +111,48 @@ public class ClickPlan { refresh = true; } - //int stateId = stateIdHack(action); + changedItems = new Int2ObjectOpenHashMap<>(); - //simulateAction(action); + boolean emulatePost1_16Logic = session.isEmulatePost1_16Logic(); + + int stateId; + if (emulatePost1_16Logic) { + stateId = stateIdHack(action); + simulateAction(action); + } else { + stateId = inventory.getStateId(); + } ItemStack clickedItemStack; if (!planIter.hasNext() && refresh) { clickedItemStack = InventoryUtils.REFRESH_ITEM; - } else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { - clickedItemStack = null; } else { - //// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) - //clickedItemStack = simulatedCursor.getItemStack(); TODO fix - this is the proper behavior but it terribly breaks 1.16.5 - clickedItemStack = getItem(action.slot).getItemStack(); + 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 (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { + clickedItemStack = null; + } else { + clickedItemStack = getItem(action.slot).getItemStack(); + } + } + } + + if (!emulatePost1_16Logic) { + simulateAction(action); } ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( inventory.getId(), - inventory.getStateId(), + stateId, action.slot, action.click.actionType, action.click.action, clickedItemStack, - Collections.emptyMap() // Anything else we change, at this time, should have a packet sent to address + changedItems ); - simulateAction(action); - session.sendDownstreamPacket(clickPacket); } @@ -146,19 +160,11 @@ public class ClickPlan { for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session); } - simulating = false; + finished = true; } public GeyserItemStack getItem(int slot) { - return getItem(slot, true); - } - - public GeyserItemStack getItem(int slot, boolean generate) { - if (generate) { - return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); - } else { - return simulatedItems.getOrDefault(slot, inventory.getItem(slot)); - } + return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); } public GeyserItemStack getCursor() { @@ -166,23 +172,40 @@ public class ClickPlan { } private void setItem(int slot, GeyserItemStack item) { - if (simulating) { - simulatedItems.put(slot, item); - } else { - inventory.setItem(slot, item, session); - } + simulatedItems.put(slot, item); + onSlotItemChange(slot, item); } private void setCursor(GeyserItemStack item) { - if (simulating) { - simulatedCursor = item; - } else { - session.getPlayerInventory().setCursor(item, session); + simulatedCursor = item; + } + + private void add(int slot, GeyserItemStack itemStack, int amount) { + itemStack.add(amount); + onSlotItemChange(slot, itemStack); + } + + private void sub(int slot, GeyserItemStack itemStack, int amount) { + itemStack.sub(amount); + onSlotItemChange(slot, itemStack); + } + + private void setAmount(int slot, GeyserItemStack itemStack, int amount) { + itemStack.setAmount(amount); + onSlotItemChange(slot, itemStack); + } + + /** + * Does not need to be called for the cursor + */ + private void onSlotItemChange(int slot, GeyserItemStack itemStack) { + if (changedItems != null) { + changedItems.put(slot, itemStack.getItemStack()); } } private void simulateAction(ClickAction action) { - GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor(); + GeyserItemStack cursor = getCursor(); switch (action.click) { case LEFT_OUTSIDE -> { setCursor(GeyserItemStack.EMPTY); @@ -196,7 +219,7 @@ public class ClickPlan { } } - GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); + GeyserItemStack clicked = getItem(action.slot); if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { switch (action.click) { case LEFT, RIGHT -> { @@ -206,6 +229,7 @@ public class ClickPlan { cursor.add(clicked.getAmount()); } reduceCraftingGrid(false); + setItem(action.slot, GeyserItemStack.EMPTY); // Matches Java Edition 1.18.1 } case LEFT_SHIFT -> reduceCraftingGrid(true); } @@ -217,20 +241,20 @@ public class ClickPlan { setItem(action.slot, cursor); } else { setCursor(GeyserItemStack.EMPTY); - clicked.add(cursor.getAmount()); + add(action.slot, clicked, cursor.getAmount()); } break; case RIGHT: if (cursor.isEmpty() && !clicked.isEmpty()) { int half = clicked.getAmount() / 2; //smaller half setCursor(clicked.copy(clicked.getAmount() - half)); //larger half - clicked.setAmount(half); + setAmount(action.slot, clicked, half); } else if (!cursor.isEmpty() && clicked.isEmpty()) { cursor.sub(1); setItem(action.slot, cursor.copy(1)); } else if (InventoryUtils.canStack(cursor, clicked)) { cursor.sub(1); - clicked.add(1); + add(action.slot, clicked, 1); } break; case SWAP_TO_HOTBAR_1: @@ -265,7 +289,7 @@ public class ClickPlan { break; case DROP_ONE: if (!clicked.isEmpty()) { - clicked.sub(1); + sub(action.slot, clicked, 1); } break; case DROP_ALL: @@ -279,7 +303,7 @@ public class ClickPlan { * Swap between two inventory slots without a cursor. This should only be used with {@link ContainerActionType#MOVE_TO_HOTBAR_SLOT} */ private void swap(int sourceSlot, int destSlot, GeyserItemStack sourceItem) { - GeyserItemStack destinationItem = simulating ? getItem(destSlot) : inventory.getItem(destSlot); + GeyserItemStack destinationItem = getItem(destSlot); setItem(sourceSlot, destinationItem); setItem(destSlot, sourceItem); } @@ -292,63 +316,44 @@ public class ClickPlan { stateId = inventory.getStateId(); } - // This is a hack. - // Java will never ever send more than one container click packet per set of actions. + // Java will never ever send more than one container click packet per set of actions*. + // *(exception being Java's "quick craft"/painting feature) // Bedrock might, and this would generally fall into one of two categories: // - Bedrock is sending an item directly from one slot to another, without picking it up, that cannot // be expressed with a shift click // - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from // one left/right click action. - // When Bedrock does one of these actions and sends multiple packets, a 1.17.1+ server will - // increment the state ID on each confirmation packet it sends back (I.E. set slot). Then when it - // reads our next packet, because we kept the same state ID but the server incremented it, it'll be - // desynced and send the entire inventory contents back at us. - // This hack therefore increments the state ID to what the server will presumably send back to us. - // (This won't be perfect, but should get us through most vanilla situations, and if this is wrong the - // server will just send a set content packet back at us) + // Java typically doesn't increment the state ID if you send a vanilla-accurate container click packet, + // but it will increment the state ID with a vanilla client in at least the crafting table if (inventory.getContainerType() == ContainerType.CRAFTING && CraftingInventoryTranslator.isCraftingGrid(action.slot)) { // 1.18.1 sends a second set slot update for any action in the crafting grid // And an additional packet if something is removed (Mojmap: CraftingContainer#removeItem) - //TODO this code kind of really sucks; it's potentially possible to see what Bedrock sends us and send a PlaceRecipePacket int stateIdIncrements; GeyserItemStack clicked = getItem(action.slot); if (action.click == Click.LEFT) { if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) { // An item is removed from the crafting table; yes deletion - stateIdIncrements = 3; + stateIdIncrements = 2; } else { // We can stack and we add all the items to the crafting slot; no deletion - stateIdIncrements = 2; + stateIdIncrements = 1; } } else if (action.click == Click.RIGHT) { - if (simulatedCursor.isEmpty() && !clicked.isEmpty()) { - // Items are taken; yes deletion - stateIdIncrements = 3; - } else if ((!simulatedCursor.isEmpty() && clicked.isEmpty()) || InventoryUtils.canStack(simulatedCursor, clicked)) { - // Adding our cursor item to the slot; no deletion - stateIdIncrements = 2; - } else { - // ?? nothing I guess - stateIdIncrements = 2; - } + stateIdIncrements = 1; + } else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { + stateIdIncrements = 1; } else { if (session.getGeyser().getConfig().isDebugMode()) { session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan); } - stateIdIncrements = 2; + stateIdIncrements = 1; } inventory.incrementStateId(stateIdIncrements); - } else if (action.click.action instanceof MoveToHotbarAction) { - // Two slot changes sent - inventory.incrementStateId(2); - } else { - inventory.incrementStateId(1); } return stateId; } - //TODO private void reduceCraftingGrid(boolean makeAll) { if (gridSize == -1) return; @@ -370,9 +375,12 @@ public class ClickPlan { } for (int i = 0; i < gridSize; i++) { - GeyserItemStack item = getItem(i + 1); - if (!item.isEmpty()) - item.sub(crafted); + final int slot = i + 1; + GeyserItemStack item = getItem(slot); + if (!item.isEmpty()) { + // These changes should be broadcasted to the server + sub(slot, item, crafted); + } } } @@ -383,8 +391,12 @@ public class ClickPlan { public IntSet getAffectedSlots() { IntSet affectedSlots = new IntOpenHashSet(); for (ClickAction action : plan) { - if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { + if (translator.getSlotType(action.slot) != SlotType.OUTPUT && action.slot != Click.OUTSIDE_SLOT) { affectedSlots.add(action.slot); + if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { + //TODO won't work if offhand is added + affectedSlots.add(inventory.getOffsetForHotbar(((MoveToHotbarAction) action.click.action).ordinal())); + } } } return affectedSlots; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index 2098e04a8..c787f87a1 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -41,16 +41,26 @@ public class StoredItemMappings { private final ItemMapping bamboo; private final ItemMapping banner; private final ItemMapping barrier; + private final int bowl; + private final int chest; private final ItemMapping compass; private final ItemMapping crossbow; private final ItemMapping enchantedBook; private final ItemMapping fishingRod; - private final ItemMapping lodestoneCompass; + private final int flintAndSteel; + private final int goldenApple; + private final int goldIngot; + private final int ironIngot; + private final int lead; private final ItemMapping milkBucket; + private final int nameTag; private final ItemMapping powderSnowBucket; private final ItemMapping playerHead; private final ItemMapping egg; + private final int saddle; + private final int shears; private final ItemMapping shield; + private final int waterBucket; private final ItemMapping wheat; private final ItemMapping writableBook; @@ -58,16 +68,26 @@ public class StoredItemMappings { this.bamboo = load(itemMappings, "bamboo"); this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID this.barrier = load(itemMappings, "barrier"); + this.bowl = load(itemMappings, "bowl").getJavaId(); + this.chest = load(itemMappings, "chest").getJavaId(); this.compass = load(itemMappings, "compass"); this.crossbow = load(itemMappings, "crossbow"); this.enchantedBook = load(itemMappings, "enchanted_book"); this.fishingRod = load(itemMappings, "fishing_rod"); - this.lodestoneCompass = load(itemMappings, "lodestone_compass"); + this.flintAndSteel = load(itemMappings, "flint_and_steel").getJavaId(); + this.goldenApple = load(itemMappings, "golden_apple").getJavaId(); + this.goldIngot = load(itemMappings, "gold_ingot").getJavaId(); + this.ironIngot = load(itemMappings, "iron_ingot").getJavaId(); + this.lead = load(itemMappings, "lead").getJavaId(); this.milkBucket = load(itemMappings, "milk_bucket"); + this.nameTag = load(itemMappings, "name_tag").getJavaId(); this.powderSnowBucket = load(itemMappings, "powder_snow_bucket"); this.playerHead = load(itemMappings, "player_head"); this.egg = load(itemMappings, "egg"); + this.saddle = load(itemMappings, "saddle").getJavaId(); + this.shears = load(itemMappings, "shears").getJavaId(); this.shield = load(itemMappings, "shield"); + this.waterBucket = load(itemMappings, "water_bucket").getJavaId(); this.wheat = load(itemMappings, "wheat"); this.writableBook = load(itemMappings, "writable_book"); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java new file mode 100644 index 000000000..641d5ad94 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.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.inventory.recipe; + +/** + * A more compact version of {@link com.github.steveice10.mc.protocol.data.game.recipe.Recipe}. + */ +public interface GeyserRecipe { + /** + * Whether the recipe is flexible or not in which items can be placed where. + */ + boolean isShaped(); +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java similarity index 57% rename from core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java index a345d8fdb..a011fef6d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java @@ -23,26 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.translator.level.block.entity; +package org.geysermc.geyser.inventory.recipe; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -/** - * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock - */ -public class NoteblockBlockEntityTranslator { +public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, ItemStack result) implements GeyserRecipe { - public static void translate(GeyserSession session, Position position) { - int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position); - BlockEventPacket blockEventPacket = new BlockEventPacket(); - blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); - blockEventPacket.setEventType(0); - blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); - session.sendUpstreamPacket(blockEventPacket); + public GeyserShapedRecipe(ShapedRecipeData data) { + this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult()); } + @Override + public boolean isShaped() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java new file mode 100644 index 000000000..6c7665bbb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.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.inventory.recipe; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; + +public record GeyserShapelessRecipe(Ingredient[] ingredients, ItemStack result) implements GeyserRecipe { + + public GeyserShapelessRecipe(ShapelessRecipeData data) { + this(data.getIngredients(), data.getResult()); + } + + @Override + public boolean isShaped() { + return false; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java new file mode 100644 index 000000000..04a772c31 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.recipe; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; + +/** + * @param buttonId the button that needs to be pressed for Java Edition to accept this item. + * @param output the expected output of this item when cut. + */ +public record GeyserStonecutterData(int buttonId, ItemStack output) { +} 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 d6f72b8d0..655d0f215 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 @@ -384,19 +384,19 @@ public class AnvilInventoryUpdater extends InventoryUpdater { if (enchantTag.get("id") instanceof StringTag javaEnchId) { JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + javaEnchId.getValue()); continue; } Tag javaEnchLvl = enchantTag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) + if (javaEnchLvl == null || !(javaEnchLvl.getValue() instanceof Number number)) continue; // Handle duplicate enchantments if (bedrock) { - enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue()); + enchantments.putIfAbsent(enchantment, number.intValue()); } else { - enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max); + enchantments.mergeInt(enchantment, number.intValue(), Math::max); } } } 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 000000000..78c6b2c6a --- /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 425c78f18..d22150ccf 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/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 3d43b066b..a9b3ffedc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -38,6 +38,8 @@ import org.geysermc.geyser.util.collection.FixedInt2ByteMap; import org.geysermc.geyser.util.collection.FixedInt2IntMap; import org.geysermc.geyser.util.collection.LecternHasBookMap; +import java.util.Locale; + /** * Used for block entities if the Java block state contains Bedrock block information. */ @@ -47,7 +49,9 @@ public final class BlockStateValues { private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); + private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet(); private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap(); + private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); private static final IntSet STICKY_PISTONS = new IntOpenHashSet(); @@ -170,12 +174,27 @@ public final class BlockStateValues { JsonNode shulkerDirection = blockData.get("shulker_direction"); if (shulkerDirection != null) { BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); + return; } - if (javaId.startsWith("minecraft:water")) { + if (javaId.startsWith("minecraft:water") && !javaId.contains("cauldron")) { String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); int level = Integer.parseInt(strLevel); WATER_LEVEL.put(javaBlockState, level); + return; + } + + if (javaId.startsWith("minecraft:jigsaw[orientation=")) { + String blockStateData = javaId.substring(javaId.indexOf("orientation=") + "orientation=".length(), javaId.lastIndexOf('_')); + Direction direction = Direction.valueOf(blockStateData.toUpperCase(Locale.ROOT)); + if (direction.isHorizontal()) { + HORIZONTAL_FACING_JIGSAWS.add(javaBlockState); + } + return; + } + + if (javaId.contains("_cauldron") && !javaId.contains("water_")) { + NON_WATER_CAULDRONS.add(javaBlockState); } } @@ -201,6 +220,15 @@ public final class BlockStateValues { return BED_COLORS.getOrDefault(state, (byte) -1); } + /** + * Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues. + * + * @return if this Java block state is a non-empty non-water cauldron + */ + public static boolean isCauldron(int state) { + return NON_WATER_CAULDRONS.contains(state); + } + /** * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags * in Bedrock need the conditional information. @@ -230,6 +258,13 @@ public final class BlockStateValues { return FLOWER_POT_VALUES; } + /** + * @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing. + */ + public static IntSet getHorizontalFacingJigsaws() { + return HORIZONTAL_FACING_JIGSAWS; + } + /** * @return the lectern book state map pointing to book present state */ diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java deleted file mode 100644 index 6ee53f992..000000000 --- a/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java +++ /dev/null @@ -1,92 +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.level.chunk; - -import com.nukkitx.network.util.Preconditions; - -public class NibbleArray implements Cloneable { - - private final byte[] data; - - public NibbleArray(int length) { - data = new byte[length / 2]; - } - - public NibbleArray(byte[] array) { - data = array; - } - - public byte get(int index) { - Preconditions.checkElementIndex(index, data.length * 2); - byte val = data[index / 2]; - if ((index & 1) == 0) { - return (byte) (val & 0x0f); - } else { - return (byte) ((val & 0xf0) >>> 4); - } - } - - public void set(int index, byte value) { - Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); - Preconditions.checkElementIndex(index, data.length * 2); - value &= 0xf; - int half = index / 2; - byte previous = data[half]; - if ((index & 1) == 0) { - data[half] = (byte) (previous & 0xf0 | value); - } else { - data[half] = (byte) (previous & 0x0f | value << 4); - } - } - - public void fill(byte value) { - Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); - value &= 0xf; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) ((value << 4) | value); - } - } - - public void copyFrom(byte[] bytes) { - Preconditions.checkNotNull(bytes, "bytes"); - Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length, - data.length); - System.arraycopy(bytes, 0, data, 0, data.length); - } - - public void copyFrom(NibbleArray array) { - Preconditions.checkNotNull(array, "array"); - copyFrom(array.data); - } - - public byte[] getData() { - return data; - } - - public NibbleArray copy() { - return new NibbleArray(getData().clone()); - } -} 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 892ddcb64..d41871cdb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -47,6 +47,8 @@ import java.nio.charset.StandardCharsets; import java.util.List; public class ConnectorServerEventHandler implements BedrockServerEventHandler { + private static final boolean PRINT_DEBUG_PINGS = Boolean.parseBoolean(System.getProperty("Geyser.PrintPingsInDebugMode", "true")); + /* The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client */ @@ -88,7 +90,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { - if (geyser.getConfig().isDebugMode()) { + if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); } 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 d28b95203..828b04a9d 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -28,12 +28,12 @@ package org.geysermc.geyser.network; import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; -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.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.StringJoiner; @@ -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_v475.V475_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v503.V503_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -58,9 +58,13 @@ public final class MinecraftProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_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(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.toBuilder() + .minecraftVersion("1.18.30/1.18.31") + .build()); } /** @@ -92,7 +96,7 @@ public final class MinecraftProtocol { * @return the supported Minecraft: Java Edition version names */ public static List getJavaVersions() { - return Arrays.asList("1.18", "1.18.1"); + return Collections.singletonList(DEFAULT_JAVA_CODEC.getMinecraftVersion()); } /** 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 f547c4dce..5ae6fbca9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -30,17 +30,18 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.data.ExperimentData; import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.auth.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.session.auth.AuthType; 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; @@ -73,11 +74,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions(); if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale - session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); return true; } else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return true; } @@ -166,11 +165,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } - if (session.getUpstream().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - // Allow extended world height in the overworld to work for pre-1.18 clients - stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - session.sendUpstreamPacket(stackPacket); break; @@ -189,6 +183,14 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean couldLoginUserByName(String bedrockUsername) { + if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { + String refreshToken = geyser.refreshTokenFor(bedrockUsername); + if (refreshToken != null) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); + session.authenticateWithRefreshToken(refreshToken); + return true; + } + } if (geyser.getConfig().getUserAuths() != null) { GeyserConfiguration.IUserAuthenticationInfo info = geyser.getConfig().getUserAuths().get(bedrockUsername); @@ -199,6 +201,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return true; } } + PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(session.getAuthData().xuid()); + if (task != null) { + if (task.getAuthentication().isDone() && session.onMicrosoftLoginComplete(task)) { + return true; + } + } return false; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java new file mode 100644 index 000000000..892f4a6df --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.geysermc.geyser.registry.loader.RegistryLoader; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +/** + * A mapped registry with an integer as the key. This class is designed to minimize the need for boxing/unboxing keys. + * + * @param the value + */ +public class IntMappedRegistry extends AbstractMappedRegistry> { + protected IntMappedRegistry(I input, RegistryLoader> registryLoader) { + super(input, registryLoader); + } + + /** + * Returns the value registered by the given integer. + * + * @param i the integer + * @return the value registered by the given integer. + */ + public V get(int i) { + return this.mappings.get(i); + } + + @Nullable + @Override + @Deprecated + public V get(Integer key) { + return super.get(key); + } + + /** + * Returns the value registered by the given key or the default value + * specified if null. + * + * @param i the key + * @param defaultValue the default value + * @return the value registered by the given key or the default value + * specified if null. + */ + public V getOrDefault(int i, V defaultValue) { + return this.mappings.getOrDefault(i, defaultValue); + } + + @Override + @Deprecated + public V getOrDefault(Integer key, V defaultValue) { + return super.getOrDefault(key, defaultValue); + } + + /** + * Registers a new value into this registry with the given key. + * + * @param i the key + * @param value the value + * @return a new value into this registry with the given key. + */ + public V register(int i, V value) { + return this.mappings.put(i, value); + } + + @Override + @Deprecated + public V register(Integer key, V value) { + return super.register(key, value); + } + + /** + * Creates a new integer mapped registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static IntMappedRegistry create(RegistryLoader> registryLoader) { + return new IntMappedRegistry<>(null, registryLoader); + } + + /** + * Creates a new integer mapped registry with the given {@link RegistryLoader} and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static IntMappedRegistry create(I input, Supplier>> registryLoader) { + return new IntMappedRegistry<>(input, registryLoader.get()); + } +} 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 3b0c1f138..0b59492d3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -29,9 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; 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; @@ -43,26 +41,26 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; -import org.geysermc.geyser.translator.collision.BlockCollision; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; -import org.geysermc.geyser.translator.sound.SoundTranslator; -import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; -import org.geysermc.geyser.translator.level.event.LevelEventTranslator; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.registry.loader.*; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; +import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.registry.type.SoundMapping; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; +import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; +import org.geysermc.geyser.translator.sound.SoundTranslator; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.IntFunction; /** * Holds all the common registries in Geyser. @@ -96,7 +94,7 @@ public final class Registries { /** * A mapped registry containing which holds block IDs to its {@link BlockCollision}. */ - public static final SimpleMappedRegistry COLLISIONS = SimpleMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); + public static final IntMappedRegistry COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); /** * A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}. @@ -141,15 +139,15 @@ public final class Registries { public static final SimpleRegistry> POTION_MIXES; /** - * A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value. + * A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value. */ - public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); /** * A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent} * as the value. */ - public static final SimpleMappedRegistry RECORDS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final IntMappedRegistry RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); /** * A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}. diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java index c6cd092d4..b74573a4e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java @@ -50,10 +50,10 @@ import java.util.regex.Pattern; /** * Loads collision data from the given resource path. */ -public class CollisionRegistryLoader extends MultiResourceRegistryLoader> { +public class CollisionRegistryLoader extends MultiResourceRegistryLoader> { @Override - public Map load(Pair input) { + public Int2ObjectMap load(Pair input) { Int2ObjectMap collisions = new Int2ObjectOpenHashMap<>(); Map, CollisionInfo> annotationMap = new IdentityHashMap<>(); 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 b1066c79c..412d7d779 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 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; -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; @@ -46,7 +47,10 @@ import org.geysermc.geyser.util.BlockUtils; import java.io.DataInputStream; import java.io.InputStream; -import java.util.*; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.Map; import java.util.function.BiFunction; import java.util.zip.GZIPInputStream; @@ -57,10 +61,51 @@ 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_30", Bedrock_v465.V465_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER); + .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(); } @@ -87,7 +132,6 @@ public class BlockRegistryPopulator { } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map javaIdentifierToBedrockTag = new Object2ObjectOpenHashMap<>(blocksTag.size()); // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // as we no longer send a block palette Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); @@ -157,10 +201,6 @@ public class BlockRegistryPopulator { flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); } - if (!cleanJavaIdentifier.equals(entry.getValue().get("bedrock_identifier").asText())) { - javaIdentifierToBedrockTag.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); - } - javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId; } @@ -195,7 +235,6 @@ public class BlockRegistryPopulator { BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) .javaToBedrockBlocks(javaToBedrockBlocks) - .javaIdentifierToBedrockTag(javaIdentifierToBedrockTag) .itemFrames(itemFrames) .flowerPotBlocks(flowerPotBlocks) .jigsawStateIds(jigsawStateIds) 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 d448bfa6a..37b6c49f4 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,11 +35,10 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +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.*; @@ -49,6 +48,8 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.*; +import org.geysermc.geyser.util.ItemUtils; +import org.geysermc.geyser.util.collection.FixedInt2IntMap; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -59,19 +60,16 @@ import java.util.*; * Populates the item registries. */ public class ItemRegistryPopulator { - private static final Map PALETTE_VERSIONS; - - static { - PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); - PALETTE_VERSIONS.put("1_17_30", new PaletteVersion(Bedrock_v465.V465_CODEC.getProtocolVersion(), Collections.emptyMap())); - 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())); - } 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<>() { }; @@ -84,8 +82,12 @@ public class ItemRegistryPopulator { throw new AssertionError("Unable to load Java runtime item IDs", e); } + // We can reduce some operations as Java information is the same across all palette versions + boolean firstMappingsPass = true; + 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) @@ -125,7 +127,7 @@ public class ItemRegistryPopulator { IntList spawnEggs = new IntArrayList(); List carpets = new ObjectArrayList<>(); - Int2ObjectMap mappings = new Int2ObjectOpenHashMap<>(); + List mappings = new ObjectArrayList<>(); // Temporary mapping to create stored items Map identifierToMapping = new Object2ObjectOpenHashMap<>(); @@ -163,6 +165,9 @@ public class ItemRegistryPopulator { if (identifier.equals("minecraft:debug_stick")) { // Just shows an empty texture; either way it doesn't exist in the creative menu on Java continue; + } else if (identifier.equals("minecraft:empty_map") && damage == 2) { + // Bedrock-only as its own item + continue; } StartGamePacket.ItemEntry entry = entries.get(identifier); int id = -1; @@ -224,16 +229,27 @@ public class ItemRegistryPopulator { // This items has a mapping specifically for this version of the game mappingItem = entry.getValue(); } - if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - mappingItem.setBedrockIdentifier("minecraft:music_disc_pigstep"); + + String bedrockIdentifier; + 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")) { javaFurnaceMinecartId = itemIndex; itemIndex++; + // Will be added later + mappings.add(null); continue; } - String bedrockIdentifier = mappingItem.getBedrockIdentifier().intern(); + int bedrockId = bedrockIdentifierToId.getInt(bedrockIdentifier); if (bedrockId == Short.MIN_VALUE) { throw new RuntimeException("Missing Bedrock ID in mappings: " + bedrockIdentifier); @@ -358,12 +374,13 @@ public class ItemRegistryPopulator { ItemMapping.ItemMappingBuilder mappingBuilder = ItemMapping.builder() .javaIdentifier(javaIdentifier) .javaId(itemIndex) - .bedrockIdentifier(bedrockIdentifier) + .bedrockIdentifier(bedrockIdentifier.intern()) .bedrockId(bedrockId) .bedrockData(mappingItem.getBedrockData()) .bedrockBlockId(bedrockBlockId) .stackSize(stackSize) - .maxDamage(mappingItem.getMaxDamage()); + .maxDamage(mappingItem.getMaxDamage()) + .hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect()); if (mappingItem.getRepairMaterials() != null) { mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials())); @@ -406,11 +423,15 @@ public class ItemRegistryPopulator { spawnEggs.add(mapping.getBedrockId()); } - mappings.put(itemIndex, mapping); + mappings.add(mapping); identifierToMapping.put(javaIdentifier, mapping); itemNames.add(javaIdentifier); + if (firstMappingsPass && mappingItem.getDyeColor() != -1) { + dyeColors.put(itemIndex, mappingItem.getDyeColor()); + } + itemIndex++; } @@ -423,16 +444,14 @@ public class ItemRegistryPopulator { // Add the lodestone compass since it doesn't exist on java but we need it for item conversion ItemMapping lodestoneEntry = ItemMapping.builder() - .javaIdentifier("minecraft:lodestone_compass") + .javaIdentifier("") .bedrockIdentifier("minecraft:lodestone_compass") - .javaId(itemIndex) + .javaId(-1) .bedrockId(lodestoneCompassId) .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) .build(); - mappings.put(itemIndex, lodestoneEntry); - identifierToMapping.put(lodestoneEntry.getJavaIdentifier(), lodestoneEntry); ComponentItemData furnaceMinecartData = null; if (usingFurnaceMinecart) { @@ -441,7 +460,7 @@ public class ItemRegistryPopulator { entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); - mappings.put(javaFurnaceMinecartId, ItemMapping.builder() + mappings.set(javaFurnaceMinecartId, ItemMapping.builder() .javaIdentifier("minecraft:furnace_minecart") .bedrockIdentifier("geysermc:furnace_minecart") .javaId(javaFurnaceMinecartId) @@ -492,9 +511,9 @@ public class ItemRegistryPopulator { } ItemMappings itemMappings = ItemMappings.builder() - .items(mappings) + .items(mappings.toArray(new ItemMapping[0])) .creativeItems(creativeItems.toArray(new ItemData[0])) - .itemEntries(new ArrayList<>(entries.values())) + .itemEntries(List.copyOf(entries.values())) .itemNames(itemNames.toArray(new String[0])) .storedItems(new StoredItemMappings(identifierToMapping)) .javaOnlyItems(javaOnlyItems) @@ -503,9 +522,14 @@ public class ItemRegistryPopulator { .spawnEggIds(spawnEggs) .carpets(carpets) .furnaceMinecartData(furnaceMinecartData) + .lodestoneCompass(lodestoneEntry) .build(); Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); + + firstMappingsPass = false; } + + ItemUtils.setDyeColors(dyeColors); } } 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 f32aeef51..f0a215f2a 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 @@ -28,10 +28,7 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -40,6 +37,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -71,7 +71,7 @@ public class RecipeRegistryPopulator { // Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions LAST_RECIPE_NET_ID = currentRecipeId; Map> craftingData = new EnumMap<>(RecipeType.class); - Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING, Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID))); @@ -124,7 +124,7 @@ public class RecipeRegistryPopulator { * @param recipes a list of all the recipes * @return the {@link CraftingData} to send to the Bedrock client. */ - private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { + private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { int netId = ++LAST_RECIPE_NET_ID; int type = node.get("bedrockRecipeType").asInt(); JsonNode outputNode = node.get("output"); @@ -165,9 +165,8 @@ public class RecipeRegistryPopulator { for (ItemData input : inputs) { ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); } - ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table", + GeyserRecipe recipe = new GeyserShapedRecipe(shape.get(0).length(), shape.size(), ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data); recipes.put(netId, recipe); /* Convert end */ @@ -185,9 +184,7 @@ public class RecipeRegistryPopulator { for (ItemData input : inputs) { ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); } - ShapelessRecipeData data = new ShapelessRecipeData("crafting_table", - ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data); + GeyserRecipe recipe = new GeyserShapelessRecipe(ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); recipes.put(netId, recipe); /* Convert end */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java index a105682a6..41318ee64 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -47,12 +47,6 @@ public class BlockMappings { NbtList bedrockBlockStates; - /** - * Contains a map of Java blocks to their respective Bedrock block tag, if the Java identifier is different from Bedrock. - * Required to fix villager trades with these blocks. - */ - Map javaIdentifierToBedrockTag; - int commandBlockRuntimeId; Object2IntMap itemFrames; @@ -74,13 +68,4 @@ public class BlockMappings { public boolean isItemFrame(int bedrockBlockRuntimeId) { return this.itemFrames.values().contains(bedrockBlockRuntimeId); } - - /** - * @param cleanJavaIdentifier the clean Java identifier of the block to look up - * - * @return the block tag of the block name mapped from Java to Bedrock. - */ - public NbtMap getBedrockBlockNbt(String cleanJavaIdentifier) { - return this.javaIdentifierToBedrockTag.get(cleanJavaIdentifier); - } } \ No newline at end of file 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 a5b6c5ab8..9d06fd3a9 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 @@ -44,4 +44,6 @@ public class GeyserMappingItem { @JsonProperty("tool_tier") String toolTier; @JsonProperty("max_damage") int maxDamage = 0; @JsonProperty("repair_materials") List repairMaterials; + @JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false; + @JsonProperty("dye_color") int dyeColor = -1; } 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 ff558c55f..28d41ba46 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 @@ -39,7 +39,7 @@ import java.util.Set; public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, 0, null); + 64, null, null, null, 0, null, false); String javaIdentifier; String bedrockIdentifier; @@ -63,6 +63,8 @@ public class ItemMapping { Set repairMaterials; + boolean hasSuspiciousStewEffect; + /** * Gets if this item is a block. * 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 a4953b05b..3072568f3 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,13 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; 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; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.item.StoredItemMappings; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; import java.util.Map; @@ -48,7 +48,12 @@ public class ItemMappings { Map cachedJavaMappings = new WeakHashMap<>(); - Int2ObjectMap items; + ItemMapping[] items; + + /** + * A unique exception as this is an item in Bedrock, but not in Java. + */ + ItemMapping lodestoneCompass; ItemData[] creativeItems; List itemEntries; @@ -70,6 +75,7 @@ public class ItemMappings { * @param itemStack the itemstack * @return an item entry from the given java edition identifier */ + @Nonnull public ItemMapping getMapping(ItemStack itemStack) { return this.getMapping(itemStack.getId()); } @@ -81,8 +87,9 @@ public class ItemMappings { * @param javaId the id * @return an item entry from the given java edition identifier */ + @Nonnull public ItemMapping getMapping(int javaId) { - return this.items.get(javaId); + return javaId >= 0 && javaId < this.items.length ? this.items[javaId] : ItemMapping.AIR; } /** @@ -94,7 +101,7 @@ public class ItemMappings { */ public ItemMapping getMapping(String javaIdentifier) { return this.cachedJavaMappings.computeIfAbsent(javaIdentifier, key -> { - for (ItemMapping mapping : this.items.values()) { + for (ItemMapping mapping : this.items) { if (mapping.getJavaIdentifier().equals(key)) { return mapping; } @@ -110,11 +117,18 @@ public class ItemMappings { * @return an item entry from the given item data */ public ItemMapping getMapping(ItemData data) { + int id = data.getId(); + if (id == 0) { + return ItemMapping.AIR; + } else if (id == lodestoneCompass.getBedrockId()) { + return lodestoneCompass; + } + boolean isBlock = data.getBlockRuntimeId() != 0; boolean hasDamage = data.getDamage() != 0; - for (ItemMapping mapping : this.items.values()) { - if (mapping.getBedrockId() == data.getId()) { + for (ItemMapping mapping : this.items) { + if (mapping.getBedrockId() == id) { if (isBlock && !hasDamage) { // Pre-1.16.220 will not use block runtime IDs at all, so we shouldn't check either if (data.getBlockRuntimeId() != mapping.getBedrockBlockId()) { continue; @@ -135,7 +149,7 @@ public class ItemMappings { } // This will hide the message when the player clicks with an empty hand - if (data.getId() != 0 && data.getDamage() != 0) { + if (id != 0 && data.getDamage() != 0) { GeyserImpl.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); } return ItemMapping.AIR; 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 9113792ba..91475fb56 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,8 +25,8 @@ 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.AuthPendingException; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.auth.service.AuthenticationService; @@ -37,9 +37,11 @@ import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.ProtocolState; import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; @@ -48,6 +50,8 @@ import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientInte import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Session; @@ -63,10 +67,12 @@ 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.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import io.netty.channel.Channel; import io.netty.channel.EventLoop; -import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -75,6 +81,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.checkerframework.common.value.qual.IntRange; import org.geysermc.common.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.util.FormBuilder; @@ -85,20 +92,21 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; -import org.geysermc.geyser.entity.InteractiveTagManager; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.AuthType; @@ -109,18 +117,15 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.geyser.util.DimensionUtils; -import org.geysermc.geyser.util.LoginEncryptionUtils; -import org.geysermc.geyser.util.MathUtils; +import org.geysermc.geyser.util.*; +import javax.annotation.Nonnull; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -128,18 +133,23 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter public class GeyserSession implements GeyserConnection, CommandSender { - private final GeyserImpl geyser; - private final 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 EventLoop eventLoop; + private final @Nonnull EventLoop eventLoop; private TcpSession downstream; @Setter private AuthData authData; @Setter private BedrockClientData clientData; + /** + * Used for Floodgate skin uploading + */ + @Setter + private JsonNode certChainData; /* Setter for GeyserConnect */ @Setter @@ -165,6 +175,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { private final LodestoneCache lodestoneCache; private final PistonCache pistonCache; private final PreferencesCache preferencesCache; + private final SkullCache skullCache; private final TagCache tagCache; private final WorldCache worldCache; @@ -212,7 +223,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private ItemMappings itemMappings; - private final Map skullCache = new Object2ObjectOpenHashMap<>(); private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); /** @@ -350,8 +360,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { private Entity mouseoverEntity; @Setter - private Int2ObjectMap craftingRecipes; - private final Set unlockedRecipes; + private Int2ObjectMap craftingRecipes; private final AtomicInteger lastRecipeNetId; /** @@ -359,7 +368,21 @@ public class GeyserSession implements GeyserConnection, CommandSender { * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier */ @Setter - private Int2ObjectMap stonecutterRecipes; + private Int2ObjectMap stonecutterRecipes; + + /** + * Whether to work around 1.13's different behavior in villager trading menus. + */ + @Setter + private boolean emulatePost1_14Logic = true; + /** + * Starting in 1.17, Java servers expect the carriedItem parameter of the serverbound click container + * packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot + * contents before any transaction is done. With the current ViaVersion structure, if we do not send what 1.16 expects + * and send multiple click container packets, then successive transactions will be rejected. + */ + @Setter + private boolean emulatePost1_16Logic = true; /** * The current attack speed of the player. Used for sending proper cooldown timings. @@ -411,6 +434,13 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private long lastVehicleMoveTimestamp = System.currentTimeMillis(); + /** + * Counts how many ticks have occurred since an arm animation started. + * -1 means there is no active arm swing. + */ + @Getter(AccessLevel.NONE) + private int armAnimationTicks = -1; + /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. */ @@ -435,6 +465,9 @@ public class GeyserSession implements GeyserConnection, CommandSender { */ private boolean flying = false; + @Setter + private boolean instabuild = false; + /** * Caches current rain status. */ @@ -499,6 +532,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.lodestoneCache = new LodestoneCache(); this.pistonCache = new PistonCache(this); this.preferencesCache = new PreferencesCache(this); + this.skullCache = new SkullCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); @@ -512,7 +546,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.playerInventory = new PlayerInventory(); this.openInventory = null; this.craftingRecipes = new Int2ObjectOpenHashMap<>(); - this.unlockedRecipes = new ObjectOpenHashSet<>(); this.lastRecipeNetId = new AtomicInteger(1); this.spawned = false; @@ -533,11 +566,14 @@ public class GeyserSession implements GeyserConnection, CommandSender { } bedrockServerSession.addDisconnectHandler(disconnectReason -> { - InetAddress address = bedrockServerSession.getRealAddress().getAddress(); - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason)); + String message = switch (disconnectReason) { + // A generic message that just means the player quit normally. + case CLOSED_BY_REMOTE_PEER -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.closed_by_remote_peer"); + case TIMED_OUT -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.timed_out"); + default -> disconnectReason.name(); + }; - disconnect(disconnectReason.name()); - geyser.getSessionManager().removeSession(this); + disconnect(message); }); this.remoteAddress = geyser.getConfig().getRemote().getAddress(); @@ -599,17 +635,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { upstream.sendPacket(gamerulePacket); } - public void login() { - if (this.remoteAuthType != AuthType.ONLINE) { - if (this.remoteAuthType == AuthType.OFFLINE) { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.offline")); - } else { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.floodgate")); - } - authenticate(authData.name()); - } - } - public void authenticate(String username) { authenticate(username, ""); } @@ -623,7 +648,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { loggingIn = true; // Use a future to prevent timeouts as all the authentication is handled sync - // This will be changed with the new protocol library. CompletableFuture.supplyAsync(() -> { try { if (password != null && !password.isEmpty()) { @@ -680,10 +704,58 @@ public class GeyserSession implements GeyserConnection, CommandSender { }); } + public void authenticateWithRefreshToken(String refreshToken) { + if (loggedIn) { + geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); + return; + } + + loggingIn = true; + + CompletableFuture.supplyAsync(() -> { + MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + service.setRefreshToken(refreshToken); + try { + service.login(); + } catch (RequestException e) { + geyser.getLogger().error("Error while attempting to use refresh token for " + name() + "!", e); + return Boolean.FALSE; + } + + GameProfile profile = service.getSelectedProfile(); + if (profile == null) { + // Java account is offline + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return null; + } + + protocol = new MinecraftProtocol(profile, service.getAccessToken()); + geyser.saveRefreshToken(name(), service.getRefreshToken()); + return Boolean.TRUE; + }).whenComplete((successful, ex) -> { + if (this.closed) { + return; + } + if (successful == Boolean.FALSE) { + // The player is waiting for a spawn packet, so let's spawn them in now to show them forms + connect(); + // Will be cached for after login + LoginEncryptionUtils.buildAndShowTokenExpiredWindow(this); + return; + } + + connectDownstream(); + }); + } + + public void authenticateWithMicrosoftCode() { + authenticateWithMicrosoftCode(false); + } + /** * Present a form window to the user asking to log in with another web browser */ - public void authenticateWithMicrosoftCode() { + public void authenticateWithMicrosoftCode(boolean offlineAccess) { if (loggedIn) { geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); return; @@ -696,65 +768,64 @@ public class GeyserSession implements GeyserConnection, CommandSender { packet.setTime(16000); sendUpstreamPacket(packet); - // new thread so clients don't timeout - MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask( + getAuthData().xuid() + ); + task.setOnline(true); + task.resetTimer(); - // Use a future to prevent timeouts as all the authentication is handled sync - // This will be changed with the new protocol library. - CompletableFuture.supplyAsync(() -> { - try { - return msaAuthenticationService.getAuthCode(); - } catch (RequestException e) { - throw new CompletionException(e); - } - }).whenComplete((response, ex) -> { - if (ex != null) { - ex.printStackTrace(); - disconnect(ex.toString()); - return; - } - LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); - attemptCodeAuthentication(msaAuthenticationService); - }); + if (task.getAuthentication().isDone()) { + onMicrosoftLoginComplete(task); + } else { + task.getCode(offlineAccess).whenComplete((response, ex) -> { + boolean connected = !closed; + if (ex != null) { + if (connected) { + geyser.getLogger().error("Failed to get Microsoft auth code", ex); + disconnect(ex.toString()); + } + task.cleanup(); // error getting auth code -> clean up immediately + } else if (connected) { + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); + task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task)); + } + }); + } } /** - * Poll every second to see if the user has successfully signed in + * If successful, also begins connecting to the Java server. */ - private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) { - if (loggedIn || closed) { - return; + public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.AuthenticationTask task) { + if (closed) { + return false; } - CompletableFuture.supplyAsync(() -> { - try { - msaAuthenticationService.login(); - GameProfile profile = msaAuthenticationService.getSelectedProfile(); - if (profile == null) { - // Java account is offline - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); - return null; - } - - return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken()); - } catch (RequestException e) { - throw new CompletionException(e); - } - }).whenComplete((response, ex) -> { - if (ex != null) { - if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) { - geyser.getLogger().error("Failed to log in with Microsoft code!", ex); - disconnect(ex.toString()); - } else { - // Wait one second before trying again - geyser.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); - } - return; - } - if (!closed) { - this.protocol = response; + task.cleanup(); // player is online -> remove pending authentication immediately + Throwable ex = task.getLoginException(); + if (ex != null) { + geyser.getLogger().error("Failed to log in with Microsoft code!", ex); + disconnect(ex.toString()); + } else { + MsaAuthenticationService service = task.getMsaAuthenticationService(); + GameProfile selectedProfile = service.getSelectedProfile(); + if (selectedProfile == null) { + disconnect(GeyserLocale.getPlayerLocaleString( + "geyser.network.remote.invalid_account", + clientData.getLanguageCode() + )); + } else { + this.protocol = new MinecraftProtocol( + selectedProfile, + service.getAccessToken() + ); connectDownstream(); + + // Save our refresh token for later use + geyser.saveRefreshToken(name(), service.getRefreshToken()); + return true; } - }); + } + return false; } /** @@ -796,6 +867,13 @@ public class GeyserSession implements GeyserConnection, CommandSender { FloodgateSkinUploader skinUploader = geyser.getSkinUploader(); FloodgateCipher cipher = geyser.getCipher(); + String bedrockAddress = upstream.getAddress().getAddress().getHostAddress(); + // both BungeeCord and Velocity remove the IPv6 scope (if there is one) for Spigot + int ipv6ScopeIndex = bedrockAddress.indexOf('%'); + if (ipv6ScopeIndex != -1) { + bedrockAddress = bedrockAddress.substring(0, ipv6ScopeIndex); + } + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.name(), @@ -804,7 +882,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getAddress().getAddress().getHostAddress(), + bedrockAddress, skinUploader.getId(), skinUploader.getVerifyCode() ).toString()); @@ -942,11 +1020,22 @@ public class GeyserSession implements GeyserConnection, CommandSender { loggedIn = false; if (downstream != null) { downstream.disconnect(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(); + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, reason)); } - if (upstream != null && !upstream.isClosed()) { - geyser.getSessionManager().removeSession(this); + if (!upstream.isClosed()) { upstream.disconnect(reason); } + geyser.getSessionManager().removeSession(this); + if (authData != null) { + PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid()); + if (task != null) { + task.setOnline(false); + } + } } if (tickThread != null) { @@ -956,10 +1045,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { closed = true; } - public void close() { - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); - } - /** * Executes a task and prints a stack trace if an error occurs. */ @@ -1009,15 +1094,17 @@ public class GeyserSession implements GeyserConnection, CommandSender { worldBorder.resize(); } - if (!worldBorder.isWithinWarningBoundaries()) { + boolean shouldShowFog = !worldBorder.isWithinWarningBoundaries(); + if (shouldShowFog || worldBorder.isCloseToBorderBoundaries()) { // Show particles representing where the world border is worldBorder.drawWall(); // Set the mood - if (!isInWorldBorderWarningArea) { + if (shouldShowFog && !isInWorldBorderWarningArea) { isInWorldBorderWarningArea = true; sendFog("minecraft:fog_crimson_forest"); } - } else if (isInWorldBorderWarningArea) { + } + if (!shouldShowFog && isInWorldBorderWarningArea) { // Clear fog as we are outside the world border now removeFog("minecraft:fog_crimson_forest"); isInWorldBorderWarningArea = false; @@ -1027,6 +1114,34 @@ public class GeyserSession implements GeyserConnection, CommandSender { for (Tickable entity : entityCache.getTickableEntities()) { entity.tick(); } + + if (armAnimationTicks != -1) { + // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the + // player's effect status, but the animation can cut short if the duration suddenly decreases + // (from suddenly no longer having mining fatigue, for example) + // This math is referenced from Java Edition 1.18.2 + int swingTotalDuration; + int hasteLevel = Math.max(effectCache.getHaste(), effectCache.getConduitPower()); + if (hasteLevel > 0) { + swingTotalDuration = 6 - hasteLevel; + } else { + int miningFatigueLevel = effectCache.getMiningFatigue(); + if (miningFatigueLevel > 0) { + swingTotalDuration = 6 + miningFatigueLevel * 2; + } else { + swingTotalDuration = 6; + } + } + if (++armAnimationTicks >= swingTotalDuration) { + if (sneaking) { + // Attempt to re-activate blocking as our swing animation is up + if (attemptToBlock()) { + playerEntity.updateBedrockMetadata(); + } + } + armAnimationTicks = -1; + } + } } catch (Throwable throwable) { throwable.printStackTrace(); } @@ -1036,7 +1151,23 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.authData = authData; } - public void setSneaking(boolean sneaking) { + public void startSneaking() { + // Toggle the shield, if there is no ongoing arm animation + // This matches Bedrock Edition behavior as of 1.18.12 + if (armAnimationTicks == -1) { + attemptToBlock(); + } + + setSneaking(true); + } + + public void stopSneaking() { + disableBlocking(); + + setSneaking(false); + } + + private void setSneaking(boolean sneaking) { this.sneaking = sneaking; // Update pose and bounding box on our end @@ -1060,7 +1191,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { if (mouseoverEntity != null) { // Horses, etc can change their property depending on if you're sneaking - InteractiveTagManager.updateTag(this, mouseoverEntity); + mouseoverEntity.updateInteractiveTag(); } } @@ -1121,6 +1252,54 @@ public class GeyserSession implements GeyserConnection, CommandSender { return null; } + /** + * Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display + * blocking and sends a packet to the Java server. + */ + private boolean attemptToBlock() { + ItemMapping shield = itemMappings.getStoredItems().shield(); + + ServerboundUseItemPacket useItemPacket; + if (playerInventory.getItemInHand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + } else if (playerInventory.getOffhand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND); + } else { + // No blocking + return false; + } + + sendDownstreamPacket(useItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, true); + // Metadata should be updated later + return true; + } + + /** + * Starts ticking the amount of time that the Bedrock client has been swinging their arm, and disables blocking if + * blocking. + */ + public void activateArmAnimationTicking() { + armAnimationTicks = 0; + if (disableBlocking()) { + playerEntity.updateBedrockMetadata(); + } + } + + /** + * Indicates to the client to stop blocking and tells the Java server the same. + */ + private boolean disableBlocking() { + if (playerEntity.getFlag(EntityFlag.BLOCKING)) { + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, + BlockUtils.POSITION_ZERO, Direction.DOWN); + sendDownstreamPacket(releaseItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, false); + return true; + } + return false; + } + /** * Will be overwritten for GeyserConnect. */ @@ -1143,6 +1322,21 @@ public class GeyserSession implements GeyserConnection, CommandSender { 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; + } + @Override public void sendMessage(String message) { TextPacket textPacket = new TextPacket(); @@ -1199,7 +1393,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { 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); @@ -1248,10 +1442,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { 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); } @@ -1510,4 +1700,17 @@ public class GeyserSession implements GeyserConnection, CommandSender { packet.getFogStack().addAll(this.fogNameSpaces); sendUpstreamPacket(packet); } + + public boolean canUseCommandBlocks() { + return instabuild && opPermissionLevel >= 2; + } + + public void playSoundEvent(SoundEvent sound, Vector3f position) { + LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet(); + packet.setPosition(position); + packet.setSound(sound); + packet.setIdentifier(":"); + packet.setExtraData(-1); + sendUpstreamPacket(packet); + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java new file mode 100644 index 000000000..93200dcb6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2019-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.geyser.session; + +import com.github.steveice10.mc.auth.exception.request.AuthPendingException; +import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.SneakyThrows; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; + +import javax.annotation.Nonnull; +import java.util.concurrent.*; + +/** + * Pending Microsoft authentication task cache. + * It permits user to exit the server while they authorize Geyser to access their Microsoft account. + */ +public class PendingMicrosoftAuthentication { + /** + * For GeyserConnect usage. + */ + private boolean storeServerInformation = false; + private final LoadingCache authentications; + + public PendingMicrosoftAuthentication(int timeoutSeconds) { + this.authentications = CacheBuilder.newBuilder() + .build(new CacheLoader<>() { + @Override + public AuthenticationTask load(@NonNull String userKey) { + return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L) + : new AuthenticationTask(userKey, timeoutSeconds * 1000L); + } + }); + } + + public AuthenticationTask getTask(@Nonnull String userKey) { + return authentications.getIfPresent(userKey); + } + + @SneakyThrows(ExecutionException.class) + public AuthenticationTask getOrCreateTask(@Nonnull String userKey) { + return authentications.get(userKey); + } + + @SuppressWarnings("unused") // GeyserConnect + public void setStoreServerInformation() { + storeServerInformation = true; + } + + public class AuthenticationTask { + private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS); + + @Getter + private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + private final String userKey; + private final long timeoutMs; + + private long remainingTimeMs; + + @Setter + private boolean online = true; + + @Getter + private final CompletableFuture authentication; + + @Getter + private volatile Throwable loginException; + + private AuthenticationTask(String userKey, long timeoutMs) { + this.userKey = userKey; + this.timeoutMs = timeoutMs; + this.remainingTimeMs = timeoutMs; + + this.authentication = new CompletableFuture<>(); + this.authentication.whenComplete((r, ex) -> { + this.loginException = ex; + // avoid memory leak, in case player doesn't connect again + CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup); + }); + } + + public void resetTimer() { + this.remainingTimeMs = this.timeoutMs; + } + + public void cleanup() { + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Cleaning up authentication task for " + userKey); + } + authentications.invalidate(userKey); + } + + public CompletableFuture getCode(boolean offlineAccess) { + // Request the code + CompletableFuture code = CompletableFuture.supplyAsync(() -> tryGetCode(offlineAccess)); + // Once the code is received, continuously try to request the access token, profile, etc + code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); + return code; + } + + /** + * @param offlineAccess whether we want a refresh token for later use. + */ + private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException { + try { + return msaAuthenticationService.getAuthCode(offlineAccess); + } catch (RequestException e) { + throw new CompletionException(e); + } + } + + private void performLoginAttempt(long lastAttempt) { + CompletableFuture.runAsync(() -> { + try { + msaAuthenticationService.login(); + } catch (AuthPendingException e) { + long currentAttempt = System.currentTimeMillis(); + if (!online) { + // decrement timer only when player's offline + remainingTimeMs -= currentAttempt - lastAttempt; + if (remainingTimeMs <= 0L) { + // time's up + authentication.completeExceptionally(new TaskTimeoutException()); + cleanup(); + return; + } + } + // try again in 1 second + performLoginAttempt(currentAttempt); + return; + } catch (Exception e) { + authentication.completeExceptionally(e); + return; + } + // login successful + authentication.complete(msaAuthenticationService); + }, DELAYED_BY_ONE_SECOND); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{userKey='" + userKey + "'}"; + } + } + + @Getter + @Setter + public final class ProxyAuthenticationTask extends AuthenticationTask { + private String server; + private int port; + + private ProxyAuthenticationTask(String userKey, long timeoutMs) { + super(userKey, timeoutMs); + } + } + + /** + * @see PendingMicrosoftAuthentication + */ + public static class TaskTimeoutException extends Exception { + TaskTimeoutException() { + super("It took too long to authorize Geyser to access your Microsoft account. " + + "Please request new code and try again."); + } + } +} 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 802ee3ca0..99b7ae3af 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 b3601f6c3..07dd38491 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/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java index ec633beb3..d1127b0fa 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 @@ -32,6 +32,7 @@ import lombok.Setter; import org.geysermc.cumulus.form.SimpleForm; import org.geysermc.geyser.level.GeyserAdvancement; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -134,7 +135,7 @@ public class AdvancementsCache { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); - builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); + builder.button((color ? ChatColor.DARK_GREEN : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } @@ -252,10 +253,9 @@ public class AdvancementsCache { } public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { - String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { - return base + "5"; + return ChatColor.DARK_PURPLE; } - return base + "a"; // Used for types TASK and GOAL + return ChatColor.GREEN; // Used for types TASK and GOAL } } 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 feb1cf3a8..91d6b33d6 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 lombok.Setter; 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/session/cache/LodestoneCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java index f0cbbb189..05c2628df 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java @@ -30,9 +30,6 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; import org.geysermc.geyser.inventory.GeyserItemStack; import javax.annotation.Nullable; @@ -43,7 +40,7 @@ import java.util.WeakHashMap; * A temporary cache for lodestone information. * Bedrock requests the lodestone position information separately from the item. */ -public class LodestoneCache { +public final class LodestoneCache { /** * A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect * when a new item has been created; instead we can re-use already existing IDs @@ -121,8 +118,16 @@ public class LodestoneCache { } public @Nullable LodestonePos getPos(int id) { - // We should not need to check the activeLodestones map as Bedrock should already be aware of this ID - return this.lodestones.remove(id); + LodestonePos pos = this.lodestones.remove(id); + if (pos != null) { + return pos; + } + for (LodestonePos activePos : this.activeLodestones.values()) { + if (activePos.id == id) { + return activePos; + } + } + return null; } public void clear() { @@ -131,16 +136,7 @@ public class LodestoneCache { this.lodestones.clear(); } - @Getter - @AllArgsConstructor - @EqualsAndHashCode - public static class LodestonePos { - private final int id; - private final int x; - private final int y; - private final int z; - private final String dimension; - + public record LodestonePos(int id, int x, int y, int z, String dimension) { boolean equals(int x, int y, int z, String dimension) { return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java new file mode 100644 index 000000000..f26e1cce3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.*; + +public class SkullCache { + private final int maxVisibleSkulls; + private final boolean cullingEnabled; + + private final int skullRenderDistanceSquared; + + /** + * The time in milliseconds before unused skull entities are despawned + */ + private static final long CLEANUP_PERIOD = 10000; + + @Getter + private final Map skulls = new Object2ObjectOpenHashMap<>(); + + private final List inRangeSkulls = new ArrayList<>(); + + private final Deque unusedSkullEntities = new ArrayDeque<>(); + private int totalSkullEntities = 0; + + private final GeyserSession session; + + private Vector3f lastPlayerPosition; + + private long lastCleanup = System.currentTimeMillis(); + + public SkullCache(GeyserSession session) { + this.session = session; + this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls(); + this.cullingEnabled = this.maxVisibleSkulls != -1; + + // Normal skulls are not rendered beyond 64 blocks + int distance = Math.min(session.getGeyser().getConfig().getCustomSkullRenderDistance(), 64); + this.skullRenderDistanceSquared = distance * distance; + } + + public void putSkull(Vector3i position, String texturesProperty, int blockState) { + Skull skull = skulls.computeIfAbsent(position, Skull::new); + skull.texturesProperty = texturesProperty; + skull.blockState = blockState; + + if (skull.entity != null) { + skull.entity.updateSkull(skull); + } else { + if (!cullingEnabled) { + assignSkullEntity(skull); + return; + } + if (lastPlayerPosition == null) { + return; + } + skull.distanceSquared = position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); + if (skull.distanceSquared < skullRenderDistanceSquared) { + // Keep list in order + int i = Collections.binarySearch(inRangeSkulls, skull, Comparator.comparingInt(Skull::getDistanceSquared)); + if (i < 0) { // skull.distanceSquared is a new distance value + i = -i - 1; + } + inRangeSkulls.add(i, skull); + + if (i < maxVisibleSkulls) { + // Reassign entity from the farthest skull to this one + if (inRangeSkulls.size() > maxVisibleSkulls) { + freeSkullEntity(inRangeSkulls.get(maxVisibleSkulls)); + } + assignSkullEntity(skull); + } + } + } + } + + public void removeSkull(Vector3i position) { + Skull skull = skulls.remove(position); + if (skull != null) { + boolean hadEntity = skull.entity != null; + freeSkullEntity(skull); + + if (cullingEnabled) { + inRangeSkulls.remove(skull); + if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) { + // Reassign entity to the closest skull without an entity + assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1)); + } + } + } + } + + public void updateVisibleSkulls() { + if (cullingEnabled) { + // No need to recheck skull visibility for small movements + if (lastPlayerPosition != null && session.getPlayerEntity().getPosition().distanceSquared(lastPlayerPosition) < 4) { + return; + } + lastPlayerPosition = session.getPlayerEntity().getPosition(); + + inRangeSkulls.clear(); + for (Skull skull : skulls.values()) { + skull.distanceSquared = skull.position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); + if (skull.distanceSquared > skullRenderDistanceSquared) { + freeSkullEntity(skull); + } else { + inRangeSkulls.add(skull); + } + } + inRangeSkulls.sort(Comparator.comparingInt(Skull::getDistanceSquared)); + + for (int i = inRangeSkulls.size() - 1; i >= 0; i--) { + if (i < maxVisibleSkulls) { + assignSkullEntity(inRangeSkulls.get(i)); + } else { + freeSkullEntity(inRangeSkulls.get(i)); + } + } + } + + // Occasionally clean up unused entities as we want to keep skull + // entities around for later use, to reduce "player" pop-in + if ((System.currentTimeMillis() - lastCleanup) > CLEANUP_PERIOD) { + lastCleanup = System.currentTimeMillis(); + for (SkullPlayerEntity entity : unusedSkullEntities) { + entity.despawnEntity(); + totalSkullEntities--; + } + unusedSkullEntities.clear(); + } + } + + private void assignSkullEntity(Skull skull) { + if (skull.entity != null) { + return; + } + if (unusedSkullEntities.isEmpty()) { + if (!cullingEnabled || totalSkullEntities < maxVisibleSkulls) { + // Create a new entity + long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); + skull.entity = new SkullPlayerEntity(session, geyserId); + skull.entity.spawnEntity(); + skull.entity.updateSkull(skull); + totalSkullEntities++; + } + } else { + // Reuse an entity + skull.entity = unusedSkullEntities.removeFirst(); + skull.entity.updateSkull(skull); + } + } + + private void freeSkullEntity(Skull skull) { + if (skull.entity != null) { + skull.entity.free(); + unusedSkullEntities.addFirst(skull.entity); + skull.entity = null; + } + } + + public void clear() { + skulls.clear(); + inRangeSkulls.clear(); + unusedSkullEntities.clear(); + totalSkullEntities = 0; + lastPlayerPosition = null; + } + + @RequiredArgsConstructor + @Data + public static class Skull { + private String texturesProperty; + private int blockState; + private SkullPlayerEntity entity; + + private final Vector3i position; + private int distanceSquared; + } +} 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 549b2dbee..d46a39616 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 @@ -28,15 +28,19 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.ParametersAreNonnullByDefault; import java.util.Map; /** * Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here * will remain empty, matching Java Edition behavior. */ +@ParametersAreNonnullByDefault public class TagCache { /* Blocks */ private IntList leaves; @@ -52,16 +56,19 @@ public class TagCache { private IntList requiresDiamondTool; /* Items */ + private IntList axolotlTemptItems; + private IntList fishes; private IntList flowers; private IntList foxFood; private IntList piglinLoved; + private IntList smallFlowers; public TagCache() { // Ensure all lists are non-null clear(); } - public void loadPacket(ClientboundUpdateTagsPacket packet) { + public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) { Map blockTags = packet.getTags().get("minecraft:block"); this.leaves = IntList.of(blockTags.get("minecraft:leaves")); this.wool = IntList.of(blockTags.get("minecraft:wool")); @@ -76,9 +83,19 @@ public class TagCache { this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); Map itemTags = packet.getTags().get("minecraft:item"); + this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items")); + this.fishes = IntList.of(itemTags.get("minecraft:fishes")); this.flowers = IntList.of(itemTags.get("minecraft:flowers")); this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); + this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers")); + + // Hack btw + boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1; + session.setEmulatePost1_14Logic(emulatePost1_14Logic); + if (session.getGeyser().getLogger().isDebug()) { + session.getGeyser().getLogger().debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic); + } } public void clear() { @@ -94,9 +111,20 @@ public class TagCache { this.requiresIronTool = IntLists.emptyList(); this.requiresDiamondTool = IntLists.emptyList(); + this.axolotlTemptItems = IntLists.emptyList(); + this.fishes = IntLists.emptyList(); this.flowers = IntLists.emptyList(); this.foxFood = IntLists.emptyList(); this.piglinLoved = IntLists.emptyList(); + this.smallFlowers = IntLists.emptyList(); + } + + public boolean isAxolotlTemptItem(ItemMapping itemMapping) { + return axolotlTemptItems.contains(itemMapping.getJavaId()); + } + + public boolean isFish(GeyserItemStack itemStack) { + return fishes.contains(itemStack.getJavaId()); } public boolean isFlower(ItemMapping mapping) { @@ -111,6 +139,10 @@ public class TagCache { return piglinLoved.contains(mapping.getJavaId()); } + public boolean isSmallFlower(GeyserItemStack itemStack) { + return smallFlowers.contains(itemStack.getJavaId()); + } + public boolean isAxeEffective(BlockMapping blockMapping) { return axeEffective.contains(blockMapping.getJavaBlockId()); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java index 00a080d8b..09e7e9234 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector2d; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.EntityDefinitions; @@ -38,7 +37,6 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import javax.annotation.Nonnull; -import java.util.Collections; public class WorldBorder { private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D; @@ -131,11 +129,26 @@ public class WorldBorder { } /** - * @return true as long the entity is within the world limits. + * @return true as long as the player entity is within the world limits. */ public boolean isInsideBorderBoundaries() { - Vector3f entityPosition = session.getPlayerEntity().getPosition(); - return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ; + return isInsideBorderBoundaries(session.getPlayerEntity().getPosition()); + } + + public boolean isInsideBorderBoundaries(Vector3f position) { + return position.getX() > minX && position.getX() < maxX && position.getZ() > minZ && position.getZ() < maxZ; + } + + private static final int CLOSE_TO_BORDER = 5; + + /** + * @return if the player is close to the border boundaries. Used to always indicate a border even if there is no + * warning blocks set. + */ + public boolean isCloseToBorderBoundaries() { + Vector3f position = session.getPlayerEntity().getPosition(); + return !(position.getX() > minX + CLOSE_TO_BORDER && position.getX() < maxX - CLOSE_TO_BORDER + && position.getZ() > minZ + CLOSE_TO_BORDER && position.getZ() < maxZ - CLOSE_TO_BORDER); } /** @@ -245,16 +258,16 @@ public class WorldBorder { float particlePosY = entityPosition.getY(); float particlePosZ = entityPosition.getZ(); - if (entityPosition.getX() > warningMaxX) { + if (entityPosition.getX() > Math.min(warningMaxX, maxX - CLOSE_TO_BORDER)) { drawWall(Vector3f.from(maxX, particlePosY, particlePosZ), true); } - if (entityPosition.getX() < warningMinX) { + if (entityPosition.getX() < Math.max(warningMinX, minX + CLOSE_TO_BORDER)) { drawWall(Vector3f.from(minX, particlePosY, particlePosZ), true); } - if (entityPosition.getZ() > warningMaxZ) { + if (entityPosition.getZ() > Math.min(warningMaxZ, maxZ - CLOSE_TO_BORDER)) { drawWall(Vector3f.from(particlePosX, particlePosY, maxZ), false); } - if (entityPosition.getZ() < warningMinZ) { + if (entityPosition.getZ() < Math.max(warningMinZ, minZ + CLOSE_TO_BORDER)) { drawWall(Vector3f.from(particlePosX, particlePosY, minZ), false); } } 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 aa4d10d04..17679ad3e 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 @@ -26,24 +26,36 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; +import org.geysermc.geyser.session.GeyserSession; -@Getter -public class WorldCache { +public final class WorldCache { private final GeyserSession session; + @Getter private final ScoreboardSession scoreboardSession; + @Getter private Scoreboard scoreboard; + @Getter @Setter private Difficulty difficulty = Difficulty.EASY; + /** + * Whether our cooldown changed the title time, and the true title times need to be re-sent. + */ + private boolean titleTimesNeedReset = false; + private int trueTitleFadeInTime; + private int trueTitleStayTime; + private int trueTitleFadeOutTime; + public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); scoreboardSession = new ScoreboardSession(session); + resetTitleTimes(false); } public void removeScoreboard() { @@ -58,4 +70,55 @@ public class WorldCache { int pps = scoreboardSession.getPacketsPerSecond(); return Math.max(pps, pendingPps); } + + public void markTitleTimesAsIncorrect() { + titleTimesNeedReset = true; + } + + /** + * Store the true active title times. + */ + public void setTitleTimes(int fadeInTime, int stayTime, int fadeOutTime) { + trueTitleFadeInTime = fadeInTime; + trueTitleStayTime = stayTime; + trueTitleFadeOutTime = fadeOutTime; + // The translator will sync this for us + titleTimesNeedReset = false; + } + + /** + * If needed, ensure that the Bedrock client will use the correct timings for titles. + */ + public void synchronizeCorrectTitleTimes() { + if (titleTimesNeedReset) { + forceSyncCorrectTitleTimes(); + } + } + + private void forceSyncCorrectTitleTimes() { + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setText(""); + titlePacket.setFadeInTime(trueTitleFadeInTime); + titlePacket.setStayTime(trueTitleStayTime); + titlePacket.setFadeOutTime(trueTitleFadeOutTime); + titlePacket.setPlatformOnlineId(""); + titlePacket.setXuid(""); + + session.sendUpstreamPacket(titlePacket); + titleTimesNeedReset = false; + } + + /** + * Reset the true active title times to the (Java Edition 1.18.2) defaults. + */ + public void resetTitleTimes(boolean clientSync) { + trueTitleFadeInTime = 10; + trueTitleStayTime = 70; + trueTitleFadeOutTime = 20; + + if (clientSync) { + forceSyncCorrectTitleTimes(); + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 66354b494..6794af498 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.skin; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -106,7 +105,7 @@ public class FakeHeadProvider { session.getPlayerWithCustomHeads().add(entity.getUuid()); - GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); SkinProvider.EXECUTOR_SERVICE.execute(() -> { try { @@ -182,7 +181,7 @@ public class FakeHeadProvider { @Getter @Setter private static class FakeHeadEntry { - private final GameProfile.Property texturesProperty; + private final String texturesProperty; private final String fakeHeadSkinUrl; private PlayerEntity entity; @@ -192,18 +191,7 @@ public class FakeHeadProvider { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FakeHeadEntry that = (FakeHeadEntry) o; - return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); - } - - private boolean equals(GameProfile.Property a, GameProfile.Property b) { - //TODO actually fix this in MCAuthLib - if (a == b) { - return true; - } - if (a == null || b == null) { - return false; - } - return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature()); + return Objects.equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); } @Override 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 37a263312..7a800890b 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; @@ -48,8 +49,6 @@ import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import static org.geysermc.geyser.util.PluginMessageUtils.getSkinChannel; - public final class FloodgateSkinUploader { private final ObjectMapper JACKSON = new ObjectMapper(); private final List skinQueue = new ArrayList<>(); @@ -126,7 +125,7 @@ public final class FloodgateSkinUploader { byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); - PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); } break; case LOG_MESSAGE: 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 4269110f5..4eb92c3ac 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; @@ -34,9 +33,9 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData; 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.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; @@ -54,7 +53,7 @@ public class SkinManager { * Builds a Bedrock player list entry from our existing, cached Bedrock skin information */ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { - GameProfileData data = GameProfileData.from(playerEntity.getProfile()); + GameProfileData data = GameProfileData.from(playerEntity); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl()); SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); @@ -65,8 +64,8 @@ public class SkinManager { return buildEntryManually( session, - playerEntity.getProfile().getId(), - playerEntity.getProfile().getName(), + playerEntity.getUuid(), + playerEntity.getUsername(), playerEntity.getGeyserId(), skin.getTextureUrl(), skin.getSkinData(), @@ -227,31 +226,31 @@ public class SkinManager { } /** - * Generate the GameProfileData from the given GameProfile + * Generate the GameProfileData from the given player entity * - * @param profile GameProfile to build the GameProfileData from + * @param entity entity to build the GameProfileData from * @return The built GameProfileData */ - public static GameProfileData from(GameProfile profile) { + public static GameProfileData from(PlayerEntity entity) { try { - GameProfile.Property skinProperty = profile.getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); - if (skinProperty == null) { + if (texturesProperty == null) { // Likely offline mode - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } - GameProfileData data = loadFromJson(skinProperty.getValue()); + GameProfileData data = loadFromJson(texturesProperty); if (data != null) { return data; } else { - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } catch (IOException exception) { - GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName()); + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { exception.printStackTrace(); } - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } @@ -280,14 +279,15 @@ public class SkinManager { * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this * is a Bedrock player. */ - private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) { + private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) { // Fallback to the offline mode of working it out - boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); + UUID uuid = entity.getUuid(); + boolean isAlex = (Math.abs(uuid.hashCode() % 2) == 1); 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) { - GeyserSession session = GeyserImpl.getInstance().connectionByUuid(profile.getId()); + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { skinUrl = session.getClientData().getSkinId(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 67364f5c6..43cf30b47 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.Tag; @@ -53,7 +52,6 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.*; import java.util.concurrent.*; import java.util.function.Predicate; @@ -115,10 +113,12 @@ public class SkinProvider { WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); + } + public static void registerCacheImageTask(GeyserImpl geyser) { // Schedule Daily Image Expiry if we are caching them - if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { - GeyserImpl.getInstance().getScheduledThread().scheduleAtFixedRate(() -> { + if (geyser.getConfig().getCacheImages() > 0) { + geyser.getScheduledThread().scheduleAtFixedRate(() -> { File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; @@ -155,7 +155,7 @@ public class SkinProvider { } public static CompletableFuture requestSkinData(PlayerEntity entity) { - SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile()); + SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) .thenApplyAsync(skinAndCape -> { @@ -544,12 +544,11 @@ public class SkinProvider { * @param skullOwner the CompoundTag of the skull with no textures * @return a completable GameProfile with textures included */ - public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { + public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { return CompletableFuture.supplyAsync(() -> { Tag uuidTag = skullOwner.get("Id"); String uuidToString = ""; JsonNode node; - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check if (!retrieveUuidFromInternet) { @@ -575,15 +574,12 @@ public class SkinProvider { // Get textures from UUID node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString); - List profileProperties = new ArrayList<>(); JsonNode properties = node.get("properties"); if (properties == null) { GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); return null; } - profileProperties.add(new GameProfile.Property("textures", node.get("properties").get(0).get("value").asText())); - gameProfile.setProperties(profileProperties); - return gameProfile; + return node.get("properties").get(0).get("value").asText(); } catch (Exception e) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) { e.printStackTrace(); @@ -599,6 +595,8 @@ public class SkinProvider { HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); BufferedImage image = ImageIO.read(con.getInputStream()); if (image == null) throw new NullPointerException(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 009d98c87..58054e9c5 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -50,7 +50,7 @@ public class SkullSkinManager extends SkinManager { public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, Consumer skinConsumer) { - GameProfileData data = GameProfileData.from(entity.getProfile()); + GameProfileData data = GameProfileData.from(entity); SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) .whenCompleteAsync((skin, throwable) -> { diff --git a/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java new file mode 100644 index 000000000..fdce1f879 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.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.text; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; +import net.kyori.adventure.util.Codec; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSerializer { + private final HoverEvent.ShowEntity dummyShowEntity; + private final HoverEvent.ShowItem dummyShowItem; + + public DummyLegacyHoverEventSerializer() { + dummyShowEntity = HoverEvent.ShowEntity.of(Key.key("geysermc", "dummyshowitem"), + UUID.nameUUIDFromBytes("entitiesareprettyneat".getBytes(StandardCharsets.UTF_8))); + dummyShowItem = HoverEvent.ShowItem.of(Key.key("geysermc", "dummyshowentity"), 0); + } + + @Override + public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) { + return dummyShowItem; + } + + @Override + public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input, + Codec.Decoder componentDecoder) { + return dummyShowEntity; + } + + @Override + public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) { + return Component.empty(); + } + + @Override + public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, + Codec.Encoder componentEncoder) { + return Component.empty(); + } +} 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 da6ea4dc0..86e015c0f 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -28,10 +28,10 @@ package org.geysermc.geyser.text; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.MessageFormat; import java.util.HashMap; import java.util.Locale; @@ -116,12 +116,22 @@ public class GeyserLocale { return locale; } + Properties localeProp = new Properties(); + + File localLanguage; + Path localFolder = bootstrap.getConfigFolder().resolve("languages"); + if (Files.exists(localFolder)) { + localLanguage = localFolder.resolve(locale + ".properties").toFile(); + } else { + localLanguage = null; + } + boolean validLocalLanguage = localLanguage != null && localLanguage.exists(); + InputStream localeStream = bootstrap.getResourceOrNull("languages/texts/" + locale + ".properties"); // Load the locale if (localeStream != null) { try { - Properties localeProp = new Properties(); try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { localeProp.load(reader); } catch (Exception e) { @@ -130,18 +140,37 @@ public class GeyserLocale { // Insert the locale into the mappings LOCALE_MAPPINGS.put(locale, localeProp); - return locale; } finally { try { localeStream.close(); } catch (IOException ignored) {} } } else { - if (GeyserImpl.getInstance() != null) { + if (GeyserImpl.getInstance() != null && !validLocalLanguage) { + // Don't warn on missing locales if a local file has been found GeyserImpl.getInstance().getLogger().warning("Missing locale: " + locale); } - return null; } + + // Load any language overrides that exist after, to override any strings that we just added + // By loading both, we ensure that if a language string doesn't exist in the custom properties folder, + // it's loaded from our jar + if (validLocalLanguage) { + try (InputStream stream = new FileInputStream(localLanguage)) { + localeProp.load(stream); + } catch (IOException e) { + String message = "Unable to load custom language override!"; + if (GeyserImpl.getInstance() != null) { + GeyserImpl.getInstance().getLogger().error(message, e); + } else { + System.err.println(message); + e.printStackTrace(); + } + } + + LOCALE_MAPPINGS.putIfAbsent(locale, localeProp); + } + return localeProp.isEmpty() ? null : locale; } /** 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 b09fcb7d4..e56586b14 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 @@ -27,7 +27,12 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeOptionalStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; @@ -35,12 +40,34 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; +import java.util.Objects; + public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE, "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } + @Override + protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_RECIPE_OPTIONAL; + } + + @Override + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst check + 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); + } + + return super.translateRequest(session, inventory, request); + } + @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { return switch (slotInfoData.getContainer()) { 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 19d9d6de5..f194d0d3f 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 @@ -38,17 +38,16 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntSets; import org.geysermc.geyser.inventory.BeaconContainer; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InventoryUtils; -import java.util.Collections; - public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public BeaconInventoryTranslator() { super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) { @@ -114,7 +113,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); session.sendDownstreamPacket(packet); - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet())); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java index ec3335f3c..61e2258b6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java @@ -37,6 +37,11 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE); } + @Override + public int getGridSize() { + return 9; + } + @Override public SlotType getSlotType(int javaSlot) { if (javaSlot == 0) { 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 97ece79d8..800b35901 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 @@ -27,23 +27,22 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; -import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; -import org.geysermc.geyser.inventory.EnchantingContainer; -import org.geysermc.geyser.inventory.GeyserEnchantOption; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import it.unimi.dsi.fastutil.ints.IntSets; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import java.util.Arrays; -import java.util.Collections; public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { public EnchantingInventoryTranslator() { @@ -130,7 +129,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla } ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot); session.sendDownstreamPacket(packet); - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet())); } @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 04d5fa3ad..6f4ca7ee4 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 @@ -26,12 +26,8 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -43,15 +39,13 @@ import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.CartographyContainer; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.inventory.click.ClickPlan; +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.session.GeyserSession; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator; @@ -119,6 +113,13 @@ public abstract class InventoryTranslator { public abstract SlotType getSlotType(int javaSlot); public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory); + /** + * Used for crafting-related transactions. Will override in PlayerInventoryTranslator and CraftingInventoryTranslator. + */ + public int getGridSize() { + return -1; + } + /** * Should be overwritten in cases where specific inventories should reject an item being in a specific spot. * For examples, looms use this to reject items that are dyes in Bedrock but not in Java. @@ -143,11 +144,11 @@ public abstract class InventoryTranslator { /** * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return rejectRequest(request); } - public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + public final void translateRequests(GeyserSession session, Inventory inventory, List requests) { boolean refresh = false; ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); for (ItemStackRequest request : requests) { @@ -199,10 +200,6 @@ public abstract class InventoryTranslator { case PLACE: { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { - if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT && - transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { - return rejectRequest(request, false); - } if (session.getGeyser().getConfig().isDebugMode()) { session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name()); dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); @@ -212,17 +209,19 @@ public abstract class InventoryTranslator { int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); + boolean isSourceCursor = isCursor(transferAction.getSource()); + boolean isDestCursor = isCursor(transferAction.getDestination()); if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), - isCursor(transferAction.getSource()) ? -1 : sourceSlot, - transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) { + isSourceCursor ? -1 : sourceSlot, + transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) { // This item would not be here in Java return rejectRequest(request, false); } - if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? + if (isSourceCursor && isDestCursor) { //??? return rejectRequest(request); - } else if (isCursor(transferAction.getSource())) { //releasing cursor + } else if (isSourceCursor) { //releasing cursor int sourceAmount = cursor.getAmount(); if (transferAction.getCount() == sourceAmount) { //release all plan.add(Click.LEFT, destSlot); @@ -231,7 +230,7 @@ public abstract class InventoryTranslator { plan.add(Click.RIGHT, destSlot); } } - } else if (isCursor(transferAction.getDestination())) { //picking up into cursor + } else if (isDestCursor) { //picking up into cursor GeyserItemStack sourceItem = plan.getItem(sourceSlot); int sourceAmount = sourceItem.getAmount(); if (cursor.isEmpty()) { //picking up into empty cursor @@ -313,18 +312,7 @@ public abstract class InventoryTranslator { if (!isSourceCursor && destination.getContainer() == ContainerSlotType.HOTBAR || destination.getContainer() == ContainerSlotType.HOTBAR_AND_INVENTORY) { // Tell the server we're pressing one of the hotbar keys to save clicks - Click click = switch (destination.getSlot()) { - case 0 -> Click.SWAP_TO_HOTBAR_1; - case 1 -> Click.SWAP_TO_HOTBAR_2; - case 2 -> Click.SWAP_TO_HOTBAR_3; - case 3 -> Click.SWAP_TO_HOTBAR_4; - case 4 -> Click.SWAP_TO_HOTBAR_5; - case 5 -> Click.SWAP_TO_HOTBAR_6; - case 6 -> Click.SWAP_TO_HOTBAR_7; - case 7 -> Click.SWAP_TO_HOTBAR_8; - case 8 -> Click.SWAP_TO_HOTBAR_9; - default -> null; - }; + Click click = InventoryUtils.getClickForHotbarSwap(destination.getSlot()); if (click != null) { plan.add(click, sourceSlot); break; @@ -442,6 +430,8 @@ public abstract class InventoryTranslator { int leftover = 0; ClickPlan plan = new ClickPlan(session, this, inventory); + // Track all the crafting table slots to report back the contents of the slots after crafting + IntSet affectedSlots = new IntOpenHashSet(); for (StackRequestActionData action : request.getActions()) { switch (action.getType()) { case CRAFT_RECIPE: { @@ -473,6 +463,7 @@ public abstract class InventoryTranslator { return rejectRequest(request); } craftState = CraftState.INGREDIENTS; + affectedSlots.add(bedrockSlotToJava(((ConsumeStackRequestActionData) action).getSource())); break; } case TAKE: @@ -533,23 +524,17 @@ public abstract class InventoryTranslator { } } plan.execute(false); - return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + affectedSlots.addAll(plan.getAffectedSlots()); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - int gridSize; - int gridDimensions; - if (this instanceof PlayerInventoryTranslator) { - gridSize = 4; - gridDimensions = 2; - } else if (this instanceof CraftingInventoryTranslator) { - gridSize = 9; - gridDimensions = 3; - } else { + final int gridSize = getGridSize(); + if (gridSize == -1) { return rejectRequest(request); } + int gridDimensions = gridSize == 4 ? 2 : 3; - Recipe recipe; Ingredient[] ingredients = new Ingredient[0]; ItemStack output = null; int recipeWidth = 0; @@ -578,7 +563,7 @@ public abstract class InventoryTranslator { craftState = CraftState.RECIPE_ID; int recipeId = autoCraftAction.getRecipeNetworkId(); - recipe = session.getCraftingRecipes().get(recipeId); + GeyserRecipe recipe = session.getCraftingRecipes().get(recipeId); if (recipe == null) { return rejectRequest(request); } @@ -592,24 +577,21 @@ public abstract class InventoryTranslator { } } - switch (recipe.getType()) { - case CRAFTING_SHAPED -> { - ShapedRecipeData shapedData = (ShapedRecipeData) recipe.getData(); - ingredients = shapedData.getIngredients(); - recipeWidth = shapedData.getWidth(); - output = shapedData.getResult(); - if (shapedData.getWidth() > gridDimensions || shapedData.getHeight() > gridDimensions) { - return rejectRequest(request); - } + if (recipe.isShaped()) { + GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe; + ingredients = shapedRecipe.ingredients(); + recipeWidth = shapedRecipe.width(); + output = shapedRecipe.result(); + if (recipeWidth > gridDimensions || shapedRecipe.height() > gridDimensions) { + return rejectRequest(request); } - case CRAFTING_SHAPELESS -> { - ShapelessRecipeData shapelessData = (ShapelessRecipeData) recipe.getData(); - ingredients = shapelessData.getIngredients(); - recipeWidth = gridDimensions; - output = shapelessData.getResult(); - if (ingredients.length > gridSize) { - return rejectRequest(request); - } + } else { + GeyserShapelessRecipe shapelessRecipe = (GeyserShapelessRecipe) recipe; + ingredients = shapelessRecipe.ingredients(); + recipeWidth = gridDimensions; + output = shapelessRecipe.result(); + if (ingredients.length > gridSize) { + return rejectRequest(request); } } break; @@ -733,7 +715,7 @@ public abstract class InventoryTranslator { /** * Handled in {@link PlayerInventoryTranslator} */ - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return rejectRequest(request); } @@ -768,14 +750,14 @@ public abstract class InventoryTranslator { } } - public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { + protected static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); } /** * Reject an incorrect ItemStackRequest. */ - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { + protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { return rejectRequest(request, true); } @@ -785,7 +767,7 @@ public abstract class InventoryTranslator { * @param throwError whether this request was truly erroneous (true), or known as an outcome and should not be treated * as bad (false). */ - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { + protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) { new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); } @@ -860,9 +842,12 @@ public abstract class InventoryTranslator { return -1; } - public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) { + protected final List makeContainerEntries(GeyserSession session, Inventory inventory, IntSet affectedSlots) { Map> containerMap = new HashMap<>(); - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); List list = containerMap.computeIfAbsent(bedrockSlot.container(), k -> new ArrayList<>()); list.add(makeItemEntry(session, bedrockSlot.slot(), inventory.getItem(slot))); @@ -879,7 +864,7 @@ public abstract class InventoryTranslator { return containerEntries; } - public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { + private static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { ItemStackResponsePacket.ItemEntry itemEntry; if (!itemStack.isEmpty()) { // As of 1.16.210: Bedrock needs confirmation on what the current item durability is. 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 a862a7e0d..a7b736d72 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 @@ -41,13 +41,13 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; -import org.geysermc.geyser.translator.inventory.item.BannerTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator; import java.util.Collections; import java.util.List; 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 6b63056a3..248bd35b7 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 @@ -26,14 +26,18 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.AutoCraftRecipeStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.inventory.Inventory; @@ -44,6 +48,9 @@ import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.inventory.updater.InventoryUpdater; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.concurrent.TimeUnit; public class MerchantInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -131,11 +138,63 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } } + @Override + public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + return super.translateCraftingRequest(session, inventory, request); + } + + // Behavior as of 1.18.10. + // We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to + // store a map of net ID to trade index on our end. + int tradeChoice = ((CraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1; + return handleTrade(session, inventory, request, tradeChoice); + } + @Override public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // We're not crafting here - // Called at least by consoles when pressing a trade option button - return translateRequest(session, inventory, request); + if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + // We're not crafting here + // Called at least by consoles when pressing a trade option button + return translateRequest(session, inventory, request); + } + + // 1.18.10 update - seems impossible to call without consoles/controller input + // We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to + // store a map of net ID to trade index on our end. + int tradeChoice = ((AutoCraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1; + return handleTrade(session, inventory, request, tradeChoice); + } + + private ItemStackResponsePacket.Response handleTrade(GeyserSession session, Inventory inventory, ItemStackRequest request, int tradeChoice) { + ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice); + session.sendDownstreamPacket(packet); + + if (session.isEmulatePost1_14Logic()) { + // 1.18 Java cooperates nicer than older versions + if (inventory instanceof MerchantContainer merchantInventory) { + merchantInventory.onTradeSelected(session, tradeChoice); + } + return translateRequest(session, inventory, request); + } else { + // 1.18 servers works fine without a workaround, but ViaVersion needs to work around 1.13 servers, + // so we need to work around that with the delay. Specifically they force a window refresh after a + // trade packet has been sent. + session.scheduleInEventLoop(() -> { + if (inventory instanceof MerchantContainer merchantInventory) { + merchantInventory.onTradeSelected(session, tradeChoice); + // Ignore output since we don't want to send a delayed response packet back to the client + translateRequest(session, inventory, request); + + // Resync items once more + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + } + }, 100, TimeUnit.MILLISECONDS); + + // Revert this request, for now + return rejectRequest(request); + } } @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 04de68a1e..e2349e5a5 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 @@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import org.geysermc.geyser.inventory.*; @@ -55,6 +56,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator { super(46); } + @Override + public int getGridSize() { + return 4; + } + @Override public void updateInventory(GeyserSession session, Inventory inventory) { updateCraftingGrid(session, inventory); @@ -370,14 +376,17 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } } } - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); sendCreativeAction(session, inventory, slot); } return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } @Override - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { ItemStack javaCreativeItem = null; IntSet affectedSlots = new IntOpenHashSet(); CraftState craftState = CraftState.START; @@ -478,7 +487,10 @@ public class PlayerInventoryTranslator extends InventoryTranslator { return rejectRequest(request); } } - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); sendCreativeAction(session, inventory, slot); } return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); 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 ae25a9ffd..e0e2e27bd 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 @@ -31,20 +31,14 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; -import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.inventory.StonecutterContainer; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.*; +import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.session.GeyserSession; public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { public StonecutterInventoryTranslator() { @@ -53,31 +47,26 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { - // First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID - return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE; + return action.getType() == StackRequestActionType.CRAFT_RECIPE; } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // TODO: Also surely to change in the future - StackRequestActionData data = request.getActions()[1]; - if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst + CraftRecipeStackRequestActionData data = (CraftRecipeStackRequestActionData) request.getActions()[0]; + + // Look up all possible options of cutting from this ID + GeyserStonecutterData craftingData = session.getStonecutterRecipes().get(data.getRecipeNetworkId()); + if (craftingData == null) { return rejectRequest(request); } StonecutterContainer container = (StonecutterContainer) inventory; - // Get the ID of the item we are cutting - int id = inventory.getItem(0).getJavaId(); - // Look up all possible options of cutting from this ID - IntList results = session.getStonecutterRecipes().get(id); - if (results == null) { - return rejectRequest(request); - } - - ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0], session.getItemMappings()); - int button = results.indexOf(javaOutput.getId()); + int button = craftingData.buttonId(); // If we've already pressed the button with this item, no need to press it again! if (container.getStonecutterButton() != button) { + ItemStack javaOutput = craftingData.output(); + // Getting the index of the item in the Java stonecutter list ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), button); session.sendDownstreamPacket(packet); 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 65f26542f..4c2978082 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 @@ -26,42 +26,45 @@ package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @ItemRemapper public class CompassTranslator extends ItemTranslator { - private final List appliedItems; - - public CompassTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) - .collect(Collectors.toList()); + @Override + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + if (isLodestoneCompass(itemStack.getNbt())) { + // NBT will be translated in nbt/LodestoneCompassTranslator if applicable + return super.translateToBedrock(itemStack, mappings.getLodestoneCompass(), mappings); + } + return super.translateToBedrock(itemStack, mapping, mappings); } @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); - - Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); - if (lodestoneTag instanceof ByteTag) { - // Get the fake lodestonecompass entry - mapping = mappings.getStoredItems().lodestoneCompass(); - // NBT will be translated in nbt/LodestoneCompassTranslator + protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { + if (isLodestoneCompass(nbt)) { + return mappings.getLodestoneCompass(); } + return super.getItemMapping(javaId, nbt, mappings); + } - return super.translateToBedrock(itemStack, mapping, mappings); + private boolean isLodestoneCompass(CompoundTag nbt) { + if (nbt != null) { + Tag lodestoneTag = nbt.get("LodestoneTracked"); + return lodestoneTag instanceof ByteTag; + } + return false; } @Override @@ -76,6 +79,9 @@ public class CompassTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.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/FilledMapTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java new file mode 100644 index 000000000..3dfa2d82f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION 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.mc.protocol.data.game.entity.metadata.ItemStack; +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.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; + +import java.util.Collections; +import java.util.List; + +@ItemRemapper +public class FilledMapTranslator extends ItemTranslator { + + @Override + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); + CompoundTag nbt = itemStack.getNbt(); + if (nbt != null && nbt.get("display") instanceof CompoundTag display) { + // Note: damage 5 treasure map, 6 ??? + Tag mapColor = display.get("MapColor"); + if (mapColor != null && mapColor.getValue() instanceof Number color) { + // Java Edition allows any color; Bedrock only allows some. So let's take what colors we can get + switch (color.intValue()) { + case 3830373 -> builder.damage(3); // Ocean Monument + case 5393476 -> builder.damage(4); // Woodland explorer + } + } + } + return builder; + } + + @Override + public List getAppliedItems() { + return Collections.singletonList( + Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getMapping("minecraft:filled_map") + ); + } +} 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 6a2182279..539d20207 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 @@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -46,6 +47,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.FileUtils; import javax.annotation.Nonnull; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.stream.Collectors; @@ -70,11 +72,11 @@ public abstract class ItemTranslator { try { if (NbtItemStackTranslator.class.isAssignableFrom(clazz)) { - NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.newInstance(); + NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.getDeclaredConstructor().newInstance(); loadedNbtItemTranslators.put(nbtItemTranslator, priority); continue; } - ItemTranslator itemStackTranslator = (ItemTranslator) clazz.newInstance(); + ItemTranslator itemStackTranslator = (ItemTranslator) clazz.getDeclaredConstructor().newInstance(); List appliedItems = itemStackTranslator.getAppliedItems(); for (ItemMapping item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); @@ -86,7 +88,7 @@ public abstract class ItemTranslator { } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } - } catch (InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName()); } } @@ -157,18 +159,13 @@ public abstract class ItemTranslator { nbt = translateDisplayProperties(session, nbt, bedrockItem); if (session.isAdvancedTooltips()) { - nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.getLocale()); + nbt = addAdvancedTooltips(nbt, bedrockItem, session.getLocale()); } ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); - ItemData.Builder builder; - ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); - if (itemStackTranslator != null) { - builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); - } else { - builder = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); - } + ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(bedrockItem.getJavaId(), DEFAULT_TRANSLATOR); + ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); if (bedrockItem.isBlock()) { builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } @@ -263,6 +260,19 @@ public abstract class ItemTranslator { return canModifyBedrock; } + /** + * Given an item stack, determine the item mapping that should be applied to Bedrock players. + */ + @Nonnull + public static ItemMapping getBedrockItemMapping(GeyserSession session, @Nonnull GeyserItemStack itemStack) { + if (itemStack.isEmpty()) { + return ItemMapping.AIR; + } + int javaId = itemStack.getJavaId(); + return ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) + .getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings()); + } + private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override public List getAppliedItems() { @@ -270,7 +280,7 @@ public abstract class ItemTranslator { } }; - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack == null) { // Return, essentially, air return ItemData.builder(); @@ -293,9 +303,16 @@ public abstract class ItemTranslator { return new ItemStack(mapping.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag())); } + /** + * Used for initialization only and only called once. + */ public abstract List getAppliedItems(); - public NbtMap translateNbtToBedrock(CompoundTag tag) { + protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { + return mappings.getMapping(javaId); + } + + protected NbtMap translateNbtToBedrock(CompoundTag tag) { NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { @@ -374,7 +391,7 @@ public abstract class ItemTranslator { return null; } - public CompoundTag translateToJavaNBT(String name, NbtMap tag) { + private CompoundTag translateToJavaNBT(String name, NbtMap tag) { CompoundTag javaTag = new CompoundTag(name); Map javaValue = javaTag.getValue(); if (tag != null && !tag.isEmpty()) { @@ -469,9 +486,8 @@ public abstract class ItemTranslator { public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping, char translationColor) { boolean hasCustomName = false; if (tag != null) { - CompoundTag display = tag.get("display"); - if (display != null && display.contains("Name")) { - String name = ((StringTag) display.get("Name")).getValue(); + if (tag.get("display") instanceof CompoundTag display && display.get("Name") instanceof StringTag tagName) { + String name = tagName.getValue(); // Get the translated name and prefix it with a reset char name = MessageTranslator.convertMessageLenient(name, session.getLocale()); @@ -491,8 +507,10 @@ public abstract class ItemTranslator { if (tag == null) { tag = new CompoundTag(""); } - CompoundTag display = tag.get("display"); - if (display == null) { + CompoundTag display; + if (tag.get("display") instanceof CompoundTag oldDisplay) { + display = oldDisplay; + } else { display = new CompoundTag("display"); // Add to the new root tag tag.put(display); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java index 31b0aa70e..bfa7ebc2e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java @@ -29,12 +29,12 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; -public class NbtItemStackTranslator { +public abstract class NbtItemStackTranslator { /** * Translate the item NBT to Bedrock * @param session the client's current session - * @param itemTag the item's CompoundTag + * @param itemTag the item's CompoundTag (cloned from Geyser's cached copy) * @param mapping Geyser's item mapping */ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { 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 272092da6..bf16af38f 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 @@ -36,25 +36,15 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @ItemRemapper public class PotionTranslator extends ItemTranslator { - private final List appliedItems; - - public PotionTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) - .collect(Collectors.toList()); - } - @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); Tag potionTag = itemStack.getNbt().get("Potion"); if (potionTag instanceof StringTag) { @@ -84,6 +74,9 @@ public class PotionTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.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 4925d3e69..d831ce586 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 @@ -36,30 +36,18 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @ItemRemapper 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()) .getMapping("minecraft:tipped_arrow") .getJavaId(); - public TippedArrowTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().contains("arrow") - && !entry.getJavaIdentifier().contains("spectral")) - .collect(Collectors.toList()); - } - @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { // We're only concerned about minecraft:arrow when translating Bedrock -> Java return super.translateToBedrock(itemStack, mapping, mappings); @@ -93,6 +81,10 @@ public class TippedArrowTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.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/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java similarity index 59% rename from core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index 3c566e76c..ed4865411 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/nbt/BannerTranslator.java @@ -23,28 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.translator.inventory.item; +package org.geysermc.geyser.translator.inventory.item.nbt; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; -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.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import javax.annotation.Nonnull; +import java.util.*; import java.util.stream.Collectors; @ItemRemapper -public class BannerTranslator extends ItemTranslator { +public class BannerTranslator extends NbtItemStackTranslator { /** * Holds what a Java ominous banner pattern looks like. * @@ -79,10 +76,8 @@ public class BannerTranslator extends ItemTranslator { } public BannerTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() + appliedItems = Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) .collect(Collectors.toList()); } @@ -96,10 +91,7 @@ public class BannerTranslator extends ItemTranslator { public static NbtList convertBannerPattern(ListTag patterns) { List tagsList = new ArrayList<>(); for (Tag patternTag : patterns.getValue()) { - NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); - if (newPatternTag != null) { - tagsList.add(newPatternTag); - } + tagsList.add(getBedrockBannerPattern((CompoundTag) patternTag)); } return new NbtList<>(NbtType.COMPOUND, tagsList); @@ -111,35 +103,14 @@ public class BannerTranslator extends ItemTranslator { * @param pattern Java edition pattern nbt * @return The Bedrock edition format pattern nbt */ - public static NbtMap getBedrockBannerPattern(CompoundTag pattern) { - String patternName = (String) pattern.get("Pattern").getValue(); - - // Return null if its the globe pattern as it doesn't exist on bedrock - if (patternName.equals("glb")) { - return null; - } - + @Nonnull + private static NbtMap getBedrockBannerPattern(CompoundTag pattern) { return NbtMap.builder() .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", patternName) + .putString("Pattern", (String) pattern.get("Pattern").getValue()) .build(); } - /** - * Convert a list of patterns from Bedrock nbt to Java nbt - * - * @param patterns The patterns to convert - * @return The new converted patterns - */ - public static ListTag convertBannerPattern(List patterns) { - List tagsList = new ArrayList<>(); - for (NbtMap patternTag : patterns) { - tagsList.add(getJavaBannerPattern(patternTag)); - } - - return new ListTag("Patterns", tagsList); - } - /** * Convert the Bedrock edition banner pattern nbt to Java edition * @@ -154,64 +125,54 @@ public class BannerTranslator extends ItemTranslator { return new CompoundTag("", tags); } - @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { - if (itemStack.getNbt() == null) { - return super.translateToBedrock(itemStack, mapping, mappings); + /** + * Convert a list of patterns from Java nbt to Bedrock nbt, or vice versa (we just need to invert the color) + * + * @param patterns The patterns to convert + */ + private void invertBannerColors(ListTag patterns) { + for (Tag patternTag : patterns.getValue()) { + IntTag color = ((CompoundTag) patternTag).get("Color"); + color.setValue(15 - color.getValue()); } - - ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); - - CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { - ListTag patterns = blockEntityTag.get("Patterns"); - - NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack - if (patterns.equals(OMINOUS_BANNER_PATTERN)) { - // Remove the current patterns and set the ominous banner type - nbtBuilder.remove("Patterns"); - nbtBuilder.putInt("Type", 1); - } else { - nbtBuilder.put("Patterns", convertBannerPattern(patterns)); - } - - builder.tag(nbtBuilder.build()); - } - - return builder; } @Override - public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { - if (itemData.getTag() == null) { - return super.translateToJava(itemData, mapping, mappings); + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); + if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) { + if (patterns.equals(OMINOUS_BANNER_PATTERN)) { + // Remove the current patterns and set the ominous banner type + itemTag.put(new IntTag("Type", 1)); + } else { + invertBannerColors(patterns); + itemTag.put(patterns); + } + itemTag.remove("BlockEntityTag"); } + } - ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); - - NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Type", NbtType.INT) && nbtTag.getInt("Type") == 1) { + @Override + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + if (itemTag.get("Type") instanceof IntTag type && type.getValue() == 1) { // Ominous banner pattern - itemStack.getNbt().remove("Type"); + itemTag.remove("Type"); CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); blockEntityTag.put(OMINOUS_BANNER_PATTERN); - itemStack.getNbt().put(blockEntityTag); - } else if (nbtTag.containsKey("Patterns", NbtType.LIST)) { - List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); - + itemTag.put(blockEntityTag); + } else if (itemTag.get("Patterns") instanceof ListTag patterns) { CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - blockEntityTag.put(convertBannerPattern(patterns)); + invertBannerColors(patterns); + blockEntityTag.put(patterns); - itemStack.getNbt().put(blockEntityTag); - itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list + itemTag.put(blockEntityTag); + itemTag.remove("Patterns"); // Remove the old Bedrock patterns list } - - return itemStack; } @Override - public List getAppliedItems() { - return appliedItems; + public boolean acceptItem(ItemMapping mapping) { + return appliedItems.contains(mapping); } } 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 42cfc0439..a507d02cc 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 @@ -51,13 +51,11 @@ public class BasicItemTranslator extends NbtItemStackTranslator { } } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag == null) { + if (!(itemTag.get("display") instanceof CompoundTag displayTag)) { return; } - Tag loreTag = displayTag.get("Lore"); - if (loreTag instanceof ListTag listTag) { + if (displayTag.get("Lore") instanceof ListTag listTag) { List lore = new ArrayList<>(); for (Tag tag : listTag.getValue()) { if (!(tag instanceof StringTag)) continue; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 155435c79..cd6d5d6ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -121,23 +121,22 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { private CompoundTag remapEnchantment(CompoundTag tag) { - Tag javaEnchLvl = tag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) - return null; - Tag javaEnchId = tag.get("id"); if (!(javaEnchId instanceof StringTag)) return null; Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment while NBT item translating: " + javaEnchId.getValue()); return null; } + Tag javaEnchLvl = tag.get("lvl"); + CompoundTag bedrockTag = new CompoundTag(""); bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); - bedrockTag.put(new ShortTag("lvl", ((Number) javaEnchLvl.getValue()).shortValue())); + // If the tag cannot parse, Java Edition 1.18.2 sets to 0 + bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : (short) 0)); return bedrockTag; } 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 a202c3f81..ac9a0517a 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 @@ -126,11 +126,10 @@ public class BiomeTranslator { storage = new BlockStorage(bitArray, bedrockPalette); } else { storage = new BlockStorage(0); - BitArray bitArray = storage.getBitArray(); // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries for (int i = 0; i < 64; i++) { - int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i)); + int javaId = palette.idToState(biomeData.getStorage().get(i)); int x = i & 3; int y = (i >> 4) & 3; int z = (i >> 2) & 3; @@ -139,7 +138,9 @@ public class BiomeTranslator { int idx = storage.idFor(biomeId); // Convert biome coordinates into block coordinates // Bedrock expects a full 4096 blocks - multiplyIdToStorage(bitArray, idx, x, y, z); + // Implementation note: storage.getBitArray() must be called and not stored - if the palette + // grows, then the instance can change + multiplyIdToStorage(storage.getBitArray(), idx, x, y, z); } } return storage; diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java index cca103cb3..725a17e7a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java @@ -28,9 +28,10 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.geyser.translator.inventory.item.BannerTranslator; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator; @BlockEntity(type = BlockEntityType.BANNER) public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -45,8 +46,7 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement return; } - if (tag.contains("Patterns")) { - ListTag patterns = tag.get("Patterns"); + if (tag.get("Patterns") instanceof ListTag patterns) { if (patterns.equals(BannerTranslator.OMINOUS_BANNER_PATTERN)) { // This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly) // and tell the Bedrock client that this is an ominous banner @@ -56,8 +56,9 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement } } - if (tag.contains("CustomName")) { - builder.put("CustomName", tag.get("CustomName").getValue()); + Tag customName = tag.get("CustomName"); + if (customName != null) { + builder.put("CustomName", customName.getValue()); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java index 0ec7219c3..94760b66c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java @@ -26,7 +26,10 @@ package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; /** @@ -59,6 +62,18 @@ public interface BedrockOnlyBlockEntity extends RequiresBlockState { return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); + } else if (BlockStateValues.isCauldron(blockState)) { + // As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim) + return NbtMap.builder() + .putString("id", "Cauldron") + .putByte("isMovable", (byte) 0) + .putShort("PotionId", (short) -1) + .putShort("PotionType", (short) -1) + .putList("Items", NbtType.END, NbtList.EMPTY) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .build(); } return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java index a1e990138..bb036a1b0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java @@ -28,16 +28,25 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; +import org.geysermc.geyser.level.block.BlockStateValues; @BlockEntity(type = BlockEntityType.JIGSAW) -public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { +public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - builder.put("joint", ((StringTag) tag.get("joint")).getValue()); - builder.put("name", ((StringTag) tag.get("name")).getValue()); - builder.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + Tag jointTag = tag.get("joint"); + if (jointTag instanceof StringTag) { + builder.put("joint", ((StringTag) jointTag).getValue()); + } else { + // Tag is not present in at least 1.14.4 Paper + // Minecraft 1.18.1 deliberately has a fallback here, but not for any other value + builder.put("joint", BlockStateValues.getHorizontalFacingJigsaws().contains(blockState) ? "aligned" : "rollable"); + } + builder.put("name", getOrDefault(tag.get("name"), "")); + builder.put("target_pool", getOrDefault(tag.get("pool"), "")); builder.put("final_state", ((StringTag) tag.get("final_state")).getValue()); - builder.put("target", ((StringTag) tag.get("target")).getValue()); + builder.put("target", getOrDefault(tag.get("target"), "")); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 8fc8732f7..94e2d4767 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -25,27 +25,18 @@ package org.geysermc.geyser.translator.level.block.entity; -import com.github.steveice10.mc.auth.data.GameProfile; 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.StringTag; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinProvider; -import org.geysermc.geyser.skin.SkullSkinManager; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; @BlockEntity(type = BlockEntityType.SKULL) public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -62,7 +53,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements builder.put("SkullType", skullVariant); } - public static CompletableFuture getProfile(CompoundTag tag) { + private static CompletableFuture getTextures(CompoundTag tag) { CompoundTag owner = tag.get("SkullOwner"); if (owner != null) { CompoundTag properties = owner.get("Properties"); @@ -73,76 +64,23 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements ListTag textures = properties.get("textures"); LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); StringTag texture = (StringTag) tag1.get("Value"); - - List profileProperties = new ArrayList<>(); - - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); - profileProperties.add(new GameProfile.Property("textures", texture.getValue())); - gameProfile.setProperties(profileProperties); - return CompletableFuture.completedFuture(gameProfile); + return CompletableFuture.completedFuture(texture.getValue()); } return CompletableFuture.completedFuture(null); } - public static void spawnPlayer(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { - float x = posX + .5f; - float y = posY - .01f; - float z = posZ + .5f; - float rotation; - - byte floorRotation = BlockStateValues.getSkullRotation(blockState); - if (floorRotation == -1) { - // Wall skull - y += 0.25f; - rotation = BlockStateValues.getSkullWallDirections().get(blockState); - switch ((int) rotation) { - case 180 -> z += 0.24f; // North - case 0 -> z -= 0.24f; // South - case 90 -> x += 0.24f; // West - case 270 -> x -= 0.24f; // East - } - } else { - rotation = (180f + (floorRotation * 22.5f)) % 360; - } - + public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { Vector3i blockPosition = Vector3i.from(posX, posY, posZ); - Vector3f entityPosition = Vector3f.from(x, y, z); - - getProfile(tag).whenComplete((gameProfile, throwable) -> { - if (gameProfile == null) { + getTextures(tag).whenComplete((texturesProperty, throwable) -> { + if (texturesProperty == null) { session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); return; } - if (session.getEventLoop().inEventLoop()) { - spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState); + session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); } else { - session.executeInEventLoop(() -> spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState)); + session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState)); } }); } - - private static void spawnPlayer(GeyserSession session, GameProfile profile, Vector3i blockPosition, - Vector3f entityPosition, float rotation, int blockState) { - long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); - - SkullPlayerEntity existingSkull = session.getSkullCache().get(blockPosition); - if (existingSkull != null) { - // Ensure that two skulls can't spawn on the same point - existingSkull.despawnEntity(blockPosition); - } - - SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, profile, entityPosition, rotation, blockState); - - // Cache entity - session.getSkullCache().put(blockPosition, player); - - player.spawnEntity(); - - SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> { - // Delay to minimize split-second "player" pop-in - player.setFlag(EntityFlag.INVISIBLE, false); - player.updateBedrockMetadata(); - }, 250, TimeUnit.MILLISECONDS))); - } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index ac4a4bb2e..670e55785 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -48,8 +48,10 @@ public class BedrockAnimateTranslator extends PacketTranslator { switch (packet.getAction()) { case SWING_ARM -> // Delay so entity damage can be processed first - session.scheduleInEventLoop(() -> - session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)), + session.scheduleInEventLoop(() -> { + session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); + session.activateArmAnimationTicking(); + }, 25, TimeUnit.MILLISECONDS ); 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 93ce71a3d..1f2f29ea5 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 @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundSignUpdatePacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -41,12 +42,16 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator SignUtils.JAVA_CHARACTER_WIDTH_MAX) { @@ -111,7 +116,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator 1; + + if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) { + // Dropping an item that you don't have selected isn't supported in Java, but we can workaround it with an inventory hack + PlayerInventory inventory = session.getPlayerInventory(); + int hotbarSlot = inventory.getOffsetForHotbar(containerAction.getSlot()); + Click clickType = dropAll ? Click.DROP_ALL : Click.DROP_ONE; + Int2ObjectMap changedItem; + if (dropAll) { + inventory.setItem(hotbarSlot, GeyserItemStack.EMPTY, session); + changedItem = Int2ObjectMaps.singleton(hotbarSlot, null); + } else { + GeyserItemStack itemStack = inventory.getItem(hotbarSlot); + if (itemStack.isEmpty()) { + return; + } + itemStack.sub(1); + changedItem = Int2ObjectMaps.singleton(hotbarSlot, itemStack.getItemStack()); + } + ServerboundContainerClickPacket dropPacket = new ServerboundContainerClickPacket( + inventory.getId(), inventory.getStateId(), hotbarSlot, clickType.actionType, clickType.action, + inventory.getCursor().getItemStack(), changedItem); + session.sendDownstreamPacket(dropPacket); + return; + } + if (session.getPlayerInventory().getItemInHand().isEmpty()) { return; } - boolean dropAll = worldAction.getToItem().getCount() > 1; - ServerboundPlayerActionPacket dropAllPacket = new ServerboundPlayerActionPacket( + ServerboundPlayerActionPacket dropPacket = new ServerboundPlayerActionPacket( dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, BlockUtils.POSITION_ZERO, Direction.DOWN ); - session.sendDownstreamPacket(dropAllPacket); + session.sendDownstreamPacket(dropPacket); if (dropAll) { session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY); @@ -109,7 +139,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); + final Vector3i packetBlockPosition = packet.getBlockPosition(); + Vector3i blockPos = BlockUtils.getBlockPosition(packetBlockPosition, packet.getBlockFace()); if (session.getGeyser().getConfig().isDisableBedrockScaffolding()) { float yaw = session.getPlayerEntity().getYaw(); @@ -129,8 +160,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { restoreCorrectBlock(session, blockPos, packet); return; } + double clickPositionFullX = (double) packetBlockPosition.getX() + (double) packet.getClickPosition().getX(); + double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY(); + double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ(); + + // More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch + double clickDiffX = playerPosition.getX() - clickPositionFullX; + double clickDiffY = playerPosition.getY() - clickPositionFullY; + double clickDiffZ = playerPosition.getZ() - clickPositionFullZ; + if (((clickDiffX * clickDiffX) + (clickDiffY * clickDiffY) + (clickDiffZ * clickDiffZ)) > + (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } + + Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f); // Vanilla check if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0) - .distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { + .distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. restoreCorrectBlock(session, blockPos, packet); return; } + + // More recent vanilla check (as of 1.18.2) + double clickDistanceX = clickPositionFullX - blockCenter.getX(); + double clickDistanceY = clickPositionFullY - blockCenter.getY(); + double clickDistanceZ = clickPositionFullZ - blockCenter.getZ(); + if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } /* Block place checks end - client is good to go */ @@ -261,7 +311,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - if (packet.getActions().size() == 1 && packet.getLegacySlots().size() > 0) { - InventoryActionData actionData = packet.getActions().get(0); - LegacySetItemSlotData slotData = packet.getLegacySlots().get(0); - if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { - // The player is trying to swap out an armor piece that already has an item in it - // Java Edition does not allow this; let's revert it - session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); - } - } - // Handled when sneaking if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) { break; @@ -298,6 +338,43 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator legacySlots = packet.getLegacySlots(); + if (packet.getActions().size() == 1 && legacySlots.size() > 0) { + InventoryActionData actionData = packet.getActions().get(0); + LegacySetItemSlotData slotData = legacySlots.get(0); + if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { + // The player is trying to swap out an armor piece that already has an item in it + if (session.getGeyser().getConfig().isAlwaysQuickChangeArmor()) { + // Java doesn't know when a player is in its own inventory and not, so we + // can abuse this feature to send a swap inventory packet + int bedrockHotbarSlot = packet.getHotbarSlot(); + Click click = InventoryUtils.getClickForHotbarSwap(bedrockHotbarSlot); + if (click != null && slotData.getSlots().length != 0) { + Inventory playerInventory = session.getPlayerInventory(); + // Bedrock sends us the index of the slot in the armor container; armor in Java + // Edition is offset by 5 in the player inventory + int armorSlot = slotData.getSlots()[0] + 5; + GeyserItemStack armorSlotItem = playerInventory.getItem(armorSlot); + GeyserItemStack hotbarItem = playerInventory.getItem(playerInventory.getOffsetForHotbar(bedrockHotbarSlot)); + playerInventory.setItem(armorSlot, hotbarItem, session); + playerInventory.setItem(bedrockHotbarSlot, armorSlotItem, session); + + Int2ObjectMap changedSlots = new Int2ObjectOpenHashMap<>(2); + changedSlots.put(armorSlot, hotbarItem.getItemStack()); + changedSlots.put(bedrockHotbarSlot, armorSlotItem.getItemStack()); + + ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( + playerInventory.getId(), playerInventory.getStateId(), armorSlot, + click.actionType, click.action, null, changedSlots); + session.sendDownstreamPacket(clickPacket); + } + } else { + // Disallowed; let's revert + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + } + } + } } case 2 -> { int blockState = session.getGameMode() == GameMode.CREATIVE ? @@ -361,47 +438,65 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator processEntityInteraction(session, packet, entity); // Interact + case 1 -> { // Attack + int entityId; if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) { // Redirects the attack to its body entity, this only happens when // attacking the underbelly of the ender dragon - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId() + 3, - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId() + 3; } else { - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId(), - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId(); } - break; + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entityId, + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + } } break; } } + private void processEntityInteraction(GeyserSession session, InventoryTransactionPacket packet, Entity entity) { + Vector3f entityPosition = entity.getPosition(); + if (!session.getWorldBorder().isInsideBorderBoundaries(entityPosition)) { + // No transaction is able to go through (as of Java Edition 1.18.1) + return; + } + + Vector3f clickPosition = packet.getClickPosition().sub(entityPosition); + boolean isSpectator = session.getGameMode() == GameMode.SPECTATOR; + for (Hand hand : EntityUtils.HANDS) { + session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(), + InteractAction.INTERACT_AT, clickPosition.getX(), clickPosition.getY(), clickPosition.getZ(), + hand, session.isSneaking())); + + InteractionResult result; + if (isSpectator) { + result = InteractionResult.PASS; + } else { + result = entity.interactAt(hand); + } + + if (!result.consumesAction()) { + session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(), + InteractAction.INTERACT, hand, session.isSneaking())); + if (!isSpectator) { + result = entity.interact(hand); + } + } + + if (result.consumesAction()) { + if (result.shouldSwing() && hand == Hand.OFF_HAND) { + // Currently, Bedrock will send us the arm swing packet in most cases. But it won't for offhand. + session.sendDownstreamPacket(new ServerboundSwingPacket(hand)); + // Note here to look into sending the animation packet back to Bedrock + } + return; + } + } + } + /** * Restore the correct block state from the server without updating the chunk cache. * diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java index 214548fe9..2bbae4a49 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java @@ -30,11 +30,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import org.geysermc.geyser.inventory.GeyserItemStack; 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.CooldownUtils; -import org.geysermc.geyser.entity.InteractiveTagManager; import java.util.concurrent.TimeUnit; @@ -43,8 +43,9 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 || - packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + int newSlot = packet.getHotbarSlot(); + if (!session.isSpawned() || newSlot > 8 || packet.getContainerId() != ContainerId.INVENTORY + || session.getPlayerInventory().getHeldItemSlot() == newSlot) { // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention return; } @@ -52,12 +53,15 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, RespawnPacket packet) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) { - // Previously we only sent the respawn packet before the server finished loading - // The message included was 'Otherwise when immediate respawn is on the client never loads' - // But I assume the new if statement below fixes that problem - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.setRuntimeEntityId(0); - respawnPacket.setPosition(Vector3f.ZERO); - respawnPacket.setState(RespawnPacket.State.SERVER_READY); - session.sendUpstreamPacket(respawnPacket); - - if (session.isSpawned()) { - // Client might be stuck; resend spawn information - PlayerEntity entity = session.getPlayerEntity(); - entity.updateBedrockMetadata(); // TODO test? - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); - session.sendUpstreamPacket(movePlayerPacket); - } - ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN); session.sendDownstreamPacket(javaRespawnPacket); } 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 e55b28602..8641a35ff 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,7 +43,17 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat if (session.getRemoteAuthType() == AuthType.ONLINE) { if (!session.isLoggedIn()) { - LoginEncryptionUtils.buildAndShowLoginWindow(session); + if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.name())) { + if (session.getGeyser().refreshTokenFor(session.name()) == null) { + LoginEncryptionUtils.buildAndShowConsentWindow(session); + } else { + // If the refresh token is not null and we're here, then the refresh token expired + // and the expiration form has been cached + session.getFormCache().resendAllForms(); + } + } else { + LoginEncryptionUtils.buildAndShowLoginWindow(session); + } } // else we were able to log the user in } 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 035a2afe2..91ed5aa2b 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 @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -39,6 +40,20 @@ public class BedrockTextTranslator extends PacketTranslator { 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(); + } + if (message.isBlank()) { // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java index a42184750..b693b7f3c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java @@ -25,11 +25,8 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity; -import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.MerchantContainer; import org.geysermc.geyser.session.GeyserSession; @@ -50,21 +47,14 @@ public class BedrockEntityEventTranslator extends PacketTranslator { + // Not sent as of 1.18.10 ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData()); session.sendDownstreamPacket(selectTradePacket); session.scheduleInEventLoop(() -> { - SessionPlayerEntity villager = session.getPlayerEntity(); Inventory openInventory = session.getOpenInventory(); if (openInventory instanceof MerchantContainer merchantInventory) { - VillagerTrade[] trades = merchantInventory.getVillagerTrades(); - if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { - VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()]; - openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session); - // TODO this logic doesn't add up - villager.addFakeTradeExperience(trade.getXp()); - villager.updateBedrockMetadata(); - } + merchantInventory.onTradeSelected(session, packet.getData()); } }, 100, TimeUnit.MILLISECONDS); return; 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 52129797b..5429899fa 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 @@ -28,7 +28,10 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.*; -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.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -39,10 +42,8 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -105,38 +106,13 @@ public class BedrockActionTranslator extends PacketTranslator { @@ -84,7 +83,7 @@ public class BedrockInteractTranslator extends PacketTranslator return; } - InteractiveTagManager.updateTag(session, interactEntity); + interactEntity.updateInteractiveTag(); } else { if (session.getMouseoverEntity() != null) { // No interactive tag should be sent 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 2fccbe482..0d3ef4cbc 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.EntityDefinitions; 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) { @@ -83,8 +81,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= (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)); @@ -141,6 +140,8 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers(); + private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.keySet().toArray(new String[0]); private static final String[] ENUM_BOOLEAN = {"true", "false"}; private static final String[] VALID_COLORS; private static final String[] VALID_SCOREBOARD_SLOTS; @@ -203,10 +206,11 @@ public class JavaCommandsTranslator extends PacketTranslator 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; + } + } default -> CommandParam.STRING; }; } @@ -302,7 +314,7 @@ public class JavaCommandsTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundLoginDisconnectPacket packet) { + Component disconnectReason = packet.getReason(); + + boolean isOutdatedMessage = false; + if (disconnectReason instanceof TranslatableComponent component) { + String key = component.key(); + isOutdatedMessage = "multiplayer.disconnect.incompatible".equals(key) || + // Legacy string (starting from at least 1.15.2) + "multiplayer.disconnect.outdated_server".equals(key) + // Reproduced on 1.15.2 server with ViaVersion 4.0.0-21w20a with 1.18.2 Java client + || key.startsWith("Outdated server!"); + } else { + if (disconnectReason instanceof TextComponent component) { + if (component.content().startsWith("Outdated server!")) { + // Reproduced with vanilla 1.8.8 server and 1.18.2 Java client + isOutdatedMessage = true; + } else { + List 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; + } + } + } + } + } + + String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.getLocale()); + String disconnectMessage; + if (isOutdatedMessage) { + String locale = session.getLocale(); + PlatformType platform = session.getGeyser().getPlatformType(); + String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY) ? + "geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server"; + disconnectMessage = GeyserLocale.getPlayerLocaleString(outdatedType, locale, MinecraftProtocol.getJavaVersions().get(0)) + '\n' + + GeyserLocale.getPlayerLocaleString("geyser.network.remote.original_disconnect_message", locale, serverDisconnectMessage); + } else { + disconnectMessage = serverDisconnectMessage; + } + // The client doesn't manually get disconnected so we have to do it ourselves - session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.getLocale())); + session.disconnect(disconnectMessage); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 14dc37e5d..b68c0996d 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 @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.AuthType; @@ -102,7 +103,7 @@ public class JavaLoginTranslator extends PacketTranslator recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); + boolean applySmithingRecipes = session.getUpstream().getProtocolVersion() >= Bedrock_v486.V486_CODEC.getProtocolVersion(); + + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); @@ -96,7 +104,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { @@ -114,7 +122,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { @@ -128,6 +136,27 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { + // Required to translate these as of 1.18.10, or else they cannot be crafted + if (!applySmithingRecipes) { + continue; + } + + SmithingRecipeData recipeData = (SmithingRecipeData) recipe.getData(); + ItemData output = ItemTranslator.translateToBedrock(session, recipeData.getResult()); + for (ItemStack base : recipeData.getBase().getOptions()) { + ItemData bedrockBase = ItemTranslator.translateToBedrock(session, base); + + for (ItemStack addition : recipeData.getAddition().getOptions()) { + ItemData bedrockAddition = ItemTranslator.translateToBedrock(session, addition); + + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Arrays.asList(bedrockBase, bedrockAddition), + Collections.singletonList(output), uuid, "smithing_table", 2, netId++)); + } + } + } default -> { List craftingData = recipeTypes.get(recipe.getType()); if (craftingData != null) { @@ -139,21 +168,22 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore // We can get the correct order for button pressing data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData -> - session.getItemMappings().getItems() - .getOrDefault(stoneCuttingRecipeData.getResult().getId(), ItemMapping.AIR) + session.getItemMappings().getMapping(stoneCuttingRecipeData.getResult()) .getJavaIdentifier()))); // Now that it's sorted, let's translate these recipes + int buttonId = 0; for (StoneCuttingRecipeData stoneCuttingData : data.getValue()) { // As of 1.16.4, all stonecutter recipes have one ingredient option ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; ItemData input = ItemTranslator.translateToBedrock(session, ingredient); - ItemData output = ItemTranslator.translateToBedrock(session, stoneCuttingData.getResult()); + ItemStack javaOutput = stoneCuttingData.getResult(); + ItemData output = ItemTranslator.translateToBedrock(session, javaOutput); if (input.equals(ItemData.AIR) || output.equals(ItemData.AIR)) { // Probably modded items continue; @@ -162,18 +192,16 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator new IntArrayList()); - outputs.add(stoneCuttingData.getResult().getId()); + // Add the net ID as the key and the button required + output for the value + stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput)); } } session.sendUpstreamPacket(craftingDataPacket); session.setCraftingRecipes(recipeMap); - session.getUnlockedRecipes().clear(); session.setStonecutterRecipes(stonecutterRecipeMap); session.getLastRecipeNetId().set(netId); } @@ -202,7 +230,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { @@ -50,6 +52,9 @@ public class JavaAnimateTranslator extends PacketTranslator { @@ -60,8 +59,9 @@ public class JavaSetEntityDataTranslator extends PacketTranslator { + GameProfile profile = entry.getProfile(); PlayerEntity playerEntity; - boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); if (self) { // Entity is ourself playerEntity = session.getPlayerEntity(); } else { - playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + 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(), - entry.getProfile(), + profile.getId(), Vector3f.ZERO, Vector3f.ZERO, - 0, 0, 0 + 0, 0, 0, + profile.getName(), + texturesProperty ); session.getEntityCache().addPlayerEntity(playerEntity); } else { - playerEntity.setProfile(entry.getProfile()); + playerEntity.setUsername(profile.getName()); + playerEntity.setTexturesProperty(texturesProperty); } playerEntity.setPlayerList(true); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java new file mode 100644 index 000000000..81a86311a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION 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.entity.player.ClientboundPlayerLookAtPacket; +import com.nukkitx.math.vector.Vector3f; +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.MathUtils; + +@Translator(packet = ClientboundPlayerLookAtPacket.class) +public class JavaPlayerLookAtTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + var targetPosition = targetPosition(session, packet); + var selfPosition = session.getPlayerEntity().getPosition(); + + var xDelta = targetPosition.getX() - selfPosition.getX(); + var yDelta = targetPosition.getY() - selfPosition.getY(); + var zDelta = targetPosition.getZ() - selfPosition.getZ(); + var sqrt = Math.sqrt(xDelta * xDelta + zDelta * zDelta); + + var yaw = MathUtils.wrapDegrees(-Math.toDegrees(Math.atan2(yDelta, sqrt))); + var pitch = MathUtils.wrapDegrees(Math.toDegrees(Math.atan2(zDelta, xDelta)) - 90.0); + + var self = session.getPlayerEntity(); + // headYaw is also set to yaw in this packet + self.updateRotation(yaw, pitch, yaw, self.isOnGround()); + } + + public Vector3f targetPosition(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + if (packet.getTargetEntityOrigin() != null) { + var entityId = packet.getTargetEntityId(); + var target = session.getEntityCache().getEntityByJavaId(entityId); + if (target != null) { + return switch (packet.getTargetEntityOrigin()) { + case FEET -> target.getPosition(); + case EYES -> target.getPosition().add(0, target.getBoundingBoxHeight(), 0); + }; + } + } + return Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index e2fe1103f..f5d21ecc9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -74,7 +74,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator 0f) { + // Needed as of 1.18.30 (tested with a totem of undying on SPIGOT 1.12.2 + // This shouldn't be triggered on a proper respawn because JavaSetHealthTranslator sets the health back to 20 + // https://github.com/GeyserMC/Geyser/issues/2957 + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.setRuntimeEntityId(0); + respawnPacket.setPosition(entity.getPosition()); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); + session.sendUpstreamPacket(respawnPacket); + } entity.setHealth(packet.getHealth()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java index f0b1b4874..c54b75f4f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java @@ -48,7 +48,9 @@ public class JavaAddPlayerTranslator extends PacketTranslator inventorySize) { + GeyserImpl geyser = session.getGeyser(); + geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.name() + + " that exceeds inventory size!"); + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().debug(packet); + geyser.getLogger().debug(inventory); + } + InventoryTranslator translator = session.getInventoryTranslator(); + if (translator != null) { + translator.updateInventory(session, inventory); + } + // 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 + return; + } + GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); inventory.setItem(i, newItem, session); } @@ -55,6 +73,10 @@ public class JavaContainerSetContentTranslator extends PacketTranslator 0 || stateId != inventory.getStateId()); + inventory.setStateId(stateId); + session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); InventoryUtils.updateCursor(session); } 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 283d95fc4..36307e7bd 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 @@ -27,10 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; -import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -39,18 +35,17 @@ import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.inventory.InventoryTranslator; -import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; 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.InventoryUtils; import java.util.Arrays; import java.util.Collections; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -72,14 +67,16 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator 0 || stateId != inventory.getStateId()); + inventory.setStateId(stateId); InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { if (session.getCraftingGridFuture() != null) { session.getCraftingGridFuture().cancel(false); } - session.setCraftingGridFuture(session.scheduleInEventLoop(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS)); + updateCraftingGrid(session, packet.getSlot(), packet.getItem(), inventory, translator); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) { @@ -93,21 +90,23 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator { int offset = gridSize == 4 ? 28 : 32; int gridDimensions = gridSize == 4 ? 2 : 3; int firstRow = -1, height = -1; @@ -135,62 +134,10 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client - recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); - if (trade.getSecondInput() != null) { - recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0)); - } + recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice(), trade.getDemand(), trade.getPriceMultiplier())); + recipe.put("buyB", getItemTag(session, trade.getSecondInput())); recipe.putInt("uses", trade.getNumUses()); recipe.putByte("rewardExp", (byte) 1); tags.add(recipe.build()); @@ -144,12 +146,31 @@ public class JavaMerchantOffersTranslator extends PacketTranslator { @@ -57,8 +56,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator { @@ -50,8 +49,9 @@ public class JavaBlockEventTranslator extends PacketTranslator 0 ? 1 : 0); @@ -60,11 +60,12 @@ public class JavaBlockEventTranslator extends PacketTranslator new PistonBlockEntity(session, pos, direction, true, true)); + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> new PistonBlockEntity(session, pos, direction, true, true)); if (blockEntity.getAction() != action) { blockEntity.setAction(action, Object2IntMaps.emptyMap()); } } } else { - PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> { + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> { int blockId = session.getGeyser().getWorldManager().getBlockAt(session, position); boolean sticky = BlockStateValues.isStickyPiston(blockId); boolean extended = action != PistonValueType.PUSHING; @@ -106,10 +107,8 @@ public class JavaBlockEventTranslator extends PacketTranslator { @@ -41,19 +43,18 @@ public class JavaForgetLevelChunkTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator(); - while (iterator.hasNext()) { - Vector3i position = iterator.next(); + // Checks if a skull is in an unloaded chunk then removes it + List removedSkulls = new ArrayList<>(); + for (Vector3i position : session.getSkullCache().getSkulls().keySet()) { if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { - session.getSkullCache().get(position).despawnEntity(); - iterator.remove(); + removedSkulls.add(position); } } + removedSkulls.forEach(session.getSkullCache()::removeSkull); if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled()) { // Do the same thing with lecterns - iterator = session.getLecternCache().iterator(); + Iterator iterator = session.getLecternCache().iterator(); while (iterator.hasNext()) { Vector3i position = iterator.next(); if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { 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 97b826473..47ba98274 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.NbtMap; 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.IntList; 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.*; @@ -96,15 +99,15 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length); BitSet waterloggedPaletteIds = new BitSet(); - BitSet pistonOrFlowerPaletteIds = new BitSet(); + BitSet bedrockOnlyBlockEntityIds = 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 class JavaLevelChunkWithLightTranslator extends PacketTranslator> 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; @@ -141,7 +144,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaId @@ -170,7 +173,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaPalette.idToState(paletteId) @@ -230,9 +233,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator= 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/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index b24c7a363..2271388c2 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 @@ -25,14 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.level; -import com.github.steveice10.mc.protocol.data.game.level.event.BonemealGrowEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.BreakBlockEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.BreakPotionEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.ComposterEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.DragonFireballEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.ParticleEvent; -import com.github.steveice10.mc.protocol.data.game.level.event.RecordEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.SmokeEventData; +import com.github.steveice10.mc.protocol.data.game.level.event.*; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -40,14 +33,13 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.level.event.LevelEventTranslator; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.text.MinecraftLocale; import java.util.Collections; import java.util.Locale; @@ -218,8 +210,7 @@ public class JavaLevelEventTranslator extends PacketTranslator effectPacket.setType(LevelEventType.PARTICLE_EYE_OF_ENDER_DEATH); case MOB_SPAWN -> effectPacket.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> { - effectPacket.setType((particleEvent == ParticleEvent.BONEMEAL_GROW - && session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH); + effectPacket.setType(particleEvent == ParticleEvent.BONEMEAL_GROW ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH); BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData(); effectPacket.setData(growEventData.getParticleCount()); 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 4da91fcd6..de586b600 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 @@ -42,6 +42,7 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.util.DimensionUtils; +import java.util.Optional; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; @@ -150,6 +151,7 @@ public class JavaLevelParticlesTranslator extends PacketTranslator= 0) { // Client thinks there is no daylight cycle but there is diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaClearTitlesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaClearTitlesTranslator.java index 212e6477f..67112833b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaClearTitlesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/title/JavaClearTitlesTranslator.java @@ -37,11 +37,14 @@ public class JavaClearTitlesTranslator extends PacketTranslator { - - /** - * Handles the block interaction when a player - * right-clicks an entity. - * - * @param session the session interacting with the block - * @param position the position of the block - * @param entity the entity interacted with - */ - static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) { - // If we need to get the hand identifier, only get it once and save it to a variable - String handIdentifier = null; - - for (Map.Entry> interactionEntry : Registries.SOUND_TRANSLATORS.get().entrySet()) { - if (!(interactionEntry.getValue() instanceof EntitySoundInteractionTranslator)) { - continue; - } - if (interactionEntry.getKey().entities().length != 0) { - boolean contains = false; - for (String entityIdentifier : interactionEntry.getKey().entities()) { - if (entity.getDefinition().entityType().name().toLowerCase().contains(entityIdentifier)) { - contains = true; - break; - } - } - if (!contains) continue; - } - GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(); - if (interactionEntry.getKey().items().length != 0) { - if (itemInHand.isEmpty()) { - continue; - } - if (handIdentifier == null) { - // Don't get the identifier unless we need it - handIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); - } - boolean contains = false; - for (String itemIdentifier : interactionEntry.getKey().items()) { - if (handIdentifier.contains(itemIdentifier)) { - contains = true; - break; - } - } - if (!contains) continue; - } - if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) { - if (!itemInHand.isEmpty()) { - continue; - } - } - ((EntitySoundInteractionTranslator) interactionEntry.getValue()).translate(session, position, entity); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java index bb0e7c20a..0146c534e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java @@ -54,17 +54,6 @@ public @interface SoundTranslator { */ String[] items() default {}; - /** - * The identifier(s) that the interacted entity must have. - * Leave empty to ignore. - * - * Only applies to interaction handlers that are an - * instance of {@link EntitySoundInteractionTranslator}. - * - * @return the value the item in the player's hand must contain - */ - String[] entities() default {}; - /** * Controls if the interaction should still be * called even if the player is sneaking while diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java deleted file mode 100644 index b996dafee..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java +++ /dev/null @@ -1,57 +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.sound.entity; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.entity.type.living.animal.OcelotEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; -import org.geysermc.geyser.translator.sound.SoundTranslator; - -@SoundTranslator -public class FeedBabySoundInteractionTranslator implements EntitySoundInteractionTranslator { - - @Override - public void translate(GeyserSession session, Vector3f position, Entity entity) { - if (entity instanceof AnimalEntity animalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) { - String handIdentifier = session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier(); - boolean isBaby = animalEntity.isBaby(); - if (isBaby && animalEntity.canEat(handIdentifier.replace("minecraft:", ""), - session.getPlayerInventory().getItemInHand().getMapping(session))) { - // Play the "feed child" effect - EntityEventPacket feedEvent = new EntityEventPacket(); - feedEvent.setRuntimeEntityId(entity.getGeyserId()); - feedEvent.setType(EntityEventType.BABY_ANIMAL_FEED); - session.sendUpstreamPacket(feedEvent); - } - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java deleted file mode 100644 index 49994f7e6..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java +++ /dev/null @@ -1,65 +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.sound.entity; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.animal.GoatEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; -import org.geysermc.geyser.translator.sound.SoundTranslator; - -@SoundTranslator(entities = {"cow", "goat"}, items = "bucket") -public class MilkEntitySoundInteractionTranslator implements EntitySoundInteractionTranslator { - - @Override - public void translate(GeyserSession session, Vector3f position, Entity value) { - if (!session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { - return; - } - if (value.getFlag(EntityFlag.BABY)) { - return; - } - - SoundEvent milkSound; - if (value instanceof GoatEntity && ((GoatEntity) value).isScreamer()) { - milkSound = SoundEvent.MILK_SCREAMER; - } else { - milkSound = SoundEvent.MILK; - } - LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); - levelSoundEventPacket.setPosition(position); - levelSoundEventPacket.setBabySound(false); - levelSoundEventPacket.setRelativeVolumeDisabled(false); - levelSoundEventPacket.setIdentifier(":"); - levelSoundEventPacket.setSound(milkSound); - levelSoundEventPacket.setExtraData(-1); - session.sendUpstreamPacket(levelSoundEventPacket); - } -} 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 f9b0afda9..6f446f136 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 @@ -34,10 +34,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.text.GsonComponentSerializerWrapper; -import org.geysermc.geyser.text.MinecraftTranslationRegistry; +import org.geysermc.geyser.text.*; import java.util.EnumMap; import java.util.Map; @@ -85,8 +82,13 @@ public class MessageTranslator { TEAM_COLORS.put(TeamColor.STRIKETHROUGH, BASE + "m"); TEAM_COLORS.put(TeamColor.ITALIC, BASE + "o"); - // Temporary fix for https://github.com/KyoriPowered/adventure/issues/447 - GsonComponentSerializer source = DefaultComponentSerializer.get(); + // Temporary fix for https://github.com/KyoriPowered/adventure/issues/447 - TODO resolve properly + GsonComponentSerializer source = DefaultComponentSerializer.get() + .toBuilder() + // Use a custom legacy hover event deserializer since we don't use any of this data anyway, and + // fixes issues where legacy hover events throw deserialization errors + .legacyHoverEventSerializer(new DummyLegacyHoverEventSerializer()) + .build(); GSON_SERIALIZER = new GsonComponentSerializerWrapper(source); // Tell MCProtocolLib to use this serializer, too. DefaultComponentSerializer.set(GSON_SERIALIZER); diff --git a/core/src/main/java/org/geysermc/geyser/util/BlockEntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/BlockEntityUtils.java index 2548ae0dc..ff283d08f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/BlockEntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/BlockEntityUtils.java @@ -30,15 +30,15 @@ import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; 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.FlowerPotBlockEntityTranslator; -import org.geysermc.geyser.registry.Registries; import javax.annotation.Nonnull; -import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; public class BlockEntityUtils { @@ -46,27 +46,22 @@ public class BlockEntityUtils { * A list of all block entities that require the Java block state in order to fill out their block entity information. * This list will be smaller with cache sections on as we don't need to double-cache data */ - public static final ObjectArrayList BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>(); + public static final List BEDROCK_ONLY_BLOCK_ENTITIES = List.of( + (BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST), + new FlowerPotBlockEntityTranslator() + ); /** * Contains a list of irregular block entity name translations that can't be fit into the regex */ - public static final Map BLOCK_ENTITY_TRANSLATIONS = new HashMap<>() { - { + public static final Map BLOCK_ENTITY_TRANSLATIONS = Map.of( // Bedrock/Java differences - put(BlockEntityType.ENCHANTING_TABLE, "EnchantTable"); - put(BlockEntityType.JIGSAW, "JigsawBlock"); - put(BlockEntityType.PISTON, "PistonArm"); - put(BlockEntityType.TRAPPED_CHEST, "Chest"); + BlockEntityType.ENCHANTING_TABLE, "EnchantTable", + BlockEntityType.JIGSAW, "JigsawBlock", + BlockEntityType.PISTON, "PistonArm", + BlockEntityType.TRAPPED_CHEST, "Chest" // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly - } - }; - - static { - // Seeing as there are only two - and, hopefully, will only ever be two - we can hardcode this - BEDROCK_ONLY_BLOCK_ENTITIES.add((BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST)); - BEDROCK_ONLY_BLOCK_ENTITIES.add(new FlowerPotBlockEntityTranslator()); - } + ); public static String getBedrockBlockEntityId(BlockEntityType type) { // These are the only exceptions when it comes to block entity ids @@ -77,9 +72,9 @@ public class BlockEntityUtils { String id = type.name(); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already - String[] words = id.split("_");; + String[] words = id.split("_"); for (int i = 0; i < words.length; i++) { - words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + words[i] = words[i].substring(0, 1).toUpperCase(Locale.ROOT) + words[i].substring(1).toLowerCase(Locale.ROOT); } return String.join("", words); 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 7fdf12ec9..6eff505f5 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -45,24 +45,15 @@ import org.geysermc.geyser.level.chunk.GeyserChunkSection; import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.SkullCache; 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. */ @@ -160,10 +151,9 @@ public class ChunkUtils { // Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now } - SkullPlayerEntity skull = session.getSkullCache().get(position); - if (skull != null && blockState != skull.getBlockState()) { + if (BlockStateValues.getSkullVariant(blockState) == -1) { // Skull is gone - skull.despawnEntity(position); + session.getSkullCache().removeSkull(position); } // Prevent moving_piston from being placed @@ -249,17 +239,20 @@ public class ChunkUtils { 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/CooldownUtils.java b/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java index 9e6cd3d88..23af2b709 100644 --- a/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java @@ -57,8 +57,19 @@ public class CooldownUtils { if (sessionPreference == CooldownType.DISABLED) return; if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used - // Needs to be sent or no subtitle packet is recognized by the client + // Set the times to stay a bit with no fade in nor out SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setStayTime(1000); + titlePacket.setText(""); + titlePacket.setXuid(""); + titlePacket.setPlatformOnlineId(""); + session.sendUpstreamPacket(titlePacket); + + session.getWorldCache().markTitleTimesAsIncorrect(); + + // Needs to be sent or no subtitle packet is recognized by the client + titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.TITLE); titlePacket.setText(" "); titlePacket.setXuid(""); @@ -85,9 +96,6 @@ public class CooldownUtils { titlePacket.setType(SetTitlePacket.Type.SUBTITLE); } titlePacket.setText(getTitle(session)); - titlePacket.setFadeInTime(0); - titlePacket.setFadeOutTime(5); - titlePacket.setStayTime(2); titlePacket.setXuid(""); titlePacket.setPlatformOnlineId(""); session.sendUpstreamPacket(titlePacket); @@ -96,11 +104,7 @@ public class CooldownUtils { computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); - if (sessionPreference == CooldownType.ACTIONBAR) { - removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - } else { - removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE); - } + removeTitlePacket.setType(SetTitlePacket.Type.CLEAR); removeTitlePacket.setText(" "); removeTitlePacket.setXuid(""); removeTitlePacket.setPlatformOnlineId(""); 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 5af5e2c2b..f1aca63e8 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 class DimensionUtils { // 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/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 1c89d38c4..aafbbf66e 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -26,18 +26,25 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.living.ArmorStandEntity; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; import java.util.Locale; public final class EntityUtils { + /** + * A constant array of the two hands that a player can interact with an entity. + */ + public static final Hand[] HANDS = Hand.values(); /** * @return a new String array of all known effect identifiers @@ -197,6 +204,26 @@ public final class EntityUtils { } } + /** + * Determine if an action would result in a successful bucketing of the given entity. + */ + public static boolean attemptToBucket(GeyserSession session, GeyserItemStack itemInHand) { + return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().waterBucket(); + } + + /** + * Attempt to determine the result of saddling the given entity. + */ + public static InteractionResult attemptToSaddle(GeyserSession session, Entity entityToSaddle, GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) { + if (!entityToSaddle.getFlag(EntityFlag.SADDLED) && !entityToSaddle.getFlag(EntityFlag.BABY)) { + // Saddle + return InteractionResult.SUCCESS; + } + } + return InteractionResult.PASS; + } + private EntityUtils() { } } diff --git a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java index a56386477..6df9c2177 100644 --- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java @@ -169,8 +169,8 @@ public class FileUtils { * @return The byte array of the file */ public static byte[] readAllBytes(File file) { - try (InputStream inputStream = new FileInputStream(file)) { - return readAllBytes(inputStream); + try (InputStream stream = new FileInputStream(file)) { + return stream.readAllBytes(); } catch (IOException e) { throw new RuntimeException("Cannot read " + file); } @@ -182,30 +182,12 @@ public class FileUtils { */ public static byte[] readAllBytes(String resource) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(resource)) { - return readAllBytes(stream); + return stream.readAllBytes(); } catch (IOException e) { throw new RuntimeException("Error while trying to read internal input stream!", e); } } - /** - * @param stream the InputStream to read off of - * @return the byte array of an InputStream - */ - public static byte[] readAllBytes(InputStream stream) { - try { - int size = stream.available(); - byte[] bytes = new byte[size]; - try (BufferedInputStream buf = new BufferedInputStream(stream)) { - //noinspection ResultOfMethodCallIgnored - buf.read(bytes, 0, bytes.length); - } - return bytes; - } catch (IOException e) { - throw new RuntimeException("Error while trying to read input stream!", e); - } - } - /** * Read the lines of a file and return it as a stream * diff --git a/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java b/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java new file mode 100644 index 000000000..fd13dd743 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +/** + * Used as a mirror of Java Edition's own interaction enum. + */ +public enum InteractionResult { + CONSUME(true), + /** + * Indicates that the action does nothing, or in rare cases is not a priority. + */ + PASS(false), + /** + * Indicates that the action does something, and don't try to find another action to process. + */ + SUCCESS(true); + + private final boolean consumesAction; + + InteractionResult(boolean consumesAction) { + this.consumesAction = consumesAction; + } + + public boolean consumesAction() { + return consumesAction; + } + + public boolean shouldSwing() { + return this == SUCCESS; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java b/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java new file mode 100644 index 000000000..1e8795478 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +import lombok.Getter; + +import java.util.Locale; + +/** + * All interactive tags in enum form. For potential API usage. + */ +public enum InteractiveTag { + NONE((Void) null), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(Void isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(Locale.ROOT); + } +} 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 76530f396..5c2905d93 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -41,15 +42,21 @@ import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.click.Click; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.type.ItemMapping; +import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -155,6 +162,13 @@ public class InventoryUtils { return item1.equals(item2, false, true, true); } + /** + * Checks to see if an item stack represents air or has no count. + */ + public static boolean isEmpty(@Nullable ItemStack itemStack) { + return itemStack == null || itemStack.getId() == ItemMapping.AIR.getJavaId() || itemStack.getAmount() <= 0; + } + /** * Returns a barrier block with custom name and lore to explain why * part of the inventory is unusable. @@ -330,4 +344,130 @@ public class InventoryUtils { session.sendUpstreamPacket(hotbarPacket); // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate } + + @Nullable + public static Click getClickForHotbarSwap(int slot) { + return switch (slot) { + case 0 -> Click.SWAP_TO_HOTBAR_1; + case 1 -> Click.SWAP_TO_HOTBAR_2; + case 2 -> Click.SWAP_TO_HOTBAR_3; + case 3 -> Click.SWAP_TO_HOTBAR_4; + case 4 -> Click.SWAP_TO_HOTBAR_5; + case 5 -> Click.SWAP_TO_HOTBAR_6; + case 6 -> Click.SWAP_TO_HOTBAR_7; + case 7 -> Click.SWAP_TO_HOTBAR_8; + case 8 -> Click.SWAP_TO_HOTBAR_9; + default -> null; + }; + } + + /** + * Test all known recipes to find a valid match + * + * @param output if not null, the recipe has to output this item + */ + @Nullable + public static GeyserRecipe getValidRecipe(final GeyserSession session, final @Nullable ItemStack output, final IntFunction inventoryGetter, + final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) { + int nonAirCount = 0; // Used for shapeless recipes for amount of items needed in recipe + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + if (!inventoryGetter.apply(col + (row * gridDimensions) + 1).isEmpty()) { + nonAirCount++; + } + } + } + + recipes: + for (GeyserRecipe recipe : session.getCraftingRecipes().values()) { + if (recipe.isShaped()) { + GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe; + if (output != null && !shapedRecipe.result().equals(output)) { + continue; + } + Ingredient[] ingredients = shapedRecipe.ingredients(); + if (shapedRecipe.width() != width || shapedRecipe.height() != height || width * height != ingredients.length) { + continue; + } + + if (!testShapedRecipe(ingredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) { + Ingredient[] mirroredIngredients = new Ingredient[ingredients.length]; + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)]; + } + } + + if (Arrays.equals(ingredients, mirroredIngredients) || + !testShapedRecipe(mirroredIngredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) { + continue; + } + } + } else { + GeyserShapelessRecipe data = (GeyserShapelessRecipe) recipe; + if (output != null && !data.result().equals(output)) { + continue; + } + if (nonAirCount != data.ingredients().length) { + // There is an amount of items on the crafting table that is not the same as the ingredient count so this is invalid + continue; + } + for (int i = 0; i < data.ingredients().length; i++) { + Ingredient ingredient = data.ingredients()[i]; + for (ItemStack itemStack : ingredient.getOptions()) { + boolean inventoryHasItem = false; + // Iterate only over the crafting table to find this item + crafting: + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1); + if (geyserItemStack.isEmpty()) { + inventoryHasItem = itemStack == null || itemStack.getId() == 0; + if (inventoryHasItem) { + break crafting; + } + } else if (itemStack.equals(geyserItemStack.getItemStack(1))) { + inventoryHasItem = true; + break crafting; + } + } + } + if (!inventoryHasItem) { + continue recipes; + } + } + } + } + return recipe; + } + return null; + } + + private static boolean testShapedRecipe(final Ingredient[] ingredients, final IntFunction inventoryGetter, + final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) { + int ingredientIndex = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1); + Ingredient ingredient = ingredients[ingredientIndex++]; + if (ingredient.getOptions().length == 0) { + if (!geyserItemStack.isEmpty()) { + return false; + } + } else { + boolean inventoryHasItem = false; + for (ItemStack item : ingredient.getOptions()) { + if (Objects.equals(geyserItemStack.getItemStack(1), item)) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + return false; + } + } + } + } + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java index be1731079..37c4609fe 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java @@ -26,22 +26,30 @@ package org.geysermc.geyser.util; import com.github.steveice10.opennbt.tag.builtin.*; +import it.unimi.dsi.fastutil.ints.Int2IntMap; import org.geysermc.geyser.session.GeyserSession; -public class ItemUtils { +import javax.annotation.Nullable; - public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { - ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments")); +public class ItemUtils { + private static Int2IntMap DYE_COLORS = null; + + public static int getEnchantmentLevel(@Nullable CompoundTag itemNBTData, String enchantmentId) { + if (itemNBTData == null) { + return 0; + } + ListTag enchantments = itemNBTData.get("Enchantments"); if (enchantments != null) { - int enchantmentLevel = 0; for (Tag tag : enchantments) { CompoundTag enchantment = (CompoundTag) tag; StringTag enchantId = enchantment.get("id"); if (enchantId.getValue().equals(enchantmentId)) { - enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue(); + Tag lvl = enchantment.get("lvl"); + if (lvl != null && lvl.getValue() instanceof Number number) { + return number.intValue(); + } } } - return enchantmentLevel; } return 0; } @@ -73,4 +81,19 @@ public class ItemUtils { } return null; } + + /** + * Return the dye color associated with this Java item ID, if any. Returns -1 if no dye color exists for this item. + */ + public static int dyeColorFor(int javaId) { + return DYE_COLORS.get(javaId); + } + + public static void setDyeColors(Int2IntMap dyeColors) { + if (DYE_COLORS != null) { + throw new RuntimeException(); + } + dyeColors.defaultReturnValue(-1); + DYE_COLORS = dyeColors; + } } 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 f54242263..0d369ac19 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -46,6 +46,7 @@ import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import javax.crypto.SecretKey; @@ -152,10 +153,11 @@ public class LoginEncryptionUtils { 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!"); } @@ -166,6 +168,7 @@ public class LoginEncryptionUtils { 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()) { @@ -257,6 +260,48 @@ public class LoginEncryptionUtils { })); } + /** + * Build a window that explains the user's credentials will be saved to the system. + */ + public static void buildAndShowConsentWindow(GeyserSession session) { + String locale = session.getLocale(); + session.sendForm( + SimpleForm.builder() + .title("%gui.signIn") + .content(GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.warning", locale) + + "\n\n" + + GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.proceed", locale)) + .button("%gui.ok") + .button("%gui.decline") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect() && response.getClickedButtonId() == 0) { + session.authenticateWithMicrosoftCode(true); + } else { + session.disconnect("%disconnect.quitting"); + } + })); + } + + public static void buildAndShowTokenExpiredWindow(GeyserSession session) { + String locale = session.getLocale(); + session.sendForm( + SimpleForm.builder() + .title(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.expired", locale)) + .content(GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.expired", locale) + + "\n\n" + + GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.proceed", locale)) + .button("%gui.ok") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + session.authenticateWithMicrosoftCode(true); + } else { + session.disconnect("%disconnect.quitting"); + } + })); + } + public static void buildAndShowLoginDetailsWindow(GeyserSession session) { session.sendForm( CustomForm.builder() @@ -302,10 +347,24 @@ public class LoginEncryptionUtils { * Shows the code that a user must input into their browser */ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) { + StringBuilder message = new StringBuilder("%xbox.signin.website\n") + .append(ChatColor.AQUA) + .append("%xbox.signin.url") + .append(ChatColor.RESET) + .append("\n%xbox.signin.enterCode\n") + .append(ChatColor.GREEN) + .append(msCode.user_code); + int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout(); + if (timeout != 0) { + message.append("\n\n") + .append(ChatColor.RESET) + .append(GeyserLocale.getPlayerLocaleString("geyser.auth.login.timeout", session.getLocale(), String.valueOf(timeout))); + } + session.sendForm( ModalForm.builder() .title("%xbox.signin") - .content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code) + .content(message.toString()) .button1("%gui.done") .button2("%menu.disconnect") .resultHandler( diff --git a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java index 353d7e26a..50cfa001e 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java @@ -26,9 +26,26 @@ package org.geysermc.geyser.util; public class MathUtils { - public static final double SQRT_OF_TWO = Math.sqrt(2); + public static float wrapDegrees(float degrees) { + degrees = degrees % 360.0f; + if (degrees < -180.0f) { + degrees += 360.0f; + } else if (degrees >= 180.0f) { + degrees -= 360.0f; + } + return degrees; + } + + public static float wrapDegrees(double degrees) { + return wrapDegrees((float) degrees); + } + + public static int wrapDegreesToInt(float degrees) { + return (int) wrapDegrees(degrees); + } + /** * Round the given float to the next whole number * diff --git a/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java b/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java index d0be2f38e..032dd2af7 100644 --- a/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/PluginMessageUtils.java @@ -33,9 +33,7 @@ import org.geysermc.geyser.session.GeyserSession; import java.nio.ByteBuffer; public class PluginMessageUtils { - private static final String SKIN_CHANNEL = "floodgate:skin"; private static final byte[] GEYSER_BRAND_DATA; - private static final byte[] FLOODGATE_REGISTER_DATA; static { byte[] data = GeyserImpl.NAME.getBytes(Charsets.UTF_8); @@ -44,8 +42,6 @@ public class PluginMessageUtils { .put(writeVarInt(data.length)) .put(data) .array(); - - FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8); } /** @@ -57,22 +53,6 @@ public class PluginMessageUtils { return GEYSER_BRAND_DATA; } - /** - * Get the prebuilt register data as a byte array - * - * @return the register data of the Floodgate channels - */ - public static byte[] getFloodgateRegisterData() { - return FLOODGATE_REGISTER_DATA; - } - - /** - * Returns the skin channel used in Floodgate - */ - public static String getSkinChannel() { - return SKIN_CHANNEL; - } - public static void sendMessage(GeyserSession session, String channel, byte[] data) { session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, data)); } 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 eb8c133d4..cdb672100 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -61,7 +61,7 @@ public class SettingsUtils { // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. if (session.getPreferencesCache().isAllowShowCoordinates()) { - builder.toggle("geyser.settings.option.coordinates", session.getPreferencesCache().isPrefersShowCoordinates()); + builder.toggle("%createWorldScreen.showCoordinates", session.getPreferencesCache().isPrefersShowCoordinates()); } if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { @@ -169,6 +169,10 @@ public class SettingsUtils { } private static String translateEntry(String key, String locale) { + if (key.startsWith("%")) { + // Bedrock will translate + return key; + } if (key.startsWith("geyser.")) { return GeyserLocale.getPlayerLocaleString(key, locale); } 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 40daf22c7..fe479363f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -52,6 +52,8 @@ public class WebUtils { HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); // Otherwise Java 8 fails on checking updates + con.setConnectTimeout(10000); + con.setReadTimeout(10000); return connectionToString(con); } catch (Exception e) { diff --git a/core/src/main/resources/bedrock/block_palette.1_17_30.nbt b/core/src/main/resources/bedrock/block_palette.1_17_30.nbt deleted file mode 100644 index fde145ca5..000000000 Binary files a/core/src/main/resources/bedrock/block_palette.1_17_30.nbt and /dev/null differ 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_10.nbt b/core/src/main/resources/bedrock/block_palette.1_18_10.nbt new file mode 100644 index 000000000..637bfc952 Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_18_10.nbt differ 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 000000000..bf7690970 Binary files /dev/null and b/core/src/main/resources/bedrock/block_palette.1_18_30.nbt differ diff --git a/core/src/main/resources/bedrock/creative_items.1_17_40.json b/core/src/main/resources/bedrock/creative_items.1_18_10.json similarity index 94% rename from core/src/main/resources/bedrock/creative_items.1_17_40.json rename to core/src/main/resources/bedrock/creative_items.1_18_10.json index 0bc7f91ea..dadabf91f 100644 --- a/core/src/main/resources/bedrock/creative_items.1_17_40.json +++ b/core/src/main/resources/bedrock/creative_items.1_18_10.json @@ -1,17 +1,5 @@ { "items" : [ - { - "id" : "minecraft:planks", - "blockRuntimeId" : 5797 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 5798 - }, - { - "id" : "minecraft:planks", - "blockRuntimeId" : 5799 - }, { "id" : "minecraft:planks", "blockRuntimeId" : 5800 @@ -24,13 +12,25 @@ "id" : "minecraft:planks", "blockRuntimeId" : 5802 }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 5803 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 5804 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 5805 + }, { "id" : "minecraft:crimson_planks", "blockRuntimeId" : 3840 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 7598 + "blockRuntimeId" : 7596 }, { "id" : "minecraft:cobblestone_wall", @@ -94,11 +94,11 @@ }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6041 + "blockRuntimeId" : 6044 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 5838 + "blockRuntimeId" : 5841 }, { "id" : "minecraft:cobbled_deepslate_wall", @@ -110,7 +110,7 @@ }, { "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 6216 + "blockRuntimeId" : 6219 }, { "id" : "minecraft:deepslate_brick_wall", @@ -142,7 +142,7 @@ }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 5689 + "blockRuntimeId" : 5690 }, { "id" : "minecraft:crimson_fence", @@ -150,7 +150,7 @@ }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 7576 + "blockRuntimeId" : 7574 }, { "id" : "minecraft:fence_gate", @@ -158,7 +158,7 @@ }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 7010 + "blockRuntimeId" : 7007 }, { "id" : "minecraft:birch_fence_gate", @@ -166,7 +166,7 @@ }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5253 + "blockRuntimeId" : 5254 }, { "id" : "minecraft:acacia_fence_gate", @@ -182,27 +182,27 @@ }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 7577 + "blockRuntimeId" : 7575 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 5708 + "blockRuntimeId" : 5709 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 7281 + "blockRuntimeId" : 7278 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 5668 + "blockRuntimeId" : 5669 }, { "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 5717 + "blockRuntimeId" : 5718 }, { "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 7042 + "blockRuntimeId" : 7039 }, { "id" : "minecraft:birch_stairs", @@ -210,7 +210,7 @@ }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 5285 + "blockRuntimeId" : 5286 }, { "id" : "minecraft:acacia_stairs", @@ -222,35 +222,35 @@ }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 7187 + "blockRuntimeId" : 7184 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5676 + "blockRuntimeId" : 5677 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 6710 + "blockRuntimeId" : 6713 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 6903 + "blockRuntimeId" : 6900 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 6637 + "blockRuntimeId" : 6640 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 6895 + "blockRuntimeId" : 6892 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 4989 + "blockRuntimeId" : 4990 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 6386 + "blockRuntimeId" : 6389 }, { "id" : "minecraft:diorite_stairs", @@ -258,7 +258,7 @@ }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6378 + "blockRuntimeId" : 6381 }, { "id" : "minecraft:andesite_stairs", @@ -266,7 +266,7 @@ }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 5814 + "blockRuntimeId" : 5817 }, { "id" : "minecraft:brick_stairs", @@ -274,11 +274,11 @@ }, { "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 5690 + "blockRuntimeId" : 5691 }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6625 + "blockRuntimeId" : 6628 }, { "id" : "minecraft:end_brick_stairs", @@ -286,19 +286,19 @@ }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 6559 + "blockRuntimeId" : 6562 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 6887 + "blockRuntimeId" : 6884 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 6537 + "blockRuntimeId" : 6540 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 6449 + "blockRuntimeId" : 6452 }, { "id" : "minecraft:dark_prismarine_stairs", @@ -306,7 +306,7 @@ }, { "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 6441 + "blockRuntimeId" : 6444 }, { "id" : "minecraft:crimson_stairs", @@ -314,7 +314,7 @@ }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 7618 + "blockRuntimeId" : 7616 }, { "id" : "minecraft:blackstone_stairs", @@ -322,11 +322,11 @@ }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 6033 + "blockRuntimeId" : 6036 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 5830 + "blockRuntimeId" : 5833 }, { "id" : "minecraft:cut_copper_stairs", @@ -338,27 +338,27 @@ }, { "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 7745 + "blockRuntimeId" : 7743 }, { "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 5758 + "blockRuntimeId" : 5760 }, { "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 7689 + "blockRuntimeId" : 7687 }, { "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 7703 + "blockRuntimeId" : 7701 }, { "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 7731 + "blockRuntimeId" : 7729 }, { "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 7717 + "blockRuntimeId" : 7715 }, { "id" : "minecraft:cobbled_deepslate_stairs", @@ -370,7 +370,7 @@ }, { "id" : "minecraft:polished_deepslate_stairs", - "blockRuntimeId" : 6208 + "blockRuntimeId" : 6211 }, { "id" : "minecraft:deepslate_brick_stairs", @@ -405,11 +405,11 @@ }, { "id" : "minecraft:trapdoor", - "blockRuntimeId" : 7363 + "blockRuntimeId" : 7360 }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 7066 + "blockRuntimeId" : 7063 }, { "id" : "minecraft:birch_trapdoor", @@ -417,7 +417,7 @@ }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5309 + "blockRuntimeId" : 5310 }, { "id" : "minecraft:acacia_trapdoor", @@ -429,7 +429,7 @@ }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 5168 + "blockRuntimeId" : 5169 }, { "id" : "minecraft:crimson_trapdoor", @@ -437,15 +437,59 @@ }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 7645 + "blockRuntimeId" : 7643 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 5133 + "blockRuntimeId" : 5134 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 4883 + "blockRuntimeId" : 4884 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7085 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7093 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7092 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7100 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7097 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7099 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7086 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7089 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7090 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7098 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7094 }, { "id" : "minecraft:stained_glass", @@ -461,63 +505,63 @@ }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7103 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7100 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7102 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7089 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7092 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7093 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7101 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7097 + "blockRuntimeId" : 7087 }, { "id" : "minecraft:stained_glass", "blockRuntimeId" : 7091 }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7099 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7098 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7090 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7094 - }, { "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 7352 + "blockRuntimeId" : 7349 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 4884 + "blockRuntimeId" : 4885 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7101 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7109 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7108 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7116 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7113 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7115 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7102 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7105 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7106 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7114 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7110 }, { "id" : "minecraft:stained_glass_pane", @@ -533,79 +577,43 @@ }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7119 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7116 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7118 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7105 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7108 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7109 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7117 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7113 + "blockRuntimeId" : 7103 }, { "id" : "minecraft:stained_glass_pane", "blockRuntimeId" : 7107 }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7115 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7114 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7106 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7110 - }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 5357 + "blockRuntimeId" : 5358 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 6730 + "blockRuntimeId" : 6733 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7220 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7270 }, { "id" : "minecraft:double_stone_slab", "blockRuntimeId" : 7223 }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7273 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7226 - }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7244 + "blockRuntimeId" : 7241 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7901 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7902 }, { "id" : "minecraft:wooden_slab", @@ -624,116 +632,108 @@ "blockRuntimeId" : 7906 }, { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7907 + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7225 }, { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7908 + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7268 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7228 + "blockRuntimeId" : 7221 }, { "id" : "minecraft:double_stone_slab4", "blockRuntimeId" : 7271 }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7224 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7274 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7245 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7239 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7275 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7256 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7261 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7262 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7259 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7260 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7258 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7257 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7227 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7230 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7246 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7255 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7229 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7272 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7240 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7241 - }, { "id" : "minecraft:double_stone_slab2", "blockRuntimeId" : 7242 }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7236 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7272 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7253 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7258 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7259 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7256 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7257 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7255 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7254 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7224 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7227 + }, { "id" : "minecraft:double_stone_slab2", "blockRuntimeId" : 7243 }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7252 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7226 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7269 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7237 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7238 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7239 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7240 + }, { "id" : "minecraft:crimson_slab", "blockRuntimeId" : 3858 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 7616 + "blockRuntimeId" : 7614 }, { "id" : "minecraft:blackstone_slab", @@ -741,11 +741,11 @@ }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6031 + "blockRuntimeId" : 6034 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 5828 + "blockRuntimeId" : 5831 }, { "id" : "minecraft:cut_copper_slab", @@ -757,27 +757,27 @@ }, { "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 7743 + "blockRuntimeId" : 7741 }, { "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5756 + "blockRuntimeId" : 5758 }, { "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7687 + "blockRuntimeId" : 7685 }, { "id" : "minecraft:waxed_exposed_cut_copper_slab", - "blockRuntimeId" : 7701 + "blockRuntimeId" : 7699 }, { "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 7729 + "blockRuntimeId" : 7727 }, { "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 7715 + "blockRuntimeId" : 7713 }, { "id" : "minecraft:cobbled_deepslate_slab", @@ -785,7 +785,7 @@ }, { "id" : "minecraft:polished_deepslate_slab", - "blockRuntimeId" : 6206 + "blockRuntimeId" : 6209 }, { "id" : "minecraft:deepslate_tile_slab", @@ -809,35 +809,35 @@ }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6557 + "blockRuntimeId" : 6560 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 7286 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 7287 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 7288 }, { "id" : "minecraft:stonebrick", "blockRuntimeId" : 7289 }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7290 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7291 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7292 - }, { "id" : "minecraft:end_bricks", "blockRuntimeId" : 4728 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6440 + "blockRuntimeId" : 6443 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 6000 + "blockRuntimeId" : 6003 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", @@ -845,7 +845,7 @@ }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4882 + "blockRuntimeId" : 4883 }, { "id" : "minecraft:chiseled_polished_blackstone", @@ -877,7 +877,7 @@ }, { "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 5667 + "blockRuntimeId" : 5668 }, { "id" : "minecraft:cobbled_deepslate", @@ -885,40 +885,40 @@ }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 6911 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6706 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6707 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6708 + "blockRuntimeId" : 6908 }, { "id" : "minecraft:sandstone", "blockRuntimeId" : 6709 }, { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6633 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 6710 }, { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6634 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 6711 }, { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6635 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 6712 }, { "id" : "minecraft:red_sandstone", "blockRuntimeId" : 6636 }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6637 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6638 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6639 + }, { "id" : "minecraft:coal_block", "blockRuntimeId" : 1141 @@ -929,11 +929,11 @@ }, { "id" : "minecraft:gold_block", - "blockRuntimeId" : 4975 + "blockRuntimeId" : 4976 }, { "id" : "minecraft:iron_block", - "blockRuntimeId" : 5134 + "blockRuntimeId" : 5135 }, { "id" : "minecraft:copper_block", @@ -945,27 +945,27 @@ }, { "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 7741 + "blockRuntimeId" : 7739 }, { "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 5754 + "blockRuntimeId" : 5756 }, { "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7685 + "blockRuntimeId" : 7683 }, { "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 7699 + "blockRuntimeId" : 7697 }, { "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 7727 + "blockRuntimeId" : 7725 }, { "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7713 + "blockRuntimeId" : 7711 }, { "id" : "minecraft:cut_copper", @@ -977,27 +977,27 @@ }, { "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7742 + "blockRuntimeId" : 7740 }, { "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5755 + "blockRuntimeId" : 5757 }, { "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7686 + "blockRuntimeId" : 7684 }, { "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 7700 + "blockRuntimeId" : 7698 }, { "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 7728 + "blockRuntimeId" : 7726 }, { "id" : "minecraft:waxed_oxidized_cut_copper", - "blockRuntimeId" : 7714 + "blockRuntimeId" : 7712 }, { "id" : "minecraft:emerald_block", @@ -1009,59 +1009,59 @@ }, { "id" : "minecraft:lapis_block", - "blockRuntimeId" : 5365 + "blockRuntimeId" : 5366 }, { "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 6579 + "blockRuntimeId" : 6582 }, { "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 6577 + "blockRuntimeId" : 6580 }, { "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 6578 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6545 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6547 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6546 + "blockRuntimeId" : 6581 }, { "id" : "minecraft:quartz_block", "blockRuntimeId" : 6548 }, { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6438 + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 6550 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 6549 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 6551 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6439 + "blockRuntimeId" : 6441 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6442 }, { "id" : "minecraft:slime", - "blockRuntimeId" : 6864 + "blockRuntimeId" : 6861 }, { "id" : "minecraft:honey_block", - "blockRuntimeId" : 5112 - }, - { - "id" : "minecraft:honeycomb_block", "blockRuntimeId" : 5113 }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 5114 + }, { "id" : "minecraft:hay_block", - "blockRuntimeId" : 5084 + "blockRuntimeId" : 5085 }, { "id" : "minecraft:bone_block", @@ -1069,51 +1069,27 @@ }, { "id" : "minecraft:nether_brick", - "blockRuntimeId" : 5688 + "blockRuntimeId" : 5689 }, { "id" : "minecraft:red_nether_brick", - "blockRuntimeId" : 6624 + "blockRuntimeId" : 6627 }, { "id" : "minecraft:netherite_block", - "blockRuntimeId" : 5705 + "blockRuntimeId" : 5706 }, { "id" : "minecraft:lodestone", - "blockRuntimeId" : 5563 + "blockRuntimeId" : 5564 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7915 + "blockRuntimeId" : 7913 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7923 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7922 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7930 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7927 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7929 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7916 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7919 + "blockRuntimeId" : 7921 }, { "id" : "minecraft:wool", @@ -1125,7 +1101,19 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7924 + "blockRuntimeId" : 7925 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7927 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7914 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7917 }, { "id" : "minecraft:wool", @@ -1137,15 +1125,27 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7925 + "blockRuntimeId" : 7922 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7917 + "blockRuntimeId" : 7916 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7921 + "blockRuntimeId" : 7924 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7923 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7915 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7919 }, { "id" : "minecraft:carpet", @@ -1345,7 +1345,51 @@ }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 5083 + "blockRuntimeId" : 5084 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7117 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7125 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7124 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7132 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7129 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7131 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7118 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7121 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7122 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7130 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7126 }, { "id" : "minecraft:stained_hardened_clay", @@ -1361,67 +1405,23 @@ }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7135 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7132 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7134 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7121 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7124 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7125 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7133 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7129 + "blockRuntimeId" : 7119 }, { "id" : "minecraft:stained_hardened_clay", "blockRuntimeId" : 7123 }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7131 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7130 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7122 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7126 - }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 7800 + "blockRuntimeId" : 7798 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 6846 + "blockRuntimeId" : 6849 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 5010 + "blockRuntimeId" : 5011 }, { "id" : "minecraft:black_glazed_terracotta", @@ -1433,23 +1433,23 @@ }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 6601 + "blockRuntimeId" : 6604 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 5748 + "blockRuntimeId" : 5750 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 7942 + "blockRuntimeId" : 7940 }, { "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 5532 + "blockRuntimeId" : 5533 }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 5026 + "blockRuntimeId" : 5027 }, { "id" : "minecraft:cyan_glazed_terracotta", @@ -1457,7 +1457,7 @@ }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5484 + "blockRuntimeId" : 5485 }, { "id" : "minecraft:blue_glazed_terracotta", @@ -1465,35 +1465,35 @@ }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 6519 + "blockRuntimeId" : 6522 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 5596 + "blockRuntimeId" : 5597 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 5779 + "blockRuntimeId" : 5782 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 6525 + "blockRuntimeId" : 6528 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 6527 + "blockRuntimeId" : 6530 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 5704 + "blockRuntimeId" : 5705 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 7667 + "blockRuntimeId" : 7665 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 6829 + "blockRuntimeId" : 6832 }, { "id" : "minecraft:crimson_nylium", @@ -1501,7 +1501,7 @@ }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 7597 + "blockRuntimeId" : 7595 }, { "id" : "minecraft:basalt", @@ -1509,15 +1509,15 @@ }, { "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 5825 }, { "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 6886 + "blockRuntimeId" : 6883 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 6956 + "blockRuntimeId" : 6953 }, { "id" : "minecraft:dirt", @@ -1533,31 +1533,31 @@ }, { "id" : "minecraft:grass", - "blockRuntimeId" : 4997 - }, - { - "id" : "minecraft:grass_path", "blockRuntimeId" : 4998 }, + { + "id" : "minecraft:grass_path", + "blockRuntimeId" : 4999 + }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 5803 + "blockRuntimeId" : 5806 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 5685 + "blockRuntimeId" : 5686 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7180 + "blockRuntimeId" : 7177 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 5167 + "blockRuntimeId" : 5168 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 4976 + "blockRuntimeId" : 4977 }, { "id" : "minecraft:diamond_ore", @@ -1565,11 +1565,11 @@ }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 5366 + "blockRuntimeId" : 5367 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 6647 + "blockRuntimeId" : 6650 }, { "id" : "minecraft:coal_ore", @@ -1585,11 +1585,11 @@ }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 6558 + "blockRuntimeId" : 6561 }, { "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 5698 + "blockRuntimeId" : 5699 }, { "id" : "minecraft:ancient_debris", @@ -1629,19 +1629,19 @@ }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 4999 + "blockRuntimeId" : 5000 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7181 + "blockRuntimeId" : 7178 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7183 + "blockRuntimeId" : 7180 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7185 + "blockRuntimeId" : 7182 }, { "id" : "minecraft:blackstone", @@ -1653,83 +1653,83 @@ }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7182 + "blockRuntimeId" : 7179 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7184 + "blockRuntimeId" : 7181 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7186 + "blockRuntimeId" : 7183 }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 5825 + "blockRuntimeId" : 5828 }, { "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 6203 + "blockRuntimeId" : 6206 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 6704 + "blockRuntimeId" : 6707 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 6705 + "blockRuntimeId" : 6708 }, { "id" : "minecraft:cactus", "blockRuntimeId" : 920 }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 5564 - }, - { - "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7319 - }, { "id" : "minecraft:log", "blockRuntimeId" : 5565 }, { - "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 7322 + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 7316 }, { "id" : "minecraft:log", "blockRuntimeId" : 5566 }, { - "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 7304 + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 7319 }, { "id" : "minecraft:log", "blockRuntimeId" : 5567 }, { - "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 7316 - }, - { - "id" : "minecraft:log2", - "blockRuntimeId" : 5576 - }, - { - "id" : "minecraft:stripped_acacia_log", + "id" : "minecraft:stripped_birch_log", "blockRuntimeId" : 7301 }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 5568 + }, + { + "id" : "minecraft:stripped_jungle_log", + "blockRuntimeId" : 7313 + }, { "id" : "minecraft:log2", "blockRuntimeId" : 5577 }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 7298 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 5578 + }, { "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 7313 + "blockRuntimeId" : 7310 }, { "id" : "minecraft:crimson_stem", @@ -1737,15 +1737,31 @@ }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 7310 + "blockRuntimeId" : 7307 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 7642 + "blockRuntimeId" : 7640 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7328 + "blockRuntimeId" : 7325 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7805 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7811 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7806 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7812 }, { "id" : "minecraft:wood", @@ -1779,41 +1795,21 @@ "id" : "minecraft:wood", "blockRuntimeId" : 7816 }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7811 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7817 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7812 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7818 - }, { "id" : "minecraft:crimson_hyphae", "blockRuntimeId" : 3836 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 7307 + "blockRuntimeId" : 7304 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 7594 + "blockRuntimeId" : 7592 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 7325 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 5410 + "blockRuntimeId" : 7322 }, { "id" : "minecraft:leaves", @@ -1828,13 +1824,17 @@ "blockRuntimeId" : 5413 }, { - "id" : "minecraft:leaves2", - "blockRuntimeId" : 5426 + "id" : "minecraft:leaves", + "blockRuntimeId" : 5414 }, { "id" : "minecraft:leaves2", "blockRuntimeId" : 5427 }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 5428 + }, { "id" : "minecraft:azalea_leaves", "blockRuntimeId" : 169 @@ -1843,18 +1843,6 @@ "id" : "minecraft:azalea_leaves_flowered", "blockRuntimeId" : 173 }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 6718 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 6719 - }, - { - "id" : "minecraft:sapling", - "blockRuntimeId" : 6720 - }, { "id" : "minecraft:sapling", "blockRuntimeId" : 6721 @@ -1867,6 +1855,18 @@ "id" : "minecraft:sapling", "blockRuntimeId" : 6723 }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 6724 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 6725 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 6726 + }, { "id" : "minecraft:bee_nest", "blockRuntimeId" : 236 @@ -1912,7 +1912,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 5609 + "blockRuntimeId" : 5610 }, { "id" : "minecraft:melon_slice" @@ -1928,7 +1928,7 @@ }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 6457 + "blockRuntimeId" : 6460 }, { "id" : "minecraft:carved_pumpkin", @@ -1936,14 +1936,14 @@ }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 5551 + "blockRuntimeId" : 5552 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 7349 + "blockRuntimeId" : 7346 }, { "id" : "minecraft:double_plant", @@ -1951,7 +1951,7 @@ }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 7348 + "blockRuntimeId" : 7345 }, { "id" : "minecraft:double_plant", @@ -2045,7 +2045,7 @@ }, { "id" : "minecraft:seagrass", - "blockRuntimeId" : 6825 + "blockRuntimeId" : 6828 }, { "id" : "minecraft:crimson_roots", @@ -2053,23 +2053,11 @@ }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 7615 + "blockRuntimeId" : 7613 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 7941 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6590 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6591 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6592 + "blockRuntimeId" : 7939 }, { "id" : "minecraft:red_flower", @@ -2103,6 +2091,18 @@ "id" : "minecraft:red_flower", "blockRuntimeId" : 6600 }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6601 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6602 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6603 + }, { "id" : "minecraft:double_plant", "blockRuntimeId" : 4501 @@ -2121,7 +2121,7 @@ }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 7806 + "blockRuntimeId" : 7804 }, { "id" : "minecraft:white_dye" @@ -2188,19 +2188,19 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 7502 + "blockRuntimeId" : 7500 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 7756 + "blockRuntimeId" : 7754 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 7430 + "blockRuntimeId" : 7427 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 7684 + "blockRuntimeId" : 7682 }, { "id" : "minecraft:deadbush", @@ -2212,15 +2212,15 @@ }, { "id" : "minecraft:snow", - "blockRuntimeId" : 6912 + "blockRuntimeId" : 6909 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 5126 + "blockRuntimeId" : 5127 }, { "id" : "minecraft:packed_ice", - "blockRuntimeId" : 5768 + "blockRuntimeId" : 5770 }, { "id" : "minecraft:blue_ice", @@ -2228,11 +2228,11 @@ }, { "id" : "minecraft:snow_layer", - "blockRuntimeId" : 6913 + "blockRuntimeId" : 6910 }, { "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 5809 + "blockRuntimeId" : 5812 }, { "id" : "minecraft:dripstone_block", @@ -2240,11 +2240,11 @@ }, { "id" : "minecraft:moss_carpet", - "blockRuntimeId" : 5666 + "blockRuntimeId" : 5667 }, { "id" : "minecraft:moss_block", - "blockRuntimeId" : 5665 + "blockRuntimeId" : 5666 }, { "id" : "minecraft:dirt_with_roots", @@ -2252,7 +2252,7 @@ }, { "id" : "minecraft:hanging_roots", - "blockRuntimeId" : 5048 + "blockRuntimeId" : 5049 }, { "id" : "minecraft:big_dripleaf", @@ -2260,11 +2260,11 @@ }, { "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 6878 + "blockRuntimeId" : 6875 }, { "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 6965 + "blockRuntimeId" : 6962 }, { "id" : "minecraft:azalea", @@ -2276,7 +2276,7 @@ }, { "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 4972 + "blockRuntimeId" : 4973 }, { "id" : "minecraft:amethyst_block", @@ -2288,23 +2288,23 @@ }, { "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 137 + "blockRuntimeId" : 138 }, { "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 5367 + "blockRuntimeId" : 5369 }, { "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 5603 + "blockRuntimeId" : 5605 }, { "id" : "minecraft:small_amethyst_bud", - "blockRuntimeId" : 6865 + "blockRuntimeId" : 6863 }, { "id" : "minecraft:tuff", - "blockRuntimeId" : 7417 + "blockRuntimeId" : 7414 }, { "id" : "minecraft:calcite", @@ -2343,7 +2343,7 @@ }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 6607 + "blockRuntimeId" : 6610 }, { "id" : "minecraft:crimson_fungus", @@ -2351,7 +2351,7 @@ }, { "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 7593 + "blockRuntimeId" : 7591 }, { "id" : "minecraft:brown_mushroom_block", @@ -2359,7 +2359,7 @@ }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 6622 + "blockRuntimeId" : 6625 }, { "id" : "minecraft:brown_mushroom_block", @@ -2386,17 +2386,13 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 7755 + "blockRuntimeId" : 7753 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 5658 - }, - { - "id" : "minecraft:monster_egg", "blockRuntimeId" : 5659 }, { @@ -2419,9 +2415,13 @@ "id" : "minecraft:monster_egg", "blockRuntimeId" : 5664 }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5665 + }, { "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 5127 + "blockRuntimeId" : 5128 }, { "id" : "minecraft:dragon_egg", @@ -2429,7 +2429,7 @@ }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7418 + "blockRuntimeId" : 7415 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2631,7 +2631,7 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 5737 + "blockRuntimeId" : 5738 }, { "id" : "minecraft:crying_obsidian", @@ -2643,15 +2643,15 @@ }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 6955 + "blockRuntimeId" : 6952 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 5706 + "blockRuntimeId" : 5707 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 5602 + "blockRuntimeId" : 5603 }, { "id" : "minecraft:nether_wart" @@ -2676,11 +2676,11 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 6963 + "blockRuntimeId" : 6960 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 6964 + "blockRuntimeId" : 6961 }, { "id" : "minecraft:coral_block", @@ -3747,23 +3747,23 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 7357 + "blockRuntimeId" : 7354 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 6957 + "blockRuntimeId" : 6954 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 6817 + "blockRuntimeId" : 6820 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 5363 + "blockRuntimeId" : 5364 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 6953 + "blockRuntimeId" : 6950 }, { "id" : "minecraft:candle", @@ -3771,39 +3771,39 @@ }, { "id" : "minecraft:white_candle", - "blockRuntimeId" : 7790 + "blockRuntimeId" : 7788 }, { "id" : "minecraft:orange_candle", - "blockRuntimeId" : 5738 + "blockRuntimeId" : 5740 }, { "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 5586 + "blockRuntimeId" : 5587 }, { "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 5474 + "blockRuntimeId" : 5475 }, { "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 7931 + "blockRuntimeId" : 7929 }, { "id" : "minecraft:lime_candle", - "blockRuntimeId" : 5522 + "blockRuntimeId" : 5523 }, { "id" : "minecraft:pink_candle", - "blockRuntimeId" : 5769 + "blockRuntimeId" : 5772 }, { "id" : "minecraft:gray_candle", - "blockRuntimeId" : 5000 + "blockRuntimeId" : 5001 }, { "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 5490 + "blockRuntimeId" : 5491 }, { "id" : "minecraft:cyan_candle", @@ -3811,7 +3811,7 @@ }, { "id" : "minecraft:purple_candle", - "blockRuntimeId" : 6509 + "blockRuntimeId" : 6512 }, { "id" : "minecraft:blue_candle", @@ -3823,11 +3823,11 @@ }, { "id" : "minecraft:green_candle", - "blockRuntimeId" : 5016 + "blockRuntimeId" : 5017 }, { "id" : "minecraft:red_candle", - "blockRuntimeId" : 6580 + "blockRuntimeId" : 6583 }, { "id" : "minecraft:black_candle", @@ -3847,7 +3847,7 @@ }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 6879 + "blockRuntimeId" : 6876 }, { "id" : "minecraft:beehive", @@ -3861,7 +3861,7 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 4876 + "blockRuntimeId" : 4877 }, { "id" : "minecraft:blast_furnace", @@ -3869,11 +3869,11 @@ }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 6880 + "blockRuntimeId" : 6877 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 6699 + "blockRuntimeId" : 6702 }, { "id" : "minecraft:brewing_stand" @@ -3892,7 +3892,7 @@ }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 5032 + "blockRuntimeId" : 5033 }, { "id" : "minecraft:enchanting_table", @@ -3904,7 +3904,7 @@ }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 5434 + "blockRuntimeId" : 5435 }, { "id" : "minecraft:cauldron" @@ -3919,7 +3919,7 @@ }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 7379 + "blockRuntimeId" : 7376 }, { "id" : "minecraft:ender_chest", @@ -3931,51 +3931,7 @@ }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 7462 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6830 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6838 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6837 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6845 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6842 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6844 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6831 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6834 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6835 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6843 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6839 + "blockRuntimeId" : 7459 }, { "id" : "minecraft:shulker_box", @@ -3991,22 +3947,66 @@ }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6832 + "blockRuntimeId" : 6848 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6845 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6847 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6834 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6837 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6838 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6846 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6842 }, { "id" : "minecraft:shulker_box", "blockRuntimeId" : 6836 }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6844 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6843 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6835 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6839 + }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 5716 + "blockRuntimeId" : 5717 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 5208 + "blockRuntimeId" : 5209 }, { "id" : "minecraft:music_disc_13" @@ -4044,6 +4044,9 @@ { "id" : "minecraft:music_disc_wait" }, + { + "id" : "minecraft:music_disc_otherside" + }, { "id" : "minecraft:music_disc_pigstep" }, @@ -4052,15 +4055,15 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 4974 + "blockRuntimeId" : 4975 }, { "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 6646 + "blockRuntimeId" : 6649 }, { "id" : "minecraft:sealantern", - "blockRuntimeId" : 6828 + "blockRuntimeId" : 6831 }, { "id" : "minecraft:oak_sign" @@ -4171,7 +4174,7 @@ }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7295 + "blockRuntimeId" : 7292 }, { "id" : "minecraft:end_portal_frame", @@ -4315,7 +4318,7 @@ }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 5516 + "blockRuntimeId" : 5517 }, { "id" : "minecraft:end_crystal" @@ -4777,11 +4780,11 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 6567 + "blockRuntimeId" : 6570 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 4977 + "blockRuntimeId" : 4978 }, { "id" : "minecraft:detector_rail", @@ -4808,23 +4811,23 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 6645 - }, - { - "id" : "minecraft:redstone_torch", "blockRuntimeId" : 6648 }, + { + "id" : "minecraft:redstone_torch", + "blockRuntimeId" : 6651 + }, { "id" : "minecraft:lever", - "blockRuntimeId" : 5442 + "blockRuntimeId" : 5443 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 7843 + "blockRuntimeId" : 7841 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 6966 + "blockRuntimeId" : 6963 }, { "id" : "minecraft:birch_button", @@ -4832,7 +4835,7 @@ }, { "id" : "minecraft:jungle_button", - "blockRuntimeId" : 5209 + "blockRuntimeId" : 5210 }, { "id" : "minecraft:acacia_button" @@ -4843,7 +4846,7 @@ }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 7195 + "blockRuntimeId" : 7192 }, { "id" : "minecraft:crimson_button", @@ -4851,23 +4854,23 @@ }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 7530 + "blockRuntimeId" : 7528 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 6001 + "blockRuntimeId" : 6004 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 7401 + "blockRuntimeId" : 7398 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 7887 + "blockRuntimeId" : 7885 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 7026 + "blockRuntimeId" : 7023 }, { "id" : "minecraft:birch_pressure_plate", @@ -4875,7 +4878,7 @@ }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 5269 + "blockRuntimeId" : 5270 }, { "id" : "minecraft:acacia_pressure_plate", @@ -4891,27 +4894,27 @@ }, { "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 7599 + "blockRuntimeId" : 7597 }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 7207 + "blockRuntimeId" : 7204 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 5500 + "blockRuntimeId" : 5501 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 5096 + "blockRuntimeId" : 5097 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6015 + "blockRuntimeId" : 6018 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 5725 + "blockRuntimeId" : 5726 }, { "id" : "minecraft:daylight_detector", @@ -4936,22 +4939,22 @@ }, { "id" : "minecraft:piston", - "blockRuntimeId" : 5786 + "blockRuntimeId" : 5789 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 7169 + "blockRuntimeId" : 7166 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 7353 + "blockRuntimeId" : 7350 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 5582 + "blockRuntimeId" : 5583 }, { "id" : "minecraft:banner" @@ -5042,6 +5045,9 @@ { "id" : "minecraft:piglin_banner_pattern" }, + { + "id" : "minecraft:globe_banner_pattern" + }, { "id" : "minecraft:firework_rocket", "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" @@ -5194,13 +5200,10 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 7351 + "blockRuntimeId" : 7348 }, { "id" : "minecraft:lodestone_compass" - }, - { - "id" : "minecraft:debug_stick" } ] } \ No newline at end of file diff --git a/core/src/main/resources/bedrock/creative_items.1_17_30.json b/core/src/main/resources/bedrock/creative_items.1_18_30.json similarity index 84% rename from core/src/main/resources/bedrock/creative_items.1_17_30.json rename to core/src/main/resources/bedrock/creative_items.1_18_30.json index bb73854dc..3cfb5cfc1 100644 --- a/core/src/main/resources/bedrock/creative_items.1_17_30.json +++ b/core/src/main/resources/bedrock/creative_items.1_18_30.json @@ -2,379 +2,379 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 5794 + "blockRuntimeId" : 5995 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5795 + "blockRuntimeId" : 5996 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5796 + "blockRuntimeId" : 5997 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5797 + "blockRuntimeId" : 5998 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5798 + "blockRuntimeId" : 5999 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5799 + "blockRuntimeId" : 6000 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 3839 + "blockRuntimeId" : 4806 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 7594 + "blockRuntimeId" : 928 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1318 + "blockRuntimeId" : 1187 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1319 + "blockRuntimeId" : 1188 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1320 + "blockRuntimeId" : 1189 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1321 + "blockRuntimeId" : 1190 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1322 + "blockRuntimeId" : 1191 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1323 + "blockRuntimeId" : 1192 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1330 + "blockRuntimeId" : 1199 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1325 + "blockRuntimeId" : 1194 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1326 + "blockRuntimeId" : 1195 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1324 + "blockRuntimeId" : 1193 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1327 + "blockRuntimeId" : 1196 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1331 + "blockRuntimeId" : 1200 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1328 + "blockRuntimeId" : 1197 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1329 + "blockRuntimeId" : 1198 }, { "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 507 + "blockRuntimeId" : 3919 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6038 + "blockRuntimeId" : 6640 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 5835 + "blockRuntimeId" : 978 }, { "id" : "minecraft:cobbled_deepslate_wall", - "blockRuntimeId" : 1155 + "blockRuntimeId" : 8024 }, { "id" : "minecraft:deepslate_tile_wall", - "blockRuntimeId" : 4297 + "blockRuntimeId" : 5027 }, { "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 6213 + "blockRuntimeId" : 7759 }, { "id" : "minecraft:deepslate_brick_wall", - "blockRuntimeId" : 4114 + "blockRuntimeId" : 437 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4773 + "blockRuntimeId" : 7306 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4774 + "blockRuntimeId" : 7307 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4775 + "blockRuntimeId" : 7308 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4776 + "blockRuntimeId" : 7309 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4777 + "blockRuntimeId" : 7310 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4778 + "blockRuntimeId" : 7311 }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 5686 + "blockRuntimeId" : 4238 }, { "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 3817 + "blockRuntimeId" : 7938 }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 7572 + "blockRuntimeId" : 5777 }, { "id" : "minecraft:fence_gate", - "blockRuntimeId" : 4779 - }, - { - "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 7006 - }, - { - "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 400 - }, - { - "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5252 - }, - { - "id" : "minecraft:acacia_fence_gate", - "blockRuntimeId" : 44 - }, - { - "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 3980 - }, - { - "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 3818 - }, - { - "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 7573 - }, - { - "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 5705 - }, - { - "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 7277 - }, - { - "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 5667 - }, - { - "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 5714 - }, - { - "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 7038 - }, - { - "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 432 - }, - { - "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 5284 - }, - { - "id" : "minecraft:acacia_stairs", "blockRuntimeId" : 76 }, { - "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 4012 + "id" : "minecraft:spruce_fence_gate", + "blockRuntimeId" : 6475 }, { - "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 7183 + "id" : "minecraft:birch_fence_gate", + "blockRuntimeId" : 3782 }, { - "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5675 + "id" : "minecraft:jungle_fence_gate", + "blockRuntimeId" : 5315 }, { - "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 6707 + "id" : "minecraft:acacia_fence_gate", + "blockRuntimeId" : 7528 }, { - "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 6899 + "id" : "minecraft:dark_oak_fence_gate", + "blockRuntimeId" : 4156 }, { - "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 6634 + "id" : "minecraft:crimson_fence_gate", + "blockRuntimeId" : 4617 }, { - "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 6891 + "id" : "minecraft:warped_fence_gate", + "blockRuntimeId" : 5349 }, { - "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 4988 + "id" : "minecraft:normal_stone_stairs", + "blockRuntimeId" : 641 }, { - "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 6383 + "id" : "minecraft:stone_stairs", + "blockRuntimeId" : 3713 }, { - "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 4475 + "id" : "minecraft:mossy_cobblestone_stairs", + "blockRuntimeId" : 4081 }, { - "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6375 + "id" : "minecraft:oak_stairs", + "blockRuntimeId" : 287 }, { - "id" : "minecraft:andesite_stairs", - "blockRuntimeId" : 144 + "id" : "minecraft:spruce_stairs", + "blockRuntimeId" : 128 }, { - "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 5811 + "id" : "minecraft:birch_stairs", + "blockRuntimeId" : 6957 }, { - "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 876 - }, - { - "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 5687 - }, - { - "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6622 - }, - { - "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 4719 - }, - { - "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 6556 - }, - { - "id" : "minecraft:smooth_quartz_stairs", + "id" : "minecraft:jungle_stairs", "blockRuntimeId" : 6883 }, + { + "id" : "minecraft:acacia_stairs", + "blockRuntimeId" : 6123 + }, + { + "id" : "minecraft:dark_oak_stairs", + "blockRuntimeId" : 5019 + }, + { + "id" : "minecraft:stone_brick_stairs", + "blockRuntimeId" : 939 + }, + { + "id" : "minecraft:mossy_stone_brick_stairs", + "blockRuntimeId" : 5807 + }, + { + "id" : "minecraft:sandstone_stairs", + "blockRuntimeId" : 3592 + }, + { + "id" : "minecraft:smooth_sandstone_stairs", + "blockRuntimeId" : 3632 + }, + { + "id" : "minecraft:red_sandstone_stairs", + "blockRuntimeId" : 5300 + }, + { + "id" : "minecraft:smooth_red_sandstone_stairs", + "blockRuntimeId" : 5496 + }, + { + "id" : "minecraft:granite_stairs", + "blockRuntimeId" : 3542 + }, + { + "id" : "minecraft:polished_granite_stairs", + "blockRuntimeId" : 4139 + }, + { + "id" : "minecraft:diorite_stairs", + "blockRuntimeId" : 4339 + }, + { + "id" : "minecraft:polished_diorite_stairs", + "blockRuntimeId" : 6588 + }, + { + "id" : "minecraft:andesite_stairs", + "blockRuntimeId" : 5258 + }, + { + "id" : "minecraft:polished_andesite_stairs", + "blockRuntimeId" : 6982 + }, + { + "id" : "minecraft:brick_stairs", + "blockRuntimeId" : 6421 + }, + { + "id" : "minecraft:nether_brick_stairs", + "blockRuntimeId" : 106 + }, + { + "id" : "minecraft:red_nether_brick_stairs", + "blockRuntimeId" : 6493 + }, + { + "id" : "minecraft:end_brick_stairs", + "blockRuntimeId" : 6347 + }, + { + "id" : "minecraft:quartz_stairs", + "blockRuntimeId" : 4723 + }, + { + "id" : "minecraft:smooth_quartz_stairs", + "blockRuntimeId" : 7642 + }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 6534 + "blockRuntimeId" : 7697 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 6446 + "blockRuntimeId" : 7205 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 4036 + "blockRuntimeId" : 7372 }, { "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 6438 + "blockRuntimeId" : 206 }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 3859 + "blockRuntimeId" : 6245 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 7614 + "blockRuntimeId" : 3723 }, { "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 499 + "blockRuntimeId" : 6973 }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 6030 + "blockRuntimeId" : 4245 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 5827 + "blockRuntimeId" : 4425 }, { "id" : "minecraft:cut_copper_stairs", - "blockRuntimeId" : 3912 + "blockRuntimeId" : 4528 }, { "id" : "minecraft:exposed_cut_copper_stairs", - "blockRuntimeId" : 4755 + "blockRuntimeId" : 4519 }, { "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 7741 + "blockRuntimeId" : 4253 }, { "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 5755 + "blockRuntimeId" : 361 }, { "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 7685 + "blockRuntimeId" : 403 }, { "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 7699 + "blockRuntimeId" : 3891 }, { "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 7727 + "blockRuntimeId" : 6091 }, { "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 7713 + "blockRuntimeId" : 5764 }, { "id" : "minecraft:cobbled_deepslate_stairs", - "blockRuntimeId" : 1147 + "blockRuntimeId" : 147 }, { "id" : "minecraft:deepslate_tile_stairs", - "blockRuntimeId" : 4289 + "blockRuntimeId" : 4609 }, { "id" : "minecraft:polished_deepslate_stairs", - "blockRuntimeId" : 6205 + "blockRuntimeId" : 308 }, { "id" : "minecraft:deepslate_brick_stairs", - "blockRuntimeId" : 4106 + "blockRuntimeId" : 7364 }, { "id" : "minecraft:wooden_door" @@ -405,747 +405,755 @@ }, { "id" : "minecraft:trapdoor", - "blockRuntimeId" : 7359 + "blockRuntimeId" : 227 }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 7062 + "blockRuntimeId" : 6443 }, { "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 456 + "blockRuntimeId" : 6524 }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5308 + "blockRuntimeId" : 5331 }, { "id" : "minecraft:acacia_trapdoor", - "blockRuntimeId" : 100 + "blockRuntimeId" : 5539 }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 4020 + "blockRuntimeId" : 7444 }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 5167 + "blockRuntimeId" : 335 }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 3886 + "blockRuntimeId" : 4281 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 7641 + "blockRuntimeId" : 4689 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 5132 + "blockRuntimeId" : 4757 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 4882 + "blockRuntimeId" : 6088 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7084 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7092 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7091 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7099 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7096 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7098 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7085 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7088 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7089 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7097 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7093 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7087 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7095 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7094 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7086 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7090 - }, - { - "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 7348 - }, - { - "id" : "minecraft:glass_pane", - "blockRuntimeId" : 4883 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7100 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7108 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7107 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7115 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7112 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7114 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7101 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7104 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7105 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7113 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7109 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7103 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7111 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7110 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7102 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7106 - }, - { - "id" : "minecraft:ladder", - "blockRuntimeId" : 5356 - }, - { - "id" : "minecraft:scaffolding", - "blockRuntimeId" : 6727 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7219 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7269 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7222 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7240 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7899 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7900 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7901 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7902 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7903 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7904 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7224 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7267 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7220 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7270 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7241 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7235 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7271 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7252 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7257 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7258 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7255 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7256 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7254 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7253 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7223 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7226 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7242 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7251 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7225 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7268 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7236 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7237 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7238 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7239 - }, - { - "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 3857 - }, - { - "id" : "minecraft:warped_slab", - "blockRuntimeId" : 7612 - }, - { - "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 497 - }, - { - "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6028 - }, - { - "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 5825 - }, - { - "id" : "minecraft:cut_copper_slab", - "blockRuntimeId" : 3910 - }, - { - "id" : "minecraft:exposed_cut_copper_slab", - "blockRuntimeId" : 4753 - }, - { - "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 7739 - }, - { - "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5753 - }, - { - "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7683 - }, - { - "id" : "minecraft:waxed_exposed_cut_copper_slab", - "blockRuntimeId" : 7697 - }, - { - "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 7725 - }, - { - "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 7711 - }, - { - "id" : "minecraft:cobbled_deepslate_slab", - "blockRuntimeId" : 1145 - }, - { - "id" : "minecraft:polished_deepslate_slab", - "blockRuntimeId" : 6203 - }, - { - "id" : "minecraft:deepslate_tile_slab", - "blockRuntimeId" : 4287 - }, - { - "id" : "minecraft:deepslate_brick_slab", - "blockRuntimeId" : 4104 - }, - { - "id" : "minecraft:brick_block", - "blockRuntimeId" : 875 - }, - { - "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 1130 - }, - { - "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 3768 - }, - { - "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6554 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7285 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7286 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7287 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7288 - }, - { - "id" : "minecraft:end_bricks", - "blockRuntimeId" : 4727 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6437 - }, - { - "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 5997 - }, - { - "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 3769 - }, - { - "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4881 - }, - { - "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 1131 - }, - { - "id" : "minecraft:deepslate_tiles", - "blockRuntimeId" : 4459 - }, - { - "id" : "minecraft:cracked_deepslate_tiles", - "blockRuntimeId" : 3767 - }, - { - "id" : "minecraft:deepslate_bricks", - "blockRuntimeId" : 4276 - }, - { - "id" : "minecraft:cracked_deepslate_bricks", - "blockRuntimeId" : 3766 - }, - { - "id" : "minecraft:chiseled_deepslate", - "blockRuntimeId" : 1129 - }, - { - "id" : "minecraft:cobblestone", - "blockRuntimeId" : 1317 - }, - { - "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 5666 - }, - { - "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 1142 - }, - { - "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 6907 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6703 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6704 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6705 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 6706 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6630 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6631 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6632 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6633 - }, - { - "id" : "minecraft:coal_block", "blockRuntimeId" : 1140 }, { - "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 4583 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1148 }, { - "id" : "minecraft:gold_block", - "blockRuntimeId" : 4974 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1147 }, { - "id" : "minecraft:iron_block", - "blockRuntimeId" : 5133 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1155 }, { - "id" : "minecraft:copper_block", - "blockRuntimeId" : 3676 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1152 }, { - "id" : "minecraft:exposed_copper", - "blockRuntimeId" : 4751 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1154 }, { - "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 7737 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1141 }, { - "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 5751 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1144 }, { - "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7681 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1145 }, { - "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 7695 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1153 }, { - "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 7723 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1149 }, { - "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7709 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1143 }, { - "id" : "minecraft:cut_copper", - "blockRuntimeId" : 3909 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1151 }, { - "id" : "minecraft:exposed_cut_copper", - "blockRuntimeId" : 4752 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1150 }, { - "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7738 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1142 }, { - "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5752 + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1146 }, { - "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7682 + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 5899 }, { - "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 7696 + "id" : "minecraft:glass_pane", + "blockRuntimeId" : 5189 }, { - "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 7724 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4808 }, { - "id" : "minecraft:waxed_oxidized_cut_copper", - "blockRuntimeId" : 7710 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4816 }, { - "id" : "minecraft:emerald_block", - "blockRuntimeId" : 4716 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4815 }, { - "id" : "minecraft:diamond_block", - "blockRuntimeId" : 4473 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4823 }, { - "id" : "minecraft:lapis_block", - "blockRuntimeId" : 5364 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4820 }, { - "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 6576 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4822 }, { - "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 6574 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4809 }, { - "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 6575 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4812 }, { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6542 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4813 }, { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6544 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4821 }, { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6543 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4817 }, { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6545 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4811 }, { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6435 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4819 }, { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 6436 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4818 }, { - "id" : "minecraft:slime", - "blockRuntimeId" : 6860 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4810 }, { - "id" : "minecraft:honey_block", - "blockRuntimeId" : 5111 + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 4814 }, { - "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 5112 + "id" : "minecraft:ladder", + "blockRuntimeId" : 8204 }, { - "id" : "minecraft:hay_block", - "blockRuntimeId" : 5083 + "id" : "minecraft:scaffolding", + "blockRuntimeId" : 3576 }, { - "id" : "minecraft:bone_block", - "blockRuntimeId" : 692 + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 249 }, { - "id" : "minecraft:nether_brick", - "blockRuntimeId" : 5685 + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 6632 }, { - "id" : "minecraft:red_nether_brick", + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 252 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6603 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5220 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5221 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5222 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5223 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5224 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 5225 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 254 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 6630 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 250 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 6633 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6604 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6598 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 6634 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6615 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6620 + }, + { + "id" : "minecraft:double_stone_slab3", "blockRuntimeId" : 6621 }, { - "id" : "minecraft:netherite_block", - "blockRuntimeId" : 5702 + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6618 }, { - "id" : "minecraft:lodestone", - "blockRuntimeId" : 5562 + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6619 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7911 + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6617 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7919 + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6616 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7918 + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 253 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7926 + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 256 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7923 + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6605 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7925 + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 6614 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7912 + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 255 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7915 + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 6631 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7916 + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6599 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7924 + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6600 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7920 + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6601 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7914 + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 6602 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7922 + "id" : "minecraft:crimson_slab", + "blockRuntimeId" : 5824 }, { - "id" : "minecraft:wool", + "id" : "minecraft:warped_slab", + "blockRuntimeId" : 6375 + }, + { + "id" : "minecraft:blackstone_slab", + "blockRuntimeId" : 918 + }, + { + "id" : "minecraft:polished_blackstone_slab", + "blockRuntimeId" : 5942 + }, + { + "id" : "minecraft:polished_blackstone_brick_slab", + "blockRuntimeId" : 4175 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 5191 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 6491 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 5977 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 5232 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 7757 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 247 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 6436 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 716 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 7252 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 302 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 4239 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 3721 + }, + { + "id" : "minecraft:brick_block", + "blockRuntimeId" : 4721 + }, + { + "id" : "minecraft:chiseled_nether_bricks", + "blockRuntimeId" : 7191 + }, + { + "id" : "minecraft:cracked_nether_bricks", + "blockRuntimeId" : 4484 + }, + { + "id" : "minecraft:quartz_bricks", + "blockRuntimeId" : 6316 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6438 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6439 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6440 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 6441 + }, + { + "id" : "minecraft:end_bricks", + "blockRuntimeId" : 295 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6011 + }, + { + "id" : "minecraft:polished_blackstone_bricks", + "blockRuntimeId" : 4636 + }, + { + "id" : "minecraft:cracked_polished_blackstone_bricks", + "blockRuntimeId" : 7156 + }, + { + "id" : "minecraft:gilded_blackstone", + "blockRuntimeId" : 4518 + }, + { + "id" : "minecraft:chiseled_polished_blackstone", + "blockRuntimeId" : 5018 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 4513 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 4149 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 5414 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 5314 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 5190 + }, + { + "id" : "minecraft:cobblestone", + "blockRuntimeId" : 3620 + }, + { + "id" : "minecraft:mossy_cobblestone", + "blockRuntimeId" : 266 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 6544 + }, + { + "id" : "minecraft:smooth_stone", + "blockRuntimeId" : 4514 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3658 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3659 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3660 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 3661 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6471 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6472 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6473 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 6474 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 5348 + }, + { + "id" : "minecraft:dried_kelp_block", "blockRuntimeId" : 7921 }, { - "id" : "minecraft:wool", - "blockRuntimeId" : 7913 + "id" : "minecraft:gold_block", + "blockRuntimeId" : 305 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 8203 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 4607 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 601 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 8188 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 3558 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 7676 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 702 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 715 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 7484 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 4645 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 6090 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 7139 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 5428 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 7235 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 3814 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 4807 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 214 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 1164 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 286 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 4234 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 8202 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 5219 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 371 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3701 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3703 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3702 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 3704 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6009 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 6010 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 4197 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 900 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 4424 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 703 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 4198 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 7214 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 146 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 3780 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 8201 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7917 + "blockRuntimeId" : 3463 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3471 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3470 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3478 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3475 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3477 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3464 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3467 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3468 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3476 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3472 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3466 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3474 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3473 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3465 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 3469 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 956 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 964 }, { "id" : "minecraft:carpet", @@ -1155,53 +1163,29 @@ "id" : "minecraft:carpet", "blockRuntimeId" : 971 }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 970 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 978 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 975 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 977 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 964 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 967 - }, { "id" : "minecraft:carpet", "blockRuntimeId" : 968 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 976 + "blockRuntimeId" : 970 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 972 + "blockRuntimeId" : 957 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 966 + "blockRuntimeId" : 960 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 974 + "blockRuntimeId" : 961 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 973 + "blockRuntimeId" : 969 }, { "id" : "minecraft:carpet", @@ -1209,667 +1193,683 @@ }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 969 + "blockRuntimeId" : 959 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 967 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 966 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 958 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 962 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3659 + "blockRuntimeId" : 6229 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3667 + "blockRuntimeId" : 6237 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3666 + "blockRuntimeId" : 6236 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3674 + "blockRuntimeId" : 6244 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3671 + "blockRuntimeId" : 6241 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3673 + "blockRuntimeId" : 6243 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3660 + "blockRuntimeId" : 6230 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3663 + "blockRuntimeId" : 6233 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3664 + "blockRuntimeId" : 6234 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3672 + "blockRuntimeId" : 6242 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3668 + "blockRuntimeId" : 6238 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3662 + "blockRuntimeId" : 6232 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3670 + "blockRuntimeId" : 6240 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3669 + "blockRuntimeId" : 6239 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3661 + "blockRuntimeId" : 6231 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3665 + "blockRuntimeId" : 6235 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3643 + "blockRuntimeId" : 668 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3651 + "blockRuntimeId" : 676 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3650 + "blockRuntimeId" : 675 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3658 + "blockRuntimeId" : 683 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3655 + "blockRuntimeId" : 680 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3657 + "blockRuntimeId" : 682 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3644 + "blockRuntimeId" : 669 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3647 + "blockRuntimeId" : 672 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3648 + "blockRuntimeId" : 673 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3656 + "blockRuntimeId" : 681 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3652 + "blockRuntimeId" : 677 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3646 + "blockRuntimeId" : 671 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3654 + "blockRuntimeId" : 679 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3653 + "blockRuntimeId" : 678 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3645 + "blockRuntimeId" : 670 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3649 + "blockRuntimeId" : 674 }, { "id" : "minecraft:clay", - "blockRuntimeId" : 1139 + "blockRuntimeId" : 7066 }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 5082 + "blockRuntimeId" : 649 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7116 + "blockRuntimeId" : 6099 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7124 + "blockRuntimeId" : 6107 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7123 + "blockRuntimeId" : 6106 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7131 + "blockRuntimeId" : 6114 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7128 + "blockRuntimeId" : 6111 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7130 + "blockRuntimeId" : 6113 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7117 + "blockRuntimeId" : 6100 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7120 + "blockRuntimeId" : 6103 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7121 + "blockRuntimeId" : 6104 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7129 + "blockRuntimeId" : 6112 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7125 + "blockRuntimeId" : 6108 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7119 + "blockRuntimeId" : 6102 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7127 + "blockRuntimeId" : 6110 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7126 + "blockRuntimeId" : 6109 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7118 + "blockRuntimeId" : 6101 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7122 + "blockRuntimeId" : 6105 }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 7796 + "blockRuntimeId" : 5523 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 6842 + "blockRuntimeId" : 3536 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 5009 + "blockRuntimeId" : 8195 }, { "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 488 + "blockRuntimeId" : 5758 }, { "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 894 + "blockRuntimeId" : 3552 }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 6598 + "blockRuntimeId" : 4150 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 5745 + "blockRuntimeId" : 1156 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 7938 + "blockRuntimeId" : 921 }, { "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 5531 + "blockRuntimeId" : 221 }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 5025 + "blockRuntimeId" : 6501 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 3930 + "blockRuntimeId" : 5308 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5483 + "blockRuntimeId" : 5421 }, { "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 685 + "blockRuntimeId" : 5415 }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 6516 + "blockRuntimeId" : 6965 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 5595 + "blockRuntimeId" : 972 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 5776 + "blockRuntimeId" : 6430 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 6522 + "blockRuntimeId" : 7656 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 6524 + "blockRuntimeId" : 7658 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 5701 + "blockRuntimeId" : 4241 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 7663 + "blockRuntimeId" : 5829 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 6825 + "blockRuntimeId" : 5017 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 3838 + "blockRuntimeId" : 4172 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 7593 + "blockRuntimeId" : 6314 }, { "id" : "minecraft:basalt", - "blockRuntimeId" : 214 + "blockRuntimeId" : 4297 }, { "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 5819 + "blockRuntimeId" : 24 }, { "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 6882 + "blockRuntimeId" : 1162 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 6952 + "blockRuntimeId" : 5738 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 4483 + "blockRuntimeId" : 5701 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 4484 + "blockRuntimeId" : 5702 }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 3901 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 4996 + "blockRuntimeId" : 6891 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 4997 + "blockRuntimeId" : 8023 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 5800 + "blockRuntimeId" : 4606 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 3688 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7176 + "blockRuntimeId" : 661 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 5166 + "blockRuntimeId" : 4646 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 4975 - }, - { - "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 4474 - }, - { - "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 5365 - }, - { - "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 6644 - }, - { - "id" : "minecraft:coal_ore", - "blockRuntimeId" : 1141 - }, - { - "id" : "minecraft:copper_ore", - "blockRuntimeId" : 3677 - }, - { - "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 4717 - }, - { - "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 6555 - }, - { - "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 5695 - }, - { - "id" : "minecraft:ancient_debris", - "blockRuntimeId" : 143 - }, - { - "id" : "minecraft:deepslate_iron_ore", - "blockRuntimeId" : 4282 - }, - { - "id" : "minecraft:deepslate_gold_ore", - "blockRuntimeId" : 4281 - }, - { - "id" : "minecraft:deepslate_diamond_ore", - "blockRuntimeId" : 4279 - }, - { - "id" : "minecraft:deepslate_lapis_ore", - "blockRuntimeId" : 4283 - }, - { - "id" : "minecraft:deepslate_redstone_ore", - "blockRuntimeId" : 4284 - }, - { - "id" : "minecraft:deepslate_emerald_ore", - "blockRuntimeId" : 4280 - }, - { - "id" : "minecraft:deepslate_coal_ore", - "blockRuntimeId" : 4277 - }, - { - "id" : "minecraft:deepslate_copper_ore", - "blockRuntimeId" : 4278 - }, - { - "id" : "minecraft:gravel", - "blockRuntimeId" : 4998 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7177 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7179 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7181 - }, - { - "id" : "minecraft:blackstone", - "blockRuntimeId" : 494 - }, - { - "id" : "minecraft:deepslate", - "blockRuntimeId" : 4099 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7178 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7180 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7182 - }, - { - "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 5822 - }, - { - "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 6200 - }, - { - "id" : "minecraft:sand", - "blockRuntimeId" : 6701 - }, - { - "id" : "minecraft:sand", - "blockRuntimeId" : 6702 - }, - { - "id" : "minecraft:cactus", "blockRuntimeId" : 920 }, + { + "id" : "minecraft:diamond_ore", + "blockRuntimeId" : 4309 + }, + { + "id" : "minecraft:lapis_ore", + "blockRuntimeId" : 7641 + }, + { + "id" : "minecraft:redstone_ore", + "blockRuntimeId" : 4237 + }, + { + "id" : "minecraft:coal_ore", + "blockRuntimeId" : 4235 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 3559 + }, + { + "id" : "minecraft:emerald_ore", + "blockRuntimeId" : 7289 + }, + { + "id" : "minecraft:quartz_ore", + "blockRuntimeId" : 4433 + }, + { + "id" : "minecraft:nether_gold_ore", + "blockRuntimeId" : 27 + }, + { + "id" : "minecraft:ancient_debris", + "blockRuntimeId" : 6031 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 7215 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 6030 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 7980 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 7204 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 6507 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 6315 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 7138 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 105 + }, + { + "id" : "minecraft:gravel", + "blockRuntimeId" : 8226 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 662 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 664 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 666 + }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 7527 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 267 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 663 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 665 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 667 + }, + { + "id" : "minecraft:polished_blackstone", + "blockRuntimeId" : 3687 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 7696 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4178 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 4179 + }, + { + "id" : "minecraft:cactus", + "blockRuntimeId" : 6940 + }, { "id" : "minecraft:log", - "blockRuntimeId" : 5563 + "blockRuntimeId" : 6546 }, { "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7315 + "blockRuntimeId" : 7485 }, { "id" : "minecraft:log", - "blockRuntimeId" : 5564 + "blockRuntimeId" : 6547 }, { "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 7318 + "blockRuntimeId" : 6253 }, { "id" : "minecraft:log", - "blockRuntimeId" : 5565 + "blockRuntimeId" : 6548 }, { "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 7300 + "blockRuntimeId" : 5896 }, { "id" : "minecraft:log", - "blockRuntimeId" : 5566 + "blockRuntimeId" : 6549 }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 7312 + "blockRuntimeId" : 650 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 5575 - }, - { - "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 7297 - }, - { - "id" : "minecraft:log2", - "blockRuntimeId" : 5576 - }, - { - "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 7309 - }, - { - "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 3883 - }, - { - "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 7306 - }, - { - "id" : "minecraft:warped_stem", - "blockRuntimeId" : 7638 - }, - { - "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7324 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7803 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7809 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7804 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7810 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7805 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7811 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7806 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7812 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7807 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7813 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7808 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7814 - }, - { - "id" : "minecraft:crimson_hyphae", "blockRuntimeId" : 3835 }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 5772 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 3836 + }, + { + "id" : "minecraft:stripped_dark_oak_log", + "blockRuntimeId" : 216 + }, + { + "id" : "minecraft:crimson_stem", + "blockRuntimeId" : 5821 + }, + { + "id" : "minecraft:stripped_crimson_stem", + "blockRuntimeId" : 6864 + }, + { + "id" : "minecraft:warped_stem", + "blockRuntimeId" : 6377 + }, + { + "id" : "minecraft:stripped_warped_stem", + "blockRuntimeId" : 7342 + }, + { + "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:wood", + "blockRuntimeId" : 3482 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3488 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3483 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3489 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3484 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 3490 + }, + { + "id" : "minecraft:crimson_hyphae", + "blockRuntimeId" : 4242 + }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 7303 + "blockRuntimeId" : 6390 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 7590 + "blockRuntimeId" : 5826 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 7321 + "blockRuntimeId" : 5529 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 5409 + "blockRuntimeId" : 6014 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 5410 + "blockRuntimeId" : 6015 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 5411 + "blockRuntimeId" : 6016 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 5412 + "blockRuntimeId" : 6017 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 5425 + "blockRuntimeId" : 4301 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 5426 + "blockRuntimeId" : 4302 }, { "id" : "minecraft:azalea_leaves", - "blockRuntimeId" : 169 + "blockRuntimeId" : 7652 }, { "id" : "minecraft:azalea_leaves_flowered", - "blockRuntimeId" : 173 + "blockRuntimeId" : 6304 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6715 + "blockRuntimeId" : 720 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6716 + "blockRuntimeId" : 721 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6717 + "blockRuntimeId" : 722 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6718 + "blockRuntimeId" : 723 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6719 + "blockRuntimeId" : 724 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6720 + "blockRuntimeId" : 725 }, { "id" : "minecraft:bee_nest", - "blockRuntimeId" : 236 + "blockRuntimeId" : 5704 }, { "id" : "minecraft:wheat_seeds" @@ -1912,7 +1912,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 5608 + "blockRuntimeId" : 402 }, { "id" : "minecraft:melon_slice" @@ -1928,200 +1928,200 @@ }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 6454 + "blockRuntimeId" : 4509 }, { "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 988 + "blockRuntimeId" : 7320 }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 5550 + "blockRuntimeId" : 6559 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 7345 + "blockRuntimeId" : 937 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4503 + "blockRuntimeId" : 5405 }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 7344 + "blockRuntimeId" : 936 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4502 + "blockRuntimeId" : 5404 }, { "id" : "minecraft:nether_sprouts" }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3681 + "blockRuntimeId" : 6383 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3679 + "blockRuntimeId" : 6381 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3680 + "blockRuntimeId" : 6382 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3678 + "blockRuntimeId" : 6380 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3682 + "blockRuntimeId" : 6384 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3686 + "blockRuntimeId" : 6388 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3684 + "blockRuntimeId" : 6386 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3685 + "blockRuntimeId" : 6387 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3683 + "blockRuntimeId" : 6385 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3687 + "blockRuntimeId" : 6389 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3701 + "blockRuntimeId" : 4540 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3699 + "blockRuntimeId" : 4538 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3700 + "blockRuntimeId" : 4539 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3698 + "blockRuntimeId" : 4537 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3702 + "blockRuntimeId" : 4541 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3711 + "blockRuntimeId" : 69 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3709 + "blockRuntimeId" : 67 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3710 + "blockRuntimeId" : 68 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3708 + "blockRuntimeId" : 66 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3712 + "blockRuntimeId" : 70 }, { "id" : "minecraft:kelp" }, { "id" : "minecraft:seagrass", - "blockRuntimeId" : 6821 + "blockRuntimeId" : 244 }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 3856 + "blockRuntimeId" : 7515 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 7611 + "blockRuntimeId" : 4310 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 7937 + "blockRuntimeId" : 316 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6587 + "blockRuntimeId" : 3621 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6588 + "blockRuntimeId" : 3622 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6589 + "blockRuntimeId" : 3623 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6590 + "blockRuntimeId" : 3624 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6591 + "blockRuntimeId" : 3625 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6592 + "blockRuntimeId" : 3626 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6593 + "blockRuntimeId" : 3627 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6594 + "blockRuntimeId" : 3628 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6595 + "blockRuntimeId" : 3629 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6596 + "blockRuntimeId" : 3630 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 6597 + "blockRuntimeId" : 3631 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4500 + "blockRuntimeId" : 5402 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4501 + "blockRuntimeId" : 5403 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4504 + "blockRuntimeId" : 5406 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4505 + "blockRuntimeId" : 5407 }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 7802 + "blockRuntimeId" : 6089 }, { "id" : "minecraft:white_dye" @@ -2188,131 +2188,127 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 7498 + "blockRuntimeId" : 902 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 7752 + "blockRuntimeId" : 5429 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 7426 + "blockRuntimeId" : 5641 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 7680 + "blockRuntimeId" : 1163 }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 4098 + "blockRuntimeId" : 4633 }, { "id" : "minecraft:bamboo", - "blockRuntimeId" : 177 + "blockRuntimeId" : 3689 }, { "id" : "minecraft:snow", - "blockRuntimeId" : 6908 + "blockRuntimeId" : 4177 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 5125 + "blockRuntimeId" : 6563 }, { "id" : "minecraft:packed_ice", - "blockRuntimeId" : 5765 + "blockRuntimeId" : 296 }, { "id" : "minecraft:blue_ice", - "blockRuntimeId" : 691 + "blockRuntimeId" : 6981 }, { "id" : "minecraft:snow_layer", - "blockRuntimeId" : 6909 + "blockRuntimeId" : 155 }, { "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 5806 - }, - { - "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 6745 + "blockRuntimeId" : 7358 }, { "id" : "minecraft:dripstone_block", - "blockRuntimeId" : 4584 + "blockRuntimeId" : 901 }, { "id" : "minecraft:moss_carpet", - "blockRuntimeId" : 5665 + "blockRuntimeId" : 300 }, { "id" : "minecraft:moss_block", - "blockRuntimeId" : 5664 + "blockRuntimeId" : 6429 }, { "id" : "minecraft:dirt_with_roots", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 5347 }, { "id" : "minecraft:hanging_roots", - "blockRuntimeId" : 5047 + "blockRuntimeId" : 205 }, { "id" : "minecraft:big_dripleaf", - "blockRuntimeId" : 328 + "blockRuntimeId" : 5904 }, { "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 6874 + "blockRuntimeId" : 4268 }, { "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 6961 + "blockRuntimeId" : 7254 }, { "id" : "minecraft:azalea", - "blockRuntimeId" : 168 + "blockRuntimeId" : 6804 }, { "id" : "minecraft:flowering_azalea", - "blockRuntimeId" : 4814 + "blockRuntimeId" : 5427 }, { "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 4971 + "blockRuntimeId" : 5634 }, { "id" : "minecraft:amethyst_block", - "blockRuntimeId" : 136 + "blockRuntimeId" : 304 }, { "id" : "minecraft:budding_amethyst", - "blockRuntimeId" : 919 + "blockRuntimeId" : 6956 }, { "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 137 + "blockRuntimeId" : 7752 }, { "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 5366 + "blockRuntimeId" : 4684 }, { "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 5602 + "blockRuntimeId" : 4324 }, { "id" : "minecraft:small_amethyst_bud", - "blockRuntimeId" : 6861 + "blockRuntimeId" : 318 }, { "id" : "minecraft:tuff", - "blockRuntimeId" : 7413 + "blockRuntimeId" : 360 }, { "id" : "minecraft:calcite", - "blockRuntimeId" : 943 + "blockRuntimeId" : 215 }, { "id" : "minecraft:chicken" @@ -2343,35 +2339,35 @@ }, { "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 900 + "blockRuntimeId" : 3551 }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 6604 + "blockRuntimeId" : 4517 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 3834 + "blockRuntimeId" : 7695 }, { "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 7589 + "blockRuntimeId" : 301 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 915 + "blockRuntimeId" : 7304 }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 6619 + "blockRuntimeId" : 3616 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 916 + "blockRuntimeId" : 7305 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 901 + "blockRuntimeId" : 7290 }, { "id" : "minecraft:egg" @@ -2390,50 +2386,50 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 7751 + "blockRuntimeId" : 6587 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 5657 + "blockRuntimeId" : 411 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5658 + "blockRuntimeId" : 4133 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5659 + "blockRuntimeId" : 4134 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5660 + "blockRuntimeId" : 4135 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5661 + "blockRuntimeId" : 4136 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5662 + "blockRuntimeId" : 4137 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5663 + "blockRuntimeId" : 4138 }, { "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 5126 + "blockRuntimeId" : 4597 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 4582 + "blockRuntimeId" : 7213 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7414 + "blockRuntimeId" : 7939 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2635,42 +2631,42 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 5734 + "blockRuntimeId" : 436 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 3908 + "blockRuntimeId" : 6596 }, { "id" : "minecraft:bedrock", - "blockRuntimeId" : 234 + "blockRuntimeId" : 6971 }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 6951 + "blockRuntimeId" : 5739 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 5703 + "blockRuntimeId" : 6991 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 5601 + "blockRuntimeId" : 7951 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 4744 + "blockRuntimeId" : 3841 }, { "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 1132 + "blockRuntimeId" : 4462 }, { "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 1138 + "blockRuntimeId" : 5455 }, { "id" : "minecraft:chorus_fruit" @@ -2680,51 +2676,51 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 6959 + "blockRuntimeId" : 637 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 6960 + "blockRuntimeId" : 638 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3688 + "blockRuntimeId" : 5193 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3689 + "blockRuntimeId" : 5194 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3690 + "blockRuntimeId" : 5195 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3691 + "blockRuntimeId" : 5196 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3692 + "blockRuntimeId" : 5197 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3693 + "blockRuntimeId" : 5198 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3694 + "blockRuntimeId" : 5199 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3695 + "blockRuntimeId" : 5200 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3696 + "blockRuntimeId" : 5201 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3697 + "blockRuntimeId" : 5202 }, { "id" : "minecraft:leather_helmet" @@ -3751,111 +3747,110 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 7353 + "blockRuntimeId" : 732 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 6953 + "blockRuntimeId" : 4600 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 6813 + "blockRuntimeId" : 5779 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 5362 + "blockRuntimeId" : 7016 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 6949 + "blockRuntimeId" : 5699 }, { "id" : "minecraft:candle", - "blockRuntimeId" : 953 + "blockRuntimeId" : 7345 }, { "id" : "minecraft:white_candle", - "blockRuntimeId" : 7786 + "blockRuntimeId" : 5250 }, { "id" : "minecraft:orange_candle", - "blockRuntimeId" : 5735 + "blockRuntimeId" : 372 }, { "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 5585 + "blockRuntimeId" : 428 }, { "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 5473 + "blockRuntimeId" : 4501 }, { "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 7927 + "blockRuntimeId" : 6115 }, { "id" : "minecraft:lime_candle", - "blockRuntimeId" : 5521 + "blockRuntimeId" : 6333 }, { "id" : "minecraft:pink_candle", - "blockRuntimeId" : 5766 + "blockRuntimeId" : 7312 }, { "id" : "minecraft:gray_candle", - "blockRuntimeId" : 4999 + "blockRuntimeId" : 947 }, { "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 5489 + "blockRuntimeId" : 6147 }, { "id" : "minecraft:cyan_candle", - "blockRuntimeId" : 3920 + "blockRuntimeId" : 7668 }, { "id" : "minecraft:purple_candle", - "blockRuntimeId" : 6506 + "blockRuntimeId" : 6992 }, { - "id" : "minecraft:blue_candle", - "blockRuntimeId" : 675 + "id" : "minecraft:blue_candle" }, { "id" : "minecraft:brown_candle", - "blockRuntimeId" : 884 + "blockRuntimeId" : 5799 }, { "id" : "minecraft:green_candle", - "blockRuntimeId" : 5015 + "blockRuntimeId" : 694 }, { "id" : "minecraft:red_candle", - "blockRuntimeId" : 6577 + "blockRuntimeId" : 4637 }, { "id" : "minecraft:black_candle", - "blockRuntimeId" : 478 + "blockRuntimeId" : 171 }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 3770 + "blockRuntimeId" : 5778 }, { "id" : "minecraft:cartography_table", - "blockRuntimeId" : 987 + "blockRuntimeId" : 8227 }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 4811 + "blockRuntimeId" : 5757 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 6875 + "blockRuntimeId" : 3731 }, { "id" : "minecraft:beehive", - "blockRuntimeId" : 260 + "blockRuntimeId" : 6032 }, { "id" : "minecraft:campfire" @@ -3865,152 +3860,152 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 4875 + "blockRuntimeId" : 7744 }, { "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 669 + "blockRuntimeId" : 7509 }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 6876 + "blockRuntimeId" : 655 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 6696 + "blockRuntimeId" : 689 }, { "id" : "minecraft:brewing_stand" }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 152 + "blockRuntimeId" : 6508 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 156 + "blockRuntimeId" : 6512 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 160 + "blockRuntimeId" : 6516 }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 5031 + "blockRuntimeId" : 7981 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 4718 + "blockRuntimeId" : 6597 }, { "id" : "minecraft:bookshelf", - "blockRuntimeId" : 704 + "blockRuntimeId" : 6545 }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 5433 + "blockRuntimeId" : 6856 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 3634 + "blockRuntimeId" : 5365 }, { "id" : "minecraft:chest", - "blockRuntimeId" : 1123 + "blockRuntimeId" : 7057 }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 7375 + "blockRuntimeId" : 5533 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4745 + "blockRuntimeId" : 4317 }, { "id" : "minecraft:barrel", - "blockRuntimeId" : 201 + "blockRuntimeId" : 4450 }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 7458 + "blockRuntimeId" : 3686 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6826 + "blockRuntimeId" : 5266 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6834 + "blockRuntimeId" : 5274 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6833 + "blockRuntimeId" : 5273 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6841 + "blockRuntimeId" : 5281 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6838 + "blockRuntimeId" : 5278 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6840 + "blockRuntimeId" : 5280 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6827 + "blockRuntimeId" : 5267 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6830 + "blockRuntimeId" : 5270 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6831 + "blockRuntimeId" : 5271 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6839 + "blockRuntimeId" : 5279 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6835 + "blockRuntimeId" : 5275 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6829 + "blockRuntimeId" : 5269 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6837 + "blockRuntimeId" : 5277 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6836 + "blockRuntimeId" : 5276 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6828 + "blockRuntimeId" : 5268 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6832 + "blockRuntimeId" : 5272 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 5713 + "blockRuntimeId" : 359 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 5207 + "blockRuntimeId" : 4830 }, { "id" : "minecraft:music_disc_13" @@ -4048,6 +4043,9 @@ { "id" : "minecraft:music_disc_wait" }, + { + "id" : "minecraft:music_disc_otherside" + }, { "id" : "minecraft:music_disc_pigstep" }, @@ -4056,15 +4054,15 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 4973 + "blockRuntimeId" : 3874 }, { "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 6643 + "blockRuntimeId" : 265 }, { - "id" : "minecraft:sealantern", - "blockRuntimeId" : 6824 + "id" : "minecraft:sea_lantern", + "blockRuntimeId" : 7488 }, { "id" : "minecraft:oak_sign" @@ -4163,23 +4161,23 @@ }, { "id" : "minecraft:beacon", - "blockRuntimeId" : 217 + "blockRuntimeId" : 145 }, { "id" : "minecraft:bell", - "blockRuntimeId" : 292 + "blockRuntimeId" : 6824 }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 3675 + "blockRuntimeId" : 4196 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7291 + "blockRuntimeId" : 7516 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 4730 + "blockRuntimeId" : 6001 }, { "id" : "minecraft:coal" @@ -4315,11 +4313,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 4738 + "blockRuntimeId" : 5815 }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 5515 + "blockRuntimeId" : 1181 }, { "id" : "minecraft:end_crystal" @@ -4781,19 +4779,19 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 6564 + "blockRuntimeId" : 3909 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 4976 + "blockRuntimeId" : 5282 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 4461 + "blockRuntimeId" : 4121 }, { "id" : "minecraft:activator_rail", - "blockRuntimeId" : 122 + "blockRuntimeId" : 323 }, { "id" : "minecraft:minecart" @@ -4812,114 +4810,115 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 6642 + "blockRuntimeId" : 3781 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 6645 + "blockRuntimeId" : 3530 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 5441 + "blockRuntimeId" : 6405 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 7839 + "blockRuntimeId" : 6356 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 6962 + "blockRuntimeId" : 4269 }, { "id" : "minecraft:birch_button", - "blockRuntimeId" : 356 + "blockRuntimeId" : 7708 }, { "id" : "minecraft:jungle_button", - "blockRuntimeId" : 5208 + "blockRuntimeId" : 116 }, { - "id" : "minecraft:acacia_button" + "id" : "minecraft:acacia_button", + "blockRuntimeId" : 7173 }, { "id" : "minecraft:dark_oak_button", - "blockRuntimeId" : 3936 + "blockRuntimeId" : 93 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 7191 + "blockRuntimeId" : 604 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 3771 + "blockRuntimeId" : 4380 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 7526 + "blockRuntimeId" : 7192 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 5998 + "blockRuntimeId" : 7732 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 7397 + "blockRuntimeId" : 5838 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 7883 + "blockRuntimeId" : 8005 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 7022 + "blockRuntimeId" : 3764 }, { "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 416 + "blockRuntimeId" : 3560 }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 5268 + "blockRuntimeId" : 3640 }, { "id" : "minecraft:acacia_pressure_plate", - "blockRuntimeId" : 60 + "blockRuntimeId" : 5203 }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 3996 + "blockRuntimeId" : 5880 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 3840 + "blockRuntimeId" : 8210 }, { "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 7595 + "blockRuntimeId" : 270 }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 7203 + "blockRuntimeId" : 3875 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 5499 + "blockRuntimeId" : 3670 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 5095 + "blockRuntimeId" : 1165 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6012 + "blockRuntimeId" : 6197 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 5722 + "blockRuntimeId" : 3518 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 4066 + "blockRuntimeId" : 4180 }, { "id" : "minecraft:repeater" @@ -4932,30 +4931,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 4588 + "blockRuntimeId" : 7327 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 4489 + "blockRuntimeId" : 7955 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 5783 + "blockRuntimeId" : 930 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 7165 + "blockRuntimeId" : 4312 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 7349 + "blockRuntimeId" : 6581 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 5581 + "blockRuntimeId" : 3831 }, { "id" : "minecraft:banner" @@ -5046,6 +5045,9 @@ { "id" : "minecraft:piglin_banner_pattern" }, + { + "id" : "minecraft:globe_banner_pattern" + }, { "id" : "minecraft:firework_rocket", "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" @@ -5198,7 +5200,7 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 7347 + "blockRuntimeId" : 6355 }, { "id" : "minecraft:lodestone_compass" diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_17_40.json b/core/src/main/resources/bedrock/runtime_item_states.1_18_10.json similarity index 98% rename from core/src/main/resources/bedrock/runtime_item_states.1_17_40.json rename to core/src/main/resources/bedrock/runtime_item_states.1_18_10.json index 342a45afb..5bebcaf99 100644 --- a/core/src/main/resources/bedrock/runtime_item_states.1_17_40.json +++ b/core/src/main/resources/bedrock/runtime_item_states.1_18_10.json @@ -51,6 +51,10 @@ "name" : "minecraft:air", "id" : -158 }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 631 + }, { "name" : "minecraft:allow", "id" : 210 @@ -65,7 +69,7 @@ }, { "name" : "minecraft:amethyst_shard", - "id" : 624 + "id" : 625 }, { "name" : "minecraft:ancient_debris", @@ -133,7 +137,7 @@ }, { "name" : "minecraft:banner_pattern", - "id" : 628 + "id" : 635 }, { "name" : "minecraft:barrel", @@ -317,7 +321,7 @@ }, { "name" : "minecraft:boat", - "id" : 626 + "id" : 633 }, { "name" : "minecraft:bone", @@ -433,7 +437,7 @@ }, { "name" : "minecraft:campfire", - "id" : 588 + "id" : 589 }, { "name" : "minecraft:candle", @@ -493,7 +497,7 @@ }, { "name" : "minecraft:chain", - "id" : 618 + "id" : 619 }, { "name" : "minecraft:chain_command_block", @@ -797,7 +801,7 @@ }, { "name" : "minecraft:crimson_door", - "id" : 615 + "id" : 616 }, { "name" : "minecraft:crimson_double_slab", @@ -837,7 +841,7 @@ }, { "name" : "minecraft:crimson_sign", - "id" : 613 + "id" : 614 }, { "name" : "minecraft:crimson_slab", @@ -955,10 +959,6 @@ "name" : "minecraft:deadbush", "id" : 32 }, - { - "name" : "minecraft:debug_stick", - "id" : 590 - }, { "name" : "minecraft:deepslate", "id" : -378 @@ -1177,7 +1177,7 @@ }, { "name" : "minecraft:dye", - "id" : 627 + "id" : 634 }, { "name" : "minecraft:egg", @@ -1705,7 +1705,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 630 + "id" : 637 }, { "name" : "minecraft:end_gateway", @@ -1811,6 +1811,10 @@ "name" : "minecraft:fire_charge", "id" : 509 }, + { + "name" : "minecraft:firefly_spawn_egg", + "id" : 632 + }, { "name" : "minecraft:firework_rocket", "id" : 519 @@ -1863,6 +1867,14 @@ "name" : "minecraft:frame", "id" : 513 }, + { + "name" : "minecraft:frog_egg", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 628 + }, { "name" : "minecraft:frosted_ice", "id" : 207 @@ -1899,13 +1911,17 @@ "name" : "minecraft:glistering_melon_slice", "id" : 434 }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, { "name" : "minecraft:glow_berries", - "id" : 631 + "id" : 638 }, { "name" : "minecraft:glow_frame", - "id" : 622 + "id" : 623 }, { "name" : "minecraft:glow_ink_sac", @@ -1921,7 +1937,7 @@ }, { "name" : "minecraft:glow_stick", - "id" : 166 + "id" : 601 }, { "name" : "minecraft:glowingobsidian", @@ -1937,7 +1953,7 @@ }, { "name" : "minecraft:goat_horn", - "id" : 623 + "id" : 624 }, { "name" : "minecraft:goat_spawn_egg", @@ -2577,7 +2593,7 @@ }, { "name" : "minecraft:lodestone_compass", - "id" : 601 + "id" : 602 }, { "name" : "minecraft:log", @@ -2731,9 +2747,13 @@ "name" : "minecraft:music_disc_mellohi", "id" : 540 }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 627 + }, { "name" : "minecraft:music_disc_pigstep", - "id" : 619 + "id" : 620 }, { "name" : "minecraft:music_disc_stal", @@ -2793,7 +2813,7 @@ }, { "name" : "minecraft:nether_sprouts", - "id" : 620 + "id" : 621 }, { "name" : "minecraft:nether_star", @@ -2813,7 +2833,7 @@ }, { "name" : "minecraft:netherite_axe", - "id" : 606 + "id" : 607 }, { "name" : "minecraft:netherite_block", @@ -2821,43 +2841,43 @@ }, { "name" : "minecraft:netherite_boots", - "id" : 611 - }, - { - "name" : "minecraft:netherite_chestplate", - "id" : 609 - }, - { - "name" : "minecraft:netherite_helmet", - "id" : 608 - }, - { - "name" : "minecraft:netherite_hoe", - "id" : 607 - }, - { - "name" : "minecraft:netherite_ingot", - "id" : 602 - }, - { - "name" : "minecraft:netherite_leggings", - "id" : 610 - }, - { - "name" : "minecraft:netherite_pickaxe", - "id" : 605 - }, - { - "name" : "minecraft:netherite_scrap", "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" : 604 + "id" : 605 }, { "name" : "minecraft:netherite_sword", - "id" : 603 + "id" : 604 }, { "name" : "minecraft:netherrack", @@ -2903,6 +2923,10 @@ "name" : "minecraft:ocelot_spawn_egg", "id" : 451 }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, { "name" : "minecraft:orange_candle", "id" : -414 @@ -2959,6 +2983,10 @@ "name" : "minecraft:parrot_spawn_egg", "id" : 478 }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, { "name" : "minecraft:phantom_membrane", "id" : 574 @@ -3593,7 +3621,7 @@ }, { "name" : "minecraft:soul_campfire", - "id" : 621 + "id" : 622 }, { "name" : "minecraft:soul_fire", @@ -3621,7 +3649,7 @@ }, { "name" : "minecraft:spawn_egg", - "id" : 629 + "id" : 636 }, { "name" : "minecraft:spider_eye", @@ -3685,7 +3713,7 @@ }, { "name" : "minecraft:spyglass", - "id" : 625 + "id" : 626 }, { "name" : "minecraft:squid_spawn_egg", @@ -3845,7 +3873,7 @@ }, { "name" : "minecraft:suspicious_stew", - "id" : 589 + "id" : 590 }, { "name" : "minecraft:sweet_berries", @@ -3855,6 +3883,14 @@ "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 @@ -3959,6 +3995,10 @@ "name" : "minecraft:unpowered_repeater", "id" : 93 }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, { "name" : "minecraft:vex_spawn_egg", "id" : 476 @@ -3993,7 +4033,7 @@ }, { "name" : "minecraft:warped_door", - "id" : 616 + "id" : 617 }, { "name" : "minecraft:warped_double_slab", @@ -4013,7 +4053,7 @@ }, { "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 617 + "id" : 618 }, { "name" : "minecraft:warped_hyphae", @@ -4037,7 +4077,7 @@ }, { "name" : "minecraft:warped_sign", - "id" : 614 + "id" : 615 }, { "name" : "minecraft:warped_slab", diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_17_30.json b/core/src/main/resources/bedrock/runtime_item_states.1_18_30.json similarity index 96% rename from core/src/main/resources/bedrock/runtime_item_states.1_17_30.json rename to core/src/main/resources/bedrock/runtime_item_states.1_18_30.json index 79690e3da..4e609c7c0 100644 --- a/core/src/main/resources/bedrock/runtime_item_states.1_17_30.json +++ b/core/src/main/resources/bedrock/runtime_item_states.1_18_30.json @@ -7,6 +7,10 @@ "name" : "minecraft:acacia_button", "id" : -140 }, + { + "name" : "minecraft:acacia_chest_boat", + "id" : 637 + }, { "name" : "minecraft:acacia_door", "id" : 556 @@ -51,6 +55,10 @@ "name" : "minecraft:air", "id" : -158 }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 630 + }, { "name" : "minecraft:allow", "id" : 210 @@ -65,7 +73,7 @@ }, { "name" : "minecraft:amethyst_shard", - "id" : 623 + "id" : 624 }, { "name" : "minecraft:ancient_debris", @@ -117,7 +125,7 @@ }, { "name" : "minecraft:balloon", - "id" : 597 + "id" : 598 }, { "name" : "minecraft:bamboo", @@ -133,7 +141,7 @@ }, { "name" : "minecraft:banner_pattern", - "id" : 627 + "id" : 642 }, { "name" : "minecraft:barrel", @@ -207,6 +215,10 @@ "name" : "minecraft:birch_button", "id" : -141 }, + { + "name" : "minecraft:birch_chest_boat", + "id" : 634 + }, { "name" : "minecraft:birch_door", "id" : 554 @@ -293,7 +305,7 @@ }, { "name" : "minecraft:bleach", - "id" : 595 + "id" : 596 }, { "name" : "minecraft:blue_candle", @@ -317,7 +329,7 @@ }, { "name" : "minecraft:boat", - "id" : 625 + "id" : 640 }, { "name" : "minecraft:bone", @@ -363,10 +375,6 @@ "name" : "minecraft:brewing_stand", "id" : 431 }, - { - "name" : "minecraft:brewingstandblock", - "id" : 117 - }, { "name" : "minecraft:brick", "id" : 383 @@ -429,11 +437,11 @@ }, { "name" : "minecraft:camera", - "id" : 592 + "id" : 593 }, { "name" : "minecraft:campfire", - "id" : 588 + "id" : 589 }, { "name" : "minecraft:candle", @@ -493,7 +501,7 @@ }, { "name" : "minecraft:chain", - "id" : 617 + "id" : 619 }, { "name" : "minecraft:chain_command_block", @@ -531,6 +539,10 @@ "name" : "minecraft:chest", "id" : 54 }, + { + "name" : "minecraft:chest_boat", + "id" : 639 + }, { "name" : "minecraft:chest_minecart", "id" : 389 @@ -575,6 +587,10 @@ "name" : "minecraft:clay_ball", "id" : 384 }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, { "name" : "minecraft:clock", "id" : 393 @@ -669,7 +685,7 @@ }, { "name" : "minecraft:compound", - "id" : 593 + "id" : 594 }, { "name" : "minecraft:concrete", @@ -793,7 +809,7 @@ }, { "name" : "minecraft:crimson_door", - "id" : 614 + "id" : 616 }, { "name" : "minecraft:crimson_double_slab", @@ -833,7 +849,7 @@ }, { "name" : "minecraft:crimson_sign", - "id" : 612 + "id" : 614 }, { "name" : "minecraft:crimson_slab", @@ -903,6 +919,10 @@ "name" : "minecraft:dark_oak_button", "id" : -142 }, + { + "name" : "minecraft:dark_oak_chest_boat", + "id" : 638 + }, { "name" : "minecraft:dark_oak_door", "id" : 557 @@ -1169,7 +1189,7 @@ }, { "name" : "minecraft:dye", - "id" : 626 + "id" : 641 }, { "name" : "minecraft:egg", @@ -1697,7 +1717,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 629 + "id" : 644 }, { "name" : "minecraft:end_gateway", @@ -1803,6 +1823,10 @@ "name" : "minecraft:fire_charge", "id" : 509 }, + { + "name" : "minecraft:firefly_spawn_egg", + "id" : 632 + }, { "name" : "minecraft:firework_rocket", "id" : 519 @@ -1855,6 +1879,14 @@ "name" : "minecraft:frame", "id" : 513 }, + { + "name" : "minecraft:frog_spawn", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 627 + }, { "name" : "minecraft:frosted_ice", "id" : 207 @@ -1891,13 +1923,17 @@ "name" : "minecraft:glistering_melon_slice", "id" : 434 }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, { "name" : "minecraft:glow_berries", - "id" : 630 + "id" : 645 }, { "name" : "minecraft:glow_frame", - "id" : 621 + "id" : 623 }, { "name" : "minecraft:glow_ink_sac", @@ -1913,7 +1949,7 @@ }, { "name" : "minecraft:glow_stick", - "id" : 166 + "id" : 601 }, { "name" : "minecraft:glowingobsidian", @@ -1927,10 +1963,6 @@ "name" : "minecraft:glowstone_dust", "id" : 394 }, - { - "name" : "minecraft:goat_horn", - "id" : 622 - }, { "name" : "minecraft:goat_spawn_egg", "id" : 501 @@ -2109,11 +2141,11 @@ }, { "name" : "minecraft:honey_bottle", - "id" : 591 + "id" : 592 }, { "name" : "minecraft:honeycomb", - "id" : 590 + "id" : 591 }, { "name" : "minecraft:honeycomb_block", @@ -2141,7 +2173,7 @@ }, { "name" : "minecraft:ice_bomb", - "id" : 594 + "id" : 595 }, { "name" : "minecraft:infested_deepslate", @@ -2160,7 +2192,7 @@ "id" : 413 }, { - "name" : "minecraft:invisiblebedrock", + "name" : "minecraft:invisible_bedrock", "id" : 95 }, { @@ -2247,6 +2279,10 @@ "name" : "minecraft:item.birch_door", "id" : 194 }, + { + "name" : "minecraft:item.brewing_stand", + "id" : 117 + }, { "name" : "minecraft:item.cake", "id" : 92 @@ -2355,6 +2391,10 @@ "name" : "minecraft:jungle_button", "id" : -143 }, + { + "name" : "minecraft:jungle_chest_boat", + "id" : 635 + }, { "name" : "minecraft:jungle_door", "id" : 555 @@ -2569,7 +2609,7 @@ }, { "name" : "minecraft:lodestone_compass", - "id" : 600 + "id" : 602 }, { "name" : "minecraft:log", @@ -2611,9 +2651,21 @@ "name" : "minecraft:magma_cube_spawn_egg", "id" : 455 }, + { + "name" : "minecraft:mangrove_leaves", + "id" : -472 + }, + { + "name" : "minecraft:mangrove_propagule", + "id" : -474 + }, + { + "name" : "minecraft:mangrove_propagule_hanging", + "id" : -476 + }, { "name" : "minecraft:medicine", - "id" : 598 + "id" : 599 }, { "name" : "minecraft:medium_amethyst_bud", @@ -2680,9 +2732,33 @@ "id" : -175 }, { - "name" : "minecraft:movingblock", + "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:mule_spawn_egg", "id" : 466 @@ -2723,9 +2799,13 @@ "name" : "minecraft:music_disc_mellohi", "id" : 540 }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 626 + }, { "name" : "minecraft:music_disc_pigstep", - "id" : 618 + "id" : 620 }, { "name" : "minecraft:music_disc_stal", @@ -2777,7 +2857,7 @@ }, { "name" : "minecraft:nether_sprouts", - "id" : 619 + "id" : 621 }, { "name" : "minecraft:nether_star", @@ -2797,7 +2877,7 @@ }, { "name" : "minecraft:netherite_axe", - "id" : 605 + "id" : 607 }, { "name" : "minecraft:netherite_block", @@ -2805,43 +2885,43 @@ }, { "name" : "minecraft:netherite_boots", - "id" : 610 + "id" : 612 }, { "name" : "minecraft:netherite_chestplate", - "id" : 608 + "id" : 610 }, { "name" : "minecraft:netherite_helmet", - "id" : 607 - }, - { - "name" : "minecraft:netherite_hoe", - "id" : 606 - }, - { - "name" : "minecraft:netherite_ingot", - "id" : 601 - }, - { - "name" : "minecraft:netherite_leggings", "id" : 609 }, { - "name" : "minecraft:netherite_pickaxe", - "id" : 604 + "name" : "minecraft:netherite_hoe", + "id" : 608 }, { - "name" : "minecraft:netherite_scrap", - "id" : 611 - }, - { - "name" : "minecraft:netherite_shovel", + "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" : 602 + "id" : 604 }, { "name" : "minecraft:netherrack", @@ -2867,6 +2947,10 @@ "name" : "minecraft:oak_boat", "id" : 375 }, + { + "name" : "minecraft:oak_chest_boat", + "id" : 633 + }, { "name" : "minecraft:oak_sign", "id" : 358 @@ -2887,6 +2971,10 @@ "name" : "minecraft:ocelot_spawn_egg", "id" : 451 }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, { "name" : "minecraft:orange_candle", "id" : -414 @@ -2927,6 +3015,10 @@ "name" : "minecraft:packed_ice", "id" : 174 }, + { + "name" : "minecraft:packed_mud", + "id" : -477 + }, { "name" : "minecraft:painting", "id" : 357 @@ -2943,6 +3035,10 @@ "name" : "minecraft:parrot_spawn_egg", "id" : 478 }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, { "name" : "minecraft:phantom_membrane", "id" : 574 @@ -2992,7 +3088,7 @@ "id" : 33 }, { - "name" : "minecraft:pistonarmcollision", + "name" : "minecraft:piston_arm_collision", "id" : 34 }, { @@ -3257,7 +3353,7 @@ }, { "name" : "minecraft:rapid_fertilizer", - "id" : 596 + "id" : 597 }, { "name" : "minecraft:ravager_spawn_egg", @@ -3371,6 +3467,10 @@ "name" : "minecraft:redstone_wire", "id" : 55 }, + { + "name" : "minecraft:reinforced_deepslate", + "id" : -466 + }, { "name" : "minecraft:repeater", "id" : 419 @@ -3451,6 +3551,10 @@ "name" : "minecraft:scute", "id" : 572 }, + { + "name" : "minecraft:sea_lantern", + "id" : 169 + }, { "name" : "minecraft:sea_pickle", "id" : -156 @@ -3459,10 +3563,6 @@ "name" : "minecraft:seagrass", "id" : -130 }, - { - "name" : "minecraft:sealantern", - "id" : 169 - }, { "name" : "minecraft:shears", "id" : 421 @@ -3577,7 +3677,7 @@ }, { "name" : "minecraft:soul_campfire", - "id" : 620 + "id" : 622 }, { "name" : "minecraft:soul_fire", @@ -3601,11 +3701,11 @@ }, { "name" : "minecraft:sparkler", - "id" : 599 + "id" : 600 }, { "name" : "minecraft:spawn_egg", - "id" : 628 + "id" : 643 }, { "name" : "minecraft:spider_eye", @@ -3635,6 +3735,10 @@ "name" : "minecraft:spruce_button", "id" : -144 }, + { + "name" : "minecraft:spruce_chest_boat", + "id" : 636 + }, { "name" : "minecraft:spruce_door", "id" : 553 @@ -3669,7 +3773,7 @@ }, { "name" : "minecraft:spyglass", - "id" : 624 + "id" : 625 }, { "name" : "minecraft:squid_spawn_egg", @@ -3704,7 +3808,7 @@ "id" : 29 }, { - "name" : "minecraft:stickypistonarmcollision", + "name" : "minecraft:sticky_piston_arm_collision", "id" : -217 }, { @@ -3829,7 +3933,7 @@ }, { "name" : "minecraft:suspicious_stew", - "id" : 589 + "id" : 590 }, { "name" : "minecraft:sweet_berries", @@ -3839,6 +3943,14 @@ "name" : "minecraft:sweet_berry_bush", "id" : -207 }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 629 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 628 + }, { "name" : "minecraft:tallgrass", "id" : 31 @@ -3880,7 +3992,7 @@ "id" : 546 }, { - "name" : "minecraft:tripwire", + "name" : "minecraft:trip_wire", "id" : 132 }, { @@ -3943,6 +4055,10 @@ "name" : "minecraft:unpowered_repeater", "id" : 93 }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, { "name" : "minecraft:vex_spawn_egg", "id" : 476 @@ -3971,13 +4087,17 @@ "name" : "minecraft:wandering_trader_spawn_egg", "id" : 492 }, + { + "name" : "minecraft:warden_spawn_egg", + "id" : 631 + }, { "name" : "minecraft:warped_button", "id" : -261 }, { "name" : "minecraft:warped_door", - "id" : 615 + "id" : 617 }, { "name" : "minecraft:warped_double_slab", @@ -3997,7 +4117,7 @@ }, { "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 616 + "id" : 618 }, { "name" : "minecraft:warped_hyphae", @@ -4021,7 +4141,7 @@ }, { "name" : "minecraft:warped_sign", - "id" : 613 + "id" : 615 }, { "name" : "minecraft:warped_slab", diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index d762220a5..d1a956187 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -66,20 +66,19 @@ remote: # If you're using a plugin version of Floodgate on the same server, the key will automatically be picked up from Floodgate. floodgate-key-file: key.pem -# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info. -# This allows automatic configuration/login to the remote Java server. -# If you are brave enough to put your Mojang account info into a config file. -# Uncomment the lines below to enable this feature. -#userAuths: -# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username -# email: javaccountemail@example.com # Your Minecraft: Java Edition email -# password: javaccountpassword123 # Your Minecraft: Java Edition password -# microsoft-account: true # Whether the account is a Mojang or Microsoft account. -# -# bluerkelp2: -# email: not_really_my_email_address_mr_minecrafter53267@gmail.com -# password: "this isn't really my password" -# microsoft-account: false +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file for this is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. @@ -128,6 +127,10 @@ show-coordinates: true # Whether Bedrock players are blocked from performing their scaffolding-style bridging. disable-bedrock-scaffolding: false +# Whether Bedrock players can right-click outside of their inventory to replace armor in their inventory, even if the +# armor slot is already occupied (which Java Edition doesn't allow) +always-quick-change-armor: false + # If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind # There are three options this can be set to: # disabled - the default/fallback, which doesn't apply this workaround @@ -145,6 +148,13 @@ cache-images: 0 # Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. allow-custom-skulls: true +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + # Whether to add (at this time, only) the furnace minecart as a separate item in the game, which normally does not exist in Bedrock Edition. # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. # If this is disabled, furnace minecart items will be mapped to hopper minecart items. diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index bdee0d0f3..f073cf2b9 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit bdee0d0f3f8a1271cd001f0bd0d672d0010be1db +Subproject commit f073cf2b9e62d1a9da45ac23448d59ca71074339 diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index b60cfcdd4..f73b45844 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit b60cfcdd40cd58a93143b489fc9153a347e48c41 +Subproject commit f73b45844f1185c3898db3052ce4ea0d18246168 diff --git a/pom.xml b/pom.xml index dd5b9a4c7..f4930959d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.1-cumulus-SNAPSHOT + 2.0.3-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. @@ -17,6 +17,8 @@ UTF-8 16 16 + geysermc + https://sonarcloud.io @@ -38,20 +40,7 @@ core - - - - apache.snapshots - https://repository.apache.org/snapshots/ - - - - - - apache.snapshots - https://repository.apache.org/snapshots/ - jitpack.io https://jitpack.io